<commit_before>// Shipments AJAX API
$(document).ready(function () {
  'use strict';

  // handle variant selection, show stock level.
  $('#add_variant_id').change(function(){
    var variant_id = $(this).val();

    Spree.ajax({
      url: Spree.routes.variants_api + "/" + variant_id,
      success: function(variant){
        var variantStockTemplate = HandlebarsTemplates["variants/autocomplete_stock"];
        $('#stock_details').html(variantStockTemplate({variant: variant}));
        $('#stock_details').show();

        $('button.add_variant').click(addVariantFromStockLocation);
      }
    });
  });

  // add header to ship ujs ajax call
  $("form#admin-ship-shipment").on("ajax:beforeSend", function(event, xhr, settings) {
    xhr.setRequestHeader("X-Spree-Token", Spree.api_key);
  });

  $("form#admin-ship-shipment").on("ajax:success", function(event, xhr, settings) {
    window.location.reload();
  });
});

updateShipment = function(shipment_number, attributes) {
  var url = Spree.routes.shipments_api + '/' + shipment_number + '.json';

  return Spree.ajax({
    type: 'PUT',
    url: url,
    data: {
      shipment: attributes
    }
  });
}

adjustShipmentItems = function(shipment_number, variant_id, quantity){
    var shipment = _.findWhere(shipments, {number: shipment_number + ''});
    var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id});

    var url = Spree.routes.shipments_api + "/" + shipment_number;

    var new_quantity = 0;
    if(inventory_units.length<quantity){
      url += "/add"
      new_quantity = (quantity - inventory_units.length);
    }else if(inventory_units.length>quantity){
      url += "/remove"
      new_quantity = (inventory_units.length - quantity);
    }
    url += '.json';

    if(new_quantity!=0){
      Spree.ajax({
        type: "PUT",
        url: url,
        data: {
          variant_id: variant_id,
          quantity: new_quantity,
        },
        success: function(response) {
          window.location.reload();
        },
        error: function(response) {
          show_flash('error', response.responseJSON.message);
        }
      });
    }
}

deleteLineItem = function(line_item_id){
  var url = Spree.routes.line_items_api(order_number) + "/" + line_item_id + ".json";

  Spree.ajax({
    type: "DELETE",
    url: url,
    success: function(response) {
      window.location.reload();
    },
    error: function(response) {
      show_flash('error', response.responseJSON.message);
    }
  });
}

startItemSplit = function(event){
  event.preventDefault();
  var link = $(this);
  link.parent().find('a.split-item').toggle();
  link.parent().find('a.delete-item').toggle();
  var variant_id = link.data('variant-id');

  Spree.ajax({
    type: "GET",
    url: Spree.routes.variants_api + "/" + variant_id,
  }).success(function(variant){
    var max_quantity = link.closest('tr').data('item-quantity');
    var split_item_template = HandlebarsTemplates['variants/split'];
    link.closest('tr').after(split_item_template({ variant: variant, shipments: shipments, max_quantity: max_quantity }));

    $('#item_stock_location').select2({ width: 'resolve', placeholder: Spree.translations.item_stock_placeholder });
  })
}

completeItemSplit = function(event) {
  event.preventDefault();

  if($('#item_stock_location').val() === ""){
      alert('Please select the split destination.');
      return false;
  }

  var link = $(this);
  var stock_item_row = link.closest('tr');
  var variant_id = stock_item_row.data('variant-id');
  var quantity = stock_item_row.find('#item_quantity').val();

  var stock_location_id = stock_item_row.find('#item_stock_location').val();
  var original_shipment_number = link.closest('tbody').data('shipment-number');

  var selected_shipment = stock_item_row.find($('#item_stock_location').select2('data').element);
  var target_shipment_number = selected_shipment.data('shipment-number');
  var new_shipment = selected_shipment.data('new-shipment');

  if (stock_location_id != 'new_shipment') {
    if (new_shipment != undefined) {
      // TRANSFER TO A NEW LOCATION
      Spree.ajax({
        type: "POST",
        async: false,
        url: Spree.routes.shipments_api + "/transfer_to_location",
        data: {
            original_shipment_number: original_shipment_number,
            variant_id: variant_id,
            quantity: quantity,
            stock_location_id: stock_location_id
        }
      }).error(function(msg) {
          alert(msg.responseJSON['message']);
      }).done(function(msg) {
        window.Spree.advanceOrder();
      });
    } else {
        // TRANSFER TO AN EXISTING SHIPMENT
        Spree.ajax({
            type: "POST",
            async: false,
            url: Spree.routes.shipments_api + "/transfer_to_shipment",
            data: {
                original_shipment_number: original_shipment_number,
                target_shipment_number: target_shipment_number,
                variant_id: variant_id,
                quantity: quantity
            }
        }).error(function(msg) {
            alert(msg.responseJSON['message']);
        }).done(function(msg) {
            window.Spree.advanceOrder();
        });
    }
  }
}

addVariantFromStockLocation = function(event) {
  event.preventDefault();

  $('#stock_details').hide();

  var variant_id = $('input.variant_autocomplete').val();
  var stock_location_id = $(this).data('stock-location-id');
  var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();

  var shipment = _.find(shipments, function(shipment){
    return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
  });

  if(shipment==undefined){
    Spree.ajax({
      type: "POST",
      url: Spree.routes.shipments_api,
      data: {
        shipment: {
          order_id: order_number
        },
        variant_id: variant_id,
        quantity: quantity,
        stock_location_id: stock_location_id,
      }
    }).done(function( msg ) {
      window.location.reload();
    });
  }else{
    //add to existing shipment
    adjustShipmentItems(shipment.number, variant_id, quantity);
  }
}

var ShipmentEditView = Backbone.View.extend({
  initialize: function(){
    var tbody = this.$("tbody[data-order-number][data-shipment-number]");
    this.shipment_number = tbody.data("shipment-number");
    this.order_number = tbody.data("order-number");
  },

  events: {
    "click a.delete-item": "deleteItem",

    "click a.split-item": "startItemSplit",
    "click a.cancel-split": "cancelItemSplit",
    "click a.save-split": "completeItemSplit",

    "click a.edit-method": "toggleMethodEdit",
    "click a.cancel-method": "toggleMethodEdit",
    "click a.save-method": "saveMethod",

    "click a.edit-tracking": "toggleTrackingEdit",
    "click a.cancel-tracking": "toggleTrackingEdit",
    "click a.save-tracking": "saveTracking",
  },

  deleteItem: function(e){
    e.preventDefault();
    if (confirm(Spree.translations.are_you_sure_delete)) {
      var del = $(e.currentTarget);
      var line_item_id = del.data('line-item-id');

      deleteLineItem(line_item_id);
    }
  },

  startItemSplit: function(e){
    e.preventDefault();
    startItemSplit.apply(e.currentTarget, [e]);
  },

  cancelItemSplit: function(e){
    e.preventDefault();
    this.$('tr.stock-item-split').remove();
    this.$('a.split-item').show();
    this.$('a.delete-item').show();
  },

  completeItemSplit: function(e){
    completeItemSplit.apply(e.currentTarget, [e]);
  },

  toggleMethodEdit: function(e){
    e.preventDefault();
    this.$('tr.edit-method').toggle();
    this.$('tr.show-method').toggle();
  },

  saveMethod: function(e) {
    e.preventDefault();
    var selected_shipping_rate_id = this.$("select#selected_shipping_rate_id").val();
    updateShipment(this.shipment_number, {
      selected_shipping_rate_id: selected_shipping_rate_id
    }).done(function () {
      window.location.reload();
    });
  },

  toggleTrackingEdit: function(e) {
    e.preventDefault();
    this.$("tr.edit-tracking").toggle()
    this.$("tr.show-tracking").toggle()
  },

  saveTracking: function(e) {
    e.preventDefault();
    var tracking = this.$('input#tracking').val();
    var _this = this;
    updateShipment(this.shipment_number, {
      tracking: tracking
    }).done(function (data) {
      _this.$('tr.edit-tracking').toggle();

      var show = _this.$('tr.show-tracking');
      show.toggle()
          .find('.tracking-value')
          .html($("<strong>")
          .html(Spree.translations.tracking + ": "))
          .append(document.createTextNode(data.tracking));
    });
  }
});

$(function(){
  $(".js-shipment-edit").each(function(){
    new ShipmentEditView({ el: $(this) });
  });
});
<commit_msg>Try async true again<commit_after>// Shipments AJAX API
$(document).ready(function () {
  'use strict';

  // handle variant selection, show stock level.
  $('#add_variant_id').change(function(){
    var variant_id = $(this).val();

    Spree.ajax({
      url: Spree.routes.variants_api + "/" + variant_id,
      success: function(variant){
        var variantStockTemplate = HandlebarsTemplates["variants/autocomplete_stock"];
        $('#stock_details').html(variantStockTemplate({variant: variant}));
        $('#stock_details').show();

        $('button.add_variant').click(addVariantFromStockLocation);
      }
    });
  });

  // add header to ship ujs ajax call
  $("form#admin-ship-shipment").on("ajax:beforeSend", function(event, xhr, settings) {
    xhr.setRequestHeader("X-Spree-Token", Spree.api_key);
  });

  $("form#admin-ship-shipment").on("ajax:success", function(event, xhr, settings) {
    window.location.reload();
  });
});

updateShipment = function(shipment_number, attributes) {
  var url = Spree.routes.shipments_api + '/' + shipment_number + '.json';

  return Spree.ajax({
    type: 'PUT',
    url: url,
    data: {
      shipment: attributes
    }
  });
}

adjustShipmentItems = function(shipment_number, variant_id, quantity){
    var shipment = _.findWhere(shipments, {number: shipment_number + ''});
    var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id});

    var url = Spree.routes.shipments_api + "/" + shipment_number;

    var new_quantity = 0;
    if(inventory_units.length<quantity){
      url += "/add"
      new_quantity = (quantity - inventory_units.length);
    }else if(inventory_units.length>quantity){
      url += "/remove"
      new_quantity = (inventory_units.length - quantity);
    }
    url += '.json';

    if(new_quantity!=0){
      Spree.ajax({
        type: "PUT",
        url: url,
        data: {
          variant_id: variant_id,
          quantity: new_quantity,
        },
        success: function(response) {
          window.location.reload();
        },
        error: function(response) {
          show_flash('error', response.responseJSON.message);
        }
      });
    }
}

deleteLineItem = function(line_item_id){
  var url = Spree.routes.line_items_api(order_number) + "/" + line_item_id + ".json";

  Spree.ajax({
    type: "DELETE",
    url: url,
    success: function(response) {
      window.location.reload();
    },
    error: function(response) {
      show_flash('error', response.responseJSON.message);
    }
  });
}

startItemSplit = function(event){
  event.preventDefault();
  var link = $(this);
  link.parent().find('a.split-item').toggle();
  link.parent().find('a.delete-item').toggle();
  var variant_id = link.data('variant-id');

  Spree.ajax({
    type: "GET",
    url: Spree.routes.variants_api + "/" + variant_id,
  }).success(function(variant){
    var max_quantity = link.closest('tr').data('item-quantity');
    var split_item_template = HandlebarsTemplates['variants/split'];
    link.closest('tr').after(split_item_template({ variant: variant, shipments: shipments, max_quantity: max_quantity }));

    $('#item_stock_location').select2({ width: 'resolve', placeholder: Spree.translations.item_stock_placeholder });
  })
}

completeItemSplit = function(event) {
  event.preventDefault();

  if($('#item_stock_location').val() === ""){
      alert('Please select the split destination.');
      return false;
  }

  var link = $(this);
  var stock_item_row = link.closest('tr');
  var variant_id = stock_item_row.data('variant-id');
  var quantity = stock_item_row.find('#item_quantity').val();

  var stock_location_id = stock_item_row.find('#item_stock_location').val();
  var original_shipment_number = link.closest('tbody').data('shipment-number');

  var selected_shipment = stock_item_row.find($('#item_stock_location').select2('data').element);
  var target_shipment_number = selected_shipment.data('shipment-number');
  var new_shipment = selected_shipment.data('new-shipment');

  if (stock_location_id != 'new_shipment') {
    if (new_shipment != undefined) {
      // TRANSFER TO A NEW LOCATION
      Spree.ajax({
        type: "POST",
        url: Spree.routes.shipments_api + "/transfer_to_location",
        data: {
            original_shipment_number: original_shipment_number,
            variant_id: variant_id,
            quantity: quantity,
            stock_location_id: stock_location_id
        }
      }).error(function(msg) {
          alert(msg.responseJSON['message']);
      }).done(function(msg) {
        window.Spree.advanceOrder();
      });
    } else {
        // TRANSFER TO AN EXISTING SHIPMENT
        Spree.ajax({
            type: "POST",
            url: Spree.routes.shipments_api + "/transfer_to_shipment",
            data: {
                original_shipment_number: original_shipment_number,
                target_shipment_number: target_shipment_number,
                variant_id: variant_id,
                quantity: quantity
            }
        }).error(function(msg) {
            alert(msg.responseJSON['message']);
        }).done(function(msg) {
            window.Spree.advanceOrder();
        });
    }
  }
}

addVariantFromStockLocation = function(event) {
  event.preventDefault();

  $('#stock_details').hide();

  var variant_id = $('input.variant_autocomplete').val();
  var stock_location_id = $(this).data('stock-location-id');
  var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();

  var shipment = _.find(shipments, function(shipment){
    return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
  });

  if(shipment==undefined){
    Spree.ajax({
      type: "POST",
      url: Spree.routes.shipments_api,
      data: {
        shipment: {
          order_id: order_number
        },
        variant_id: variant_id,
        quantity: quantity,
        stock_location_id: stock_location_id,
      }
    }).done(function( msg ) {
      window.location.reload();
    });
  }else{
    //add to existing shipment
    adjustShipmentItems(shipment.number, variant_id, quantity);
  }
}

var ShipmentEditView = Backbone.View.extend({
  initialize: function(){
    var tbody = this.$("tbody[data-order-number][data-shipment-number]");
    this.shipment_number = tbody.data("shipment-number");
    this.order_number = tbody.data("order-number");
  },

  events: {
    "click a.delete-item": "deleteItem",

    "click a.split-item": "startItemSplit",
    "click a.cancel-split": "cancelItemSplit",
    "click a.save-split": "completeItemSplit",

    "click a.edit-method": "toggleMethodEdit",
    "click a.cancel-method": "toggleMethodEdit",
    "click a.save-method": "saveMethod",

    "click a.edit-tracking": "toggleTrackingEdit",
    "click a.cancel-tracking": "toggleTrackingEdit",
    "click a.save-tracking": "saveTracking",
  },

  deleteItem: function(e){
    e.preventDefault();
    if (confirm(Spree.translations.are_you_sure_delete)) {
      var del = $(e.currentTarget);
      var line_item_id = del.data('line-item-id');

      deleteLineItem(line_item_id);
    }
  },

  startItemSplit: function(e){
    e.preventDefault();
    startItemSplit.apply(e.currentTarget, [e]);
  },

  cancelItemSplit: function(e){
    e.preventDefault();
    this.$('tr.stock-item-split').remove();
    this.$('a.split-item').show();
    this.$('a.delete-item').show();
  },

  completeItemSplit: function(e){
    completeItemSplit.apply(e.currentTarget, [e]);
  },

  toggleMethodEdit: function(e){
    e.preventDefault();
    this.$('tr.edit-method').toggle();
    this.$('tr.show-method').toggle();
  },

  saveMethod: function(e) {
    e.preventDefault();
    var selected_shipping_rate_id = this.$("select#selected_shipping_rate_id").val();
    updateShipment(this.shipment_number, {
      selected_shipping_rate_id: selected_shipping_rate_id
    }).done(function () {
      window.location.reload();
    });
  },

  toggleTrackingEdit: function(e) {
    e.preventDefault();
    this.$("tr.edit-tracking").toggle()
    this.$("tr.show-tracking").toggle()
  },

  saveTracking: function(e) {
    e.preventDefault();
    var tracking = this.$('input#tracking').val();
    var _this = this;
    updateShipment(this.shipment_number, {
      tracking: tracking
    }).done(function (data) {
      _this.$('tr.edit-tracking').toggle();

      var show = _this.$('tr.show-tracking');
      show.toggle()
          .find('.tracking-value')
          .html($("<strong>")
          .html(Spree.translations.tracking + ": "))
          .append(document.createTextNode(data.tracking));
    });
  }
});

$(function(){
  $(".js-shipment-edit").each(function(){
    new ShipmentEditView({ el: $(this) });
  });
});
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import * as styles from './Settings.css';
import DatabaseDropdown from './DatabaseDropdown/DatabaseDropdown.react';
import TableDropdown from './TableDropdown/TableDropdown.react';
import ConnectButton from './ConnectButton/ConnectButton.react';
import UserCredentials from './UserCredentials/UserCredentials.react';
import LoggerController from './Logger/LoggerController.react';
import Preview from './Preview/Preview.react';
import DialectSelector from './DialectSelector/DialectSelector.react';
import {APP_STATUS} from '../../constants/constants';

export default class Settings extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        const {
            configuration, configActions,
            ipc, ipcActions,
            connection, connectionActions
        } = this.props;

        const dialectSelector = (

                <DialectSelector
                    configActions={configActions}
                    configuration={configuration}
                />

        );

        const userCredentials = (
            <UserCredentials
                configuration={configuration}
                configActions={configActions}
            />
        );

        const connectButton = (
            <ConnectButton
                configuration={configuration}
                connection={connection}
                connectionActions={connectionActions}
                ipc={ipc}
                ipcActions={ipcActions}
            />
        );

        let tablePreview = null;
        if (ipc.has('previews')) {
            tablePreview = (

                    <Preview
                        ipcActions={ipcActions}
                        ipc={ipc}
                    />

            );
        }

        const selectDatabaseTable = (

                <DatabaseDropdown
                    configuration={configuration}
                    configActions={configActions}
                    ipc={ipc}
                    ipcActions={ipcActions}
                />

                <TableDropdown
                    ipc={ipc}
                    ipcActions={ipcActions}
                />

        );

        const logger = (
            <LoggerController ipc={ipc}/>
        );

        const step1 = (

1. Connect to Database

                        {dialectSelector}

                        {userCredentials}

                    {connectButton}

        );

        let step2 = null;
        let step3 = null;
        if (connection.get('status') === APP_STATUS.CONNECTED ||
            connection.get('status') === APP_STATUS.ERROR) {
            step2 = (

2. Preview Database and Tables

                    {selectDatabaseTable}
                    {tablePreview}

            );

            step3 = (
                /*eslint-disable */

3. Query from Plotly 2.0

                        Query data by clicking on 'import data' from
                        {" "}

                        plotly workspace

                        {" "}
                        and choose the SQL option.

                        Remember to keep this app running
                        while you are making queries!

                /*eslint-disable */

            );
        }

        const logContainer = (

                    {logger}

        );
        /*eslint-disable */
        return (

                        Plotly 2.0 Database Connector

                        Learn more
                        {" "}
                        here
                        {" "}
                        about setting up a connection using https.

                    {step1}
                    {step2}
                    {step3}
                    {logContainer}

        );
        /*eslint-disable */
    }
}

Settings.propTypes = {
    configuration: ImmutablePropTypes.map.isRequired,
    configActions: PropTypes.object,
    ipc: ImmutablePropTypes.map.isRequired,
    ipcActions: PropTypes.object,
    connection: ImmutablePropTypes.map.isRequired,
    connectionActions: PropTypes.object
};
<commit_msg>:koala: show user which serve the app is running on to help when connecting from workspace<commit_after>import React, {Component, PropTypes} from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import * as styles from './Settings.css';
import DatabaseDropdown from './DatabaseDropdown/DatabaseDropdown.react';
import TableDropdown from './TableDropdown/TableDropdown.react';
import ConnectButton from './ConnectButton/ConnectButton.react';
import UserCredentials from './UserCredentials/UserCredentials.react';
import LoggerController from './Logger/LoggerController.react';
import Preview from './Preview/Preview.react';
import DialectSelector from './DialectSelector/DialectSelector.react';
import {APP_STATUS} from '../../constants/constants';
import {OPTIONS} from '../../../sequelizeManager';

export default class Settings extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        const {
            configuration, configActions,
            ipc, ipcActions,
            connection, connectionActions
        } = this.props;

        const dialectSelector = (

                <DialectSelector
                    configActions={configActions}
                    configuration={configuration}
                />

        );

        const userCredentials = (
            <UserCredentials
                configuration={configuration}
                configActions={configActions}
            />
        );

        const connectButton = (
            <ConnectButton
                configuration={configuration}
                connection={connection}
                connectionActions={connectionActions}
                ipc={ipc}
                ipcActions={ipcActions}
            />
        );

        let tablePreview = null;
        if (ipc.has('previews')) {
            tablePreview = (

                    <Preview
                        ipcActions={ipcActions}
                        ipc={ipc}
                    />

            );
        }

        const selectDatabaseTable = (

                <DatabaseDropdown
                    configuration={configuration}
                    configActions={configActions}
                    ipc={ipc}
                    ipcActions={ipcActions}
                />

                <TableDropdown
                    ipc={ipc}
                    ipcActions={ipcActions}
                />

        );

        const logger = (
            <LoggerController ipc={ipc}/>
        );

        const step1 = (

1. Connect to Database

                        {dialectSelector}

                        {userCredentials}

                    {connectButton}

        );

        let step2 = null;
        let step3 = null;
        if (connection.get('status') === APP_STATUS.CONNECTED ||
            connection.get('status') === APP_STATUS.ERROR) {
            step2 = (

2. Preview Database and Tables

                    {selectDatabaseTable}
                    {tablePreview}

            );

            step3 = (
                /*eslint-disable */

3. Query from Plotly 2.0

                        Query data by clicking on 'import data' from
                        {" "}

                        plotly workspace

                        {" "}
                        and choose the SQL option.

                        Remember to keep this app running
                        while you are making queries!

                /*eslint-disable */

            );
        }

        const logContainer = (

                    {logger}

        );
        /*eslint-disable */
        return (

                        Plotly 2.0 Database Connector

                        This application is running on {" "}
                        http://localhost:{OPTIONS.port} and {" "}
                        https://connector.plot.ly:{OPTIONS.port + 1}

                        Learn more
                        {" "}
                        here
                        {" "}
                        about setting up a connection using https.

                    {step1}
                    {step2}
                    {step3}
                    {logContainer}

        );
        /*eslint-disable */
    }
}

Settings.propTypes = {
    configuration: ImmutablePropTypes.map.isRequired,
    configActions: PropTypes.object,
    ipc: ImmutablePropTypes.map.isRequired,
    ipcActions: PropTypes.object,
    connection: ImmutablePropTypes.map.isRequired,
    connectionActions: PropTypes.object
};
<|endoftext|>


<commit_before>define(function(){

	// Adds an annimation to the hamburger inside header.php.

	var Hamburger = function(toggleID, closed, opened) {
		var toggleDiv = document.getElementById(toggleID),
			closeDiv = document.getElementsByClassName(closed)[0],
			openDiv = ' ' + opened;

		toggleDiv.onchange = function(){
			$(closeDiv).toggleClass(openDiv);
<<<<<<< HEAD
		};
	};
=======
		}
	}
>>>>>>> 7b8fa3dc106727de504c15bb5eaac298df4f42bc

	return Hamburger;
});<commit_msg>Update hamburger.js<commit_after>define(function(){

	// Adds an annimation to the hamburger inside header.php.

	var Hamburger = function(toggleID, closed, opened) {
		var toggleDiv = document.getElementById(toggleID),
			closeDiv = document.getElementsByClassName(closed)[0],
			openDiv = ' ' + opened;

		toggleDiv.onchange = function(){
			$(closeDiv).toggleClass(openDiv);
		}
	}

	return Hamburger;
});
<|endoftext|>


<commit_before>import React from 'react';
import Relay from 'react-relay';
import { Link } from 'react-router';
import classNames from 'classnames';

import Emojify from '../../shared/Emojify';
import Panel from '../../shared/Panel';

import 'highlight.js/styles/atom-one-light.css';

// Grab guides from the file system (at ../../docs)
const GUIDES = ((guideRequire) =>
  guideRequire.keys().map((guidePath) =>
    guideRequire(guidePath).default
  )
)(require.context(
  '../../docs',
  true,
  /^\.\/[^\/]+(?:\/index)?\.[^\/]*$/ // matches any file in ../../docs, or any index file in a subdirectory of ../../docs
));

const getEmojiForGuide = ({ emoji, title }) => emoji || `:${title.toLowerCase()}:`;
const getSlugForGuide = ({ slug, title }) => slug || encodeURIComponent(title.toLowerCase());

class QuickStart extends React.Component {
  static propTypes = {
    hash: React.PropTypes.string,
    organization: React.PropTypes.shape({
      name: React.PropTypes.string.isRequired,
      slug: React.PropTypes.string.isRequired,
      uuid: React.PropTypes.string.isRequired,
      agentTokens: React.PropTypes.shape({
        edges: React.PropTypes.array.isRequired
      })
    }).isRequired,
    viewer: React.PropTypes.shape({
      apiAccessTokens: React.PropTypes.shape({
        edges: React.PropTypes.array.isRequired
      })
    }),
    relay: React.PropTypes.object.isRequired
  };

  componentDidMount() {
    this.props.relay.setVariables({
      isMounted: true,
      organizationSlug: this.props.organization.slug
    });
  }

  getSlugFromHash() {
    if (this.props.hash.length < 2) {
      return null;
    }

    return this.props.hash.split('#setup-').pop() || null;
  }

  getIndexOfGuide() {
    const slugToFind = this.getSlugFromHash();

    if (!slugToFind) {
      return null;
    }

    const index = GUIDES.findIndex(
      (Guide) => getSlugForGuide(Guide) === slugToFind
    );

    if (index === -1) {
      return null;
    }

    return index;
  }

  renderGuideButtons(selectedGuideIndex) {
    const baseUri = `/organizations/${this.props.organization.slug}/agents`;

    return (

        {
          GUIDES.map((Guide, index) => (
            <Link
              key={index}
              to={
                index === selectedGuideIndex
                  ? baseUri // use base route URI for selected guide so we can close it
                  : `${baseUri}#setup-${getSlugForGuide(Guide)}`
              }
              className={classNames(
                'inline-block blue hover-navy text-decoration-none border rounded m1 p1',
                {
                  'border-white': index !== selectedGuideIndex,
                  'border-gray': index === selectedGuideIndex
                }
              )}
            >
              <Emojify
                className="block mt1"
                style={{ fontSize: '1.15em' }}
                text={getEmojiForGuide(Guide)}
              />
              {Guide.title}
            </Link>
          ))
        }

    );
  }

  renderGuide(selectedGuideIndex) {
    const GuideToRender = GUIDES[selectedGuideIndex];
    const { name, slug, uuid, agentTokens: { edges: agentTokens } = {} } = this.props.organization;
    const { apiAccessTokens: { edges: apiAccessTokens } = {} } = this.props.viewer;

    if (GuideToRender) {
      return (
        <GuideToRender
          className="border border-gray mt3"
          style={{
            borderLeft: 'none',
            borderRight: 'none',
            borderBottom: 'none'
          }}
          elementProps={{
            'code': {
              style: { fontFamily: 'Monaco, Consolas, monospace', fontSize: '.9em' }
            },
            'a': {
              className: 'blue hover-navy text-decoration-none hover-underline'
            },
            'pre': {
              className: 'border border-gray rounded bg-silver overflow-auto p2 monospace'
            }
          }}
          token={
            agentTokens
              && agentTokens.length
              && agentTokens[0].node.token
          }
          apiAccessTokens={apiAccessTokens ? apiAccessTokens.map((edge) => edge.node) : []}
          organization={{ name, slug, uuid }}
        />
      );
    }
  }

  render() {
    const selectedGuideIndex = this.getIndexOfGuide();

    return (
      <Panel className="mb3">
        <Panel.Header className="center">Agent Quick Start Guides</Panel.Header>
        <Panel.Section>
          {this.renderGuideButtons(selectedGuideIndex)}
          {this.renderGuide(selectedGuideIndex)}
        </Panel.Section>
      </Panel>
    );
  }
}

export default Relay.createContainer(QuickStart, {
  initialVariables: {
    isMounted: false,
    organizationSlug: null
  },

  fragments: {
    viewer: () => Relay.QL`
      fragment on Viewer {
        apiAccessTokens(
          first: 10,
          template: [ API_ACCESS_TOKEN_TEMPLATE_ELASTIC_CI_AWS ],
          organizations: [ $organizationSlug ]
        ) @include(if: $isMounted) {
          edges {
            node {
              description
              uuid
              token
            }
          }
        }
      }
    `,
    organization: () => Relay.QL`
      fragment on Organization {
        name
        slug
        uuid
        agentTokens(first: 1, revoked: false) @include(if: $isMounted) {
          edges {
            node {
              token
            }
          }
        }
      }
    `
  }
});
<commit_msg>Shallow compare in quick start component<commit_after>import React from 'react';
import Relay from 'react-relay';
import { Link } from 'react-router';
import shallowCompare from 'react-addons-shallow-compare';
import classNames from 'classnames';

import Emojify from '../../shared/Emojify';
import Panel from '../../shared/Panel';

import 'highlight.js/styles/atom-one-light.css';

// Grab guides from the file system (at ../../docs)
const GUIDES = ((guideRequire) =>
  guideRequire.keys().map((guidePath) =>
    guideRequire(guidePath).default
  )
)(require.context(
  '../../docs',
  true,
  /^\.\/[^\/]+(?:\/index)?\.[^\/]*$/ // matches any file in ../../docs, or any index file in a subdirectory of ../../docs
));

const getEmojiForGuide = ({ emoji, title }) => emoji || `:${title.toLowerCase()}:`;
const getSlugForGuide = ({ slug, title }) => slug || encodeURIComponent(title.toLowerCase());

class QuickStart extends React.Component {
  static propTypes = {
    hash: React.PropTypes.string,
    organization: React.PropTypes.shape({
      name: React.PropTypes.string.isRequired,
      slug: React.PropTypes.string.isRequired,
      uuid: React.PropTypes.string.isRequired,
      agentTokens: React.PropTypes.shape({
        edges: React.PropTypes.array.isRequired
      })
    }).isRequired,
    viewer: React.PropTypes.shape({
      apiAccessTokens: React.PropTypes.shape({
        edges: React.PropTypes.array.isRequired
      })
    }),
    relay: React.PropTypes.object.isRequired
  };

  componentDidMount() {
    this.props.relay.setVariables({
      isMounted: true,
      organizationSlug: this.props.organization.slug
    });
  }

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  getSlugFromHash() {
    if (this.props.hash.length < 2) {
      return null;
    }

    return this.props.hash.split('#setup-').pop() || null;
  }

  getIndexOfGuide() {
    const slugToFind = this.getSlugFromHash();

    if (!slugToFind) {
      return null;
    }

    const index = GUIDES.findIndex(
      (Guide) => getSlugForGuide(Guide) === slugToFind
    );

    if (index === -1) {
      return null;
    }

    return index;
  }

  renderGuideButtons(selectedGuideIndex) {
    const baseUri = `/organizations/${this.props.organization.slug}/agents`;

    return (

        {
          GUIDES.map((Guide, index) => (
            <Link
              key={index}
              to={
                index === selectedGuideIndex
                  ? baseUri // use base route URI for selected guide so we can close it
                  : `${baseUri}#setup-${getSlugForGuide(Guide)}`
              }
              className={classNames(
                'inline-block blue hover-navy text-decoration-none border rounded m1 p1',
                {
                  'border-white': index !== selectedGuideIndex,
                  'border-gray': index === selectedGuideIndex
                }
              )}
            >
              <Emojify
                className="block mt1"
                style={{ fontSize: '1.15em' }}
                text={getEmojiForGuide(Guide)}
              />
              {Guide.title}
            </Link>
          ))
        }

    );
  }

  renderGuide(selectedGuideIndex) {
    const GuideToRender = GUIDES[selectedGuideIndex];
    const { name, slug, uuid, agentTokens: { edges: agentTokens } = {} } = this.props.organization;
    const { apiAccessTokens: { edges: apiAccessTokens } = {} } = this.props.viewer;

    if (GuideToRender) {
      return (
        <GuideToRender
          className="border border-gray mt3"
          style={{
            borderLeft: 'none',
            borderRight: 'none',
            borderBottom: 'none'
          }}
          elementProps={{
            'code': {
              style: { fontFamily: 'Monaco, Consolas, monospace', fontSize: '.9em' }
            },
            'a': {
              className: 'blue hover-navy text-decoration-none hover-underline'
            },
            'pre': {
              className: 'border border-gray rounded bg-silver overflow-auto p2 monospace'
            }
          }}
          token={
            agentTokens
              && agentTokens.length
              && agentTokens[0].node.token
          }
          apiAccessTokens={apiAccessTokens ? apiAccessTokens.map((edge) => edge.node) : []}
          organization={{ name, slug, uuid }}
        />
      );
    }
  }

  render() {
    const selectedGuideIndex = this.getIndexOfGuide();

    return (
      <Panel className="mb3">
        <Panel.Header className="center">Agent Quick Start Guides</Panel.Header>
        <Panel.Section>
          {this.renderGuideButtons(selectedGuideIndex)}
          {this.renderGuide(selectedGuideIndex)}
        </Panel.Section>
      </Panel>
    );
  }
}

export default Relay.createContainer(QuickStart, {
  initialVariables: {
    isMounted: false,
    organizationSlug: null
  },

  fragments: {
    viewer: () => Relay.QL`
      fragment on Viewer {
        apiAccessTokens(
          first: 10,
          template: [ API_ACCESS_TOKEN_TEMPLATE_ELASTIC_CI_AWS ],
          organizations: [ $organizationSlug ]
        ) @include(if: $isMounted) {
          edges {
            node {
              description
              uuid
              token
            }
          }
        }
      }
    `,
    organization: () => Relay.QL`
      fragment on Organization {
        name
        slug
        uuid
        agentTokens(first: 1, revoked: false) @include(if: $isMounted) {
          edges {
            node {
              token
            }
          }
        }
      }
    `
  }
});
<|endoftext|>


<commit_before>
exports.bind = function(context, method/*, args... */) {
	if (!context || !method || (typeof method == 'string' && !context[method])) { throw "bad bind arguments" }
	var args = Array.prototype.slice.call(arguments, 2)
	return function() {
		fn = (typeof method == 'string' ? context[method] : method)
		return fn.apply(context, args.concat(Array.prototype.slice.call(arguments, 0)))
	}
}

exports.forEach = function(items, ctx, fn) {
	if (!items) { return }
	if (!fn) { fn = ctx, ctx = this }
	if (exports.isArray(items)) {
		for (var i=0, item; item = items[i]; i++) { fn.call(ctx, item, i) }
	} else {
		for (var key in items) { fn.call(ctx, key, items[key]) }
	}
}

exports.map = function(items, fn) {
	var results = []
	exports.forEach(items, function(item) { results.push(fn(item)) })
	return results
}

exports.Class = function(parent, proto) {
	if(!proto) { proto = parent }
	proto.prototype = parent.prototype

	var cls = function() { if(this.init) { this.init.apply(this, arguments) }}
	cls.prototype = new proto(function(context, method, args) {
		var target = parent
		while(target = target.prototype) {
			if(target[method]) {
				return target[method].apply(context, args || [])
			}
		}
		throw new Error('supr: parent method ' + method + ' does not exist')
	})

	// Sometimes you want a method that renders UI to only execute once if it's called
	// multiple times within a short time period. Delayed methods do just that
	cls.prototype.createDelayedMethod = function(methodName, fn) {
		// "this" is the class
		this[methodName] = function() {
			// now "this" is the instance. Each instance gets its own function
			var executionTimeout
			this[methodName] = bind(this, function() {
				clearTimeout(executionTimeout)
				executionTimeout = setTimeout(bind(fn, 'apply', this, arguments), 10)
			})
			this[methodName].apply(this, arguments)
		}
	}

	cls.prototype.constructor = cls
	return cls
}

exports.Singleton = function(parent, proto) {
	return new (exports.Class(parent, proto))()
}

var stripRegexp = /^\s*(.*?)\s*$/
exports.strip = function(str) {
	return str.match(stripRegexp)[1]
}

exports.capitalize = function(str) {
	if (!str) { return '' }
	return str[0].toUpperCase() + str.substring(1)
}

exports.isArray = function(obj) {
	return Object.prototype.toString.call(obj) === '[object Array]'
}

exports.blockCallback = function(callback, opts) {
	opts = opts || {}
	opts.fireOnce = (typeof opts.fireOnce != 'undefined' ? opts.fireOnce : true)
	var blocks = 0,
		fired = false,
		result = {
		addBlock: function() {
			blocks++
			var blockReleased = false
			return function(err) {
				if (err && opts.throwErr) {
					throw err
				}
				if (blockReleased) {
					result.tryNow()
					return
				}
				blockReleased = true
				blocks--
				setTimeout(result.tryNow)
			}
		},
		tryNow: function() {
			if (fired && opts.fireOnce) { return }
			if (blocks == 0) {
				fired = true
				callback()
			}
		}
	}
	return result
}

exports.bytesToString = function(byteArray, offset) {
	return byteArray.toString();
	return String.fromCharCode.apply(String, Array.prototype.slice.call(byteArray, offset || 0))
}

exports.recall = function(self, args, timeout) {
	var fn = args.callee
	return function(){ return fn.apply(self, args) }
}

exports.assert = function(shouldBeTrue, msg, values) {
	if (shouldBeTrue) { return }
	var moreInfo = values ? (' : ' + JSON.stringify(values)) : ''
	throw new Error(msg + moreInfo)
}
<commit_msg>Throw an Error instead of a string, for a proper stack trace<commit_after>
exports.bind = function(context, method/*, args... */) {
	if (!context || !method || (typeof method == 'string' && !context[method])) { throw "bad bind arguments" }
	var args = Array.prototype.slice.call(arguments, 2)
	return function() {
		fn = (typeof method == 'string' ? context[method] : method)
		return fn.apply(context, args.concat(Array.prototype.slice.call(arguments, 0)))
	}
}

exports.forEach = function(items, ctx, fn) {
	if (!items) { return }
	if (!fn) { fn = ctx, ctx = this }
	if (exports.isArray(items)) {
		for (var i=0, item; item = items[i]; i++) { fn.call(ctx, item, i) }
	} else {
		for (var key in items) { fn.call(ctx, key, items[key]) }
	}
}

exports.map = function(items, fn) {
	var results = []
	exports.forEach(items, function(item) { results.push(fn(item)) })
	return results
}

exports.Class = function(parent, proto) {
	if(!proto) { proto = parent }
	proto.prototype = parent.prototype

	var cls = function() { if(this.init) { this.init.apply(this, arguments) }}
	cls.prototype = new proto(function(context, method, args) {
		var target = parent
		while(target = target.prototype) {
			if(target[method]) {
				return target[method].apply(context, args || [])
			}
		}
		throw new Error('supr: parent method ' + method + ' does not exist')
	})

	// Sometimes you want a method that renders UI to only execute once if it's called
	// multiple times within a short time period. Delayed methods do just that
	cls.prototype.createDelayedMethod = function(methodName, fn) {
		// "this" is the class
		this[methodName] = function() {
			// now "this" is the instance. Each instance gets its own function
			var executionTimeout
			this[methodName] = bind(this, function() {
				clearTimeout(executionTimeout)
				executionTimeout = setTimeout(bind(fn, 'apply', this, arguments), 10)
			})
			this[methodName].apply(this, arguments)
		}
	}

	cls.prototype.constructor = cls
	return cls
}

exports.Singleton = function(parent, proto) {
	return new (exports.Class(parent, proto))()
}

var stripRegexp = /^\s*(.*?)\s*$/
exports.strip = function(str) {
	return str.match(stripRegexp)[1]
}

exports.capitalize = function(str) {
	if (!str) { return '' }
	return str[0].toUpperCase() + str.substring(1)
}

exports.isArray = function(obj) {
	return Object.prototype.toString.call(obj) === '[object Array]'
}

exports.blockCallback = function(callback, opts) {
	opts = opts || {}
	opts.fireOnce = (typeof opts.fireOnce != 'undefined' ? opts.fireOnce : true)
	var blocks = 0,
		fired = false,
		result = {
		addBlock: function() {
			blocks++
			var blockReleased = false
			return function(err) {
				if (err && opts.throwErr) {
					throw new Error(err)
				}
				if (blockReleased) {
					result.tryNow()
					return
				}
				blockReleased = true
				blocks--
				setTimeout(result.tryNow)
			}
		},
		tryNow: function() {
			if (fired && opts.fireOnce) { return }
			if (blocks == 0) {
				fired = true
				callback()
			}
		}
	}
	return result
}

exports.bytesToString = function(byteArray, offset) {
	return byteArray.toString();
	return String.fromCharCode.apply(String, Array.prototype.slice.call(byteArray, offset || 0))
}

exports.recall = function(self, args, timeout) {
	var fn = args.callee
	return function(){ return fn.apply(self, args) }
}

exports.assert = function(shouldBeTrue, msg, values) {
	if (shouldBeTrue) { return }
	var moreInfo = values ? (' : ' + JSON.stringify(values)) : ''
	throw new Error(msg + moreInfo)
}
<|endoftext|>


<commit_before>Apps = new Mongo.Collection('apps', {transform: function(app) {

  app.latestVersion = function() {
    return _.sortBy(this.versions, function(entry) {
      return -entry.createdAt;
    })[0];
  };

  app.onGithub = function() {
    return this.codeLink && this.codeLink.indexOf('github.com') > -1;
  };

  app.spk = function() {
    var latest = this.latestVersion();
    return Spks.findOne({'meta.packageId': latest && latest.packageId});
  };

  if (Meteor.isClient)
    app.install = function() {
      var _this = this;
      Meteor.call('user/installApp', _this._id, function(err) {
        if (err) console.log(err);
        else {
          var installedLocally = amplify.store('sandstormInstalledApps');
          if (!installedLocally) amplify.store('sandstormInstalledApps', [_this._id]);
          if (installedLocally.indexOf(_this._id) === -1) {
            installedLocally.push(_this._id);
            amplify.store('sandstormInstalledApps', installedLocally);
          }
        }
      });
    };

  app.installed = function() {

    if (typeof window !== 'undefined') {
      var appIds = window.amplify.store('sandstormInstalledApps') || [];
      if (appIds.indexOf(this._id) > -1) return true;
    }
    var userId = this.userId || Meteor.userId(),
        user = Meteor.users.findOne(userId);
    if (user && this._id in user.installedApps) return true;

    return false;

  };

  // it's actually slightly difficult to know when an app's ultimate install
  // link will be available due to the unknown length of time it will take the
  // .spk to get to S3.  So, we construct the install link on demand, and then
  // cache it if it turns out to be available.
  app.makeInstallLink = function() {
    if (this.installLink) return this.installLink;
    else if (Meteor.isClient) return Meteor.call('apps/updateInstallLink', this._id);
    var latest = this.latestVersion(),
        spk = Spks.findOne({'meta.packageId': latest && latest.packageId}),
        installLink;
    if (spk && spk.copies.spkS3) {
      installLink = 'install/' + spk.meta.packageId + '?url=https://s3-' +
         Meteor.settings.public.AWSRegion + '.amazonaws.com/' +
         Meteor.settings.public.spkBucket + '/' + spk.copies.spkS3.key;
      Apps.update(this._id, {$set: {installLink: installLink}});
      return installLink;
    }
  };

  return app;
}});

Apps.approval = {
  approved: 0,
  pending: 1,
  revisionRequested: 2,
  rejected: 3
};

var converter = new Showdown.converter();

// appsBaseSchema contains the keys that are required for a valid app object,
// but NOT anything which will be autoValued or receive a default value only
// when the app is added to the DB.
var VersionSchema = new SimpleSchema({
  number: {
    type: String,
    max: 20,
  },
  version: {
    type: Number,
    optional: true
  },
  packageId: {
    type: String,
  },
  spkId: {
    type: String,
    regEx: SimpleSchema.RegEx.Id
  },
  createdAt: {
    type: Date,
    autoValue: function(doc) {
      if (!this.isSet || this.operator !== '$push') return new Date();
      // if (this.isInsert) {
      //   return new Date();
      // } else if (this.isUpsert) {
      //   return {$setOnInsert: new Date()};
      // } else {
      //   this.unset();
      // }
    },
    // optional: true
  },
  // dateTime: {
  //   type: Date
  // },
  changes: {
    type: String,
    max: 200,
    optional: true
  }
});

var appsBaseSchema = {
  name: {
    type: String,
    max: 200,
    index: true
  },
  categories: {
    type: [String],
    index: true,
    defaultValue: [],
    minCount: 1
  },
  description: {
    type: String,
    max: 5000,
    defaultValue: ''
  },
  image: {
    type: String,
    // regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  screenshots: {
    type: [Object],
    blackbox: true,
    defaultValue: []
  },
  'screenshots.$.url': {
    type: String,
    regEx: SimpleSchema.RegEx.Url
  },
  'screenshots.$.comment': {
    type: String,
    optional: true
  },
  author: {
    type: String,
    regEx: SimpleSchema.RegEx.Id,
    index: true
  },
  webLink: {
    type: String,
    regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  codeLink: {
    type: String,
    regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  spkLink: {
    type: String,
    regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  price: {
    type: Number,
    min: 0,
    // Autovalue only in place whilst payment methods are suppressed
    // defaultValue: 0,
    autoValue: function() {
      return 0;
    },
    decimal: true,
    index: true
  },
  public: {
    type: Boolean,
    defaultValue: true,
    index: true
  },
  license: {
    type: String,
    optional: true
  },
  versions: {
    type: [VersionSchema],
    defaultValue: [],
    minCount: 1
  },
  replacesApp: {
    type: String,
    regEx: SimpleSchema.RegEx.Id,
    optional: true
  },
  appId: {
    type: String,
    optional: true
  }

};

Schemas.AppsBase = new SimpleSchema(appsBaseSchema);

// appsFullSchema adds the autoValue and defaultValue keys,
// plus any keys that the user shouldn't be allowed to set themselves
var appsFullSchema = _.extend({}, appsBaseSchema, {
  createdAt: {
    type: Date,
    autoValue: function() {
      if (this.isInsert) {
        return new Date();
      } else if (this.isUpsert) {
        return {$setOnInsert: new Date()};
      } else {
        this.unset();
      }
    },
    optional: true
  },
  authorName: {
    type: String,
    autoValue: function() {
      if (this.isInsert) {
        var userId = this.field('author').value,
            user = Meteor.users.findOne(userId);
        return user && user.username;
      }
    }
  },
  ratingsCount: {
    type: Number,
    min: 0,
    defaultValue: 0
  },
  ratings: {
    type: Object,
    defaultValue: {
      broken: 0,
      didntLike: 0,
      jobDone: 0,
      amazing: 0
    },
    blackbox: true
  },
  installLink: {
    type: String,
    optional: true,
    // autoValue: function(doc) {
    //   var versions = this.field('versions');
    //   if (versions) {
    //     var latest = _.last(versions.value),
    //         spk = Spks.findOne({'meta.packageId': latest.packageId});
    //     if (spk && spk.copies.spkS3) return '/install/' + spk.meta.packageId + '?url=https://s3-' +
    //          Meteor.settings.AWSRegion + '.amazonaws.com/' +
    //          Meteor.settings.spkBucket + '/' + spk.copies.spkS3.key;
    //     // need to wait for spkS3 to populate - TODO: is there a better way of doing this?
    //   }
    // }
  },
  // Approval state
  // 0 - Approved
  // 1 - Pending
  // 2 - Revision Requested
  // 3 - Rejected
  approved: {
    type: Number,
    defaultValue: 1,
    index: true
  },
  reviews: {
    type: Object,
    blackbox: true,
    defaultValue: {}
  },
  note: {
    type: String,
    optional: true
  },
  notes: {
    type: [Object],
    blackbox: true,
    optional: true,
    autoValue: function(doc) {
      var note = this.field('note');
      if (note.isSet) {
        var authorObj = Meteor.users.findOne(this.userId),
            app = Apps.findOne(this.docId),
            noteObj =  {
              text: note.value,
              author: this.userId,
              authorName: authorObj ? authorObj.username : 'system',
              admin: Roles.userIsInRole(this.userId, 'admin'),
              byAuthor: this.userId === app.author,
              dateTime: new Date()
            };
        if (this.isInsert) {
          return [noteObj];
        } else {
          return {$push: noteObj};
        }
      } else {
        this.unset();
      }
    }
  },
  lastUpdated: {
    type: Date,
    autoValue: function(doc) {
      if (this.isUpdate && this.userId && !Roles.userIsInRole(this.userId, 'admin')) {
        return new Date();
      } else if (this.isFromTrustedCode) {
        return this.value;
      } else {
        this.unset();
      }
    },
    // denyInsert: true,
    optional: true
  },
  lastUpdatedAdmin: {
    type: Date,
    autoValue: function(doc) {
      if (this.isUpdate && this.userId && Roles.userIsInRole(this.userId, 'admin')) {
        return new Date();
      } else if (this.isFromTrustedCode) {
        return this.value;
      } else {
        this.unset();
      }
    },
    // denyInsert: true,
    optional: true
  },
  installCount: {
    type: Number,
    min: 0,
    defaultValue: 0
  },
  installCountThisWeek: {
    type: Number,
    min: 0,
    defaultValue: 0
  },
  htmlDescription: {
    type: String,
    optional: true,
    autoValue: function(doc) {
      var markdownContent = this.field("description");
      if (Meteor.isServer && markdownContent.isSet) {
        return converter.makeHtml(markdownContent.value);
      }
    }
  },
  adminRequests: {
    // due to a Simple-Schema problem, optionality doesn't seem to extend to
    // subschemas, so if we use `type: Schemas.AppsBase` and `optional: true`
    // it complains that the object has no name, etc., even thought the object
    // itself is supposed to be optional.  This way, we can declare an array
    // of max size 1, and either populate it or not.
    // see: https://github.com/aldeed/meteor-simple-schema/issues/133
    type: [Schemas.AppsBase],
    maxCount: 1,
    defaultValue: []
  }
},
{
  // For some reason, SimpleSchema does not like arrays of prototypes being extended,
  // so we need to add these keys again.
  categories: {
    type: [String],
    index: true,
    defaultValue: [],
    minCount: 1
  },
  screenshots: {
    type: [Object],
    blackbox: true,
    defaultValue: []
  },
  'screenshots.$.url': {
    type: String,
    regEx: SimpleSchema.RegEx.Url
  },
  'screenshots.$.comment': {
    type: String,
    optional: true
  },
  versions: {
    type: [Object],
    defaultValue: [],
    blackbox: true,
    minCount: 1
  },
  flags: {
    type: Object,
    blackbox: true,
    optional: true
  }
});

Schemas.AppsFull = new SimpleSchema(appsFullSchema);

Apps.attachSchema(Schemas.AppsFull);

if (Meteor.isServer) {
  Apps.allow({
    insert: function (userId, doc) {
      return false;
    },

    update: function (userId, doc, fieldNames, modifier) {
      return false;
    },

    remove: function (userId, doc) {
      return false;
    }
  });

  Apps.deny({
    insert: function (userId, doc) {
      return true;
    },

    update: function (userId, doc, fieldNames, modifier) {
      return true;
    },

    remove: function (userId, doc) {
      return true;
    }
  });
}

// Update installed counts
function updateInstallCount() {

  Apps.find().forEach(function(app) {

    var query = {};
    query['installedApps.' + app._id] = {$exists: true};

    Apps.update(app._id, {$set: {
      installCount: Meteor.users.find(query).count()
    }});

  });

}

function updateInstallCountThisWeek() {

  Apps.find().forEach(function(app) {

    var query = {},
        lastWeek = new moment().subtract(7, 'days').toDate();
    query['installedApps.' + app._id + '.dateTime'] = {$gte: lastWeek};

    Apps.update(app._id, {$set: {
      installCountThisWeek: Meteor.users.find(query).count()
    }});

  });

}

// Apps.before.update(function(userId, doc, fieldNames, modifier) {
//
//   modifier = modifier || {};
//   modifier.$set = modifier.$set || {};
//   if (fieldNames.indexOf('reviews') > -1) {
//     _.extend(modifier.$set, _.reduce(modifier.$set, function(counts, val, key) {
//       if (key.slice(0, 7) === 'reviews') {
//         counts.ratingsCount += 1;
//         if (val.rating > 1) counts.ratingsPos += 1;
//       }
//       return counts;
//     }, {ratingsCount: 0, ratingsPos: 0}));
//   }
//
// }, {fetchPrevious: false});

Apps.after.update(function(userId, doc, fieldNames) {

  if (fieldNames.indexOf('reviews') > -1) {
    Apps.update(doc._id, {
      $set: _.reduce(doc.reviews, function(counts, review) {
          if ('rating' in review) {
            counts.ratingsCount += 1;
            counts.ratings[Reviews.invertedRating[review.rating]] += 1;
          }
          return counts;
        }, {
          ratingsCount: 0,
          ratings: {
            broken: 0,
            didntLike: 0,
            jobDone: 0,
            amazing: 0
          }
        })
    });
  }

}, {fetchPrevious: false});
<commit_msg>minor installed callback helper fix<commit_after>Apps = new Mongo.Collection('apps', {transform: function(app) {

  app.latestVersion = function() {
    return _.sortBy(this.versions, function(entry) {
      return -entry.createdAt;
    })[0];
  };

  app.onGithub = function() {
    return this.codeLink && this.codeLink.indexOf('github.com') > -1;
  };

  app.spk = function() {
    var latest = this.latestVersion();
    return Spks.findOne({'meta.packageId': latest && latest.packageId});
  };

  if (Meteor.isClient)
    app.install = function() {
      var _this = this;
      Meteor.call('user/installApp', _this._id, function(err) {
        if (err) console.log(err);
        else {
          var installedLocally = amplify.store('sandstormInstalledApps');
          if (!installedLocally) amplify.store('sandstormInstalledApps', [_this._id]);
          else if (installedLocally.indexOf(_this._id) === -1) {
            installedLocally.push(_this._id);
            amplify.store('sandstormInstalledApps', installedLocally);
          }
        }
      });
    };

  app.installed = function() {

    if (typeof window !== 'undefined') {
      var appIds = window.amplify.store('sandstormInstalledApps') || [];
      if (appIds.indexOf(this._id) > -1) return true;
    }
    var userId = this.userId || Meteor.userId(),
        user = Meteor.users.findOne(userId);
    if (user && this._id in user.installedApps) return true;

    return false;

  };

  // it's actually slightly difficult to know when an app's ultimate install
  // link will be available due to the unknown length of time it will take the
  // .spk to get to S3.  So, we construct the install link on demand, and then
  // cache it if it turns out to be available.
  app.makeInstallLink = function() {
    if (this.installLink) return this.installLink;
    else if (Meteor.isClient) return Meteor.call('apps/updateInstallLink', this._id);
    var latest = this.latestVersion(),
        spk = Spks.findOne({'meta.packageId': latest && latest.packageId}),
        installLink;
    if (spk && spk.copies.spkS3) {
      installLink = 'install/' + spk.meta.packageId + '?url=https://s3-' +
         Meteor.settings.public.AWSRegion + '.amazonaws.com/' +
         Meteor.settings.public.spkBucket + '/' + spk.copies.spkS3.key;
      Apps.update(this._id, {$set: {installLink: installLink}});
      return installLink;
    }
  };

  return app;
}});

Apps.approval = {
  approved: 0,
  pending: 1,
  revisionRequested: 2,
  rejected: 3
};

var converter = new Showdown.converter();

// appsBaseSchema contains the keys that are required for a valid app object,
// but NOT anything which will be autoValued or receive a default value only
// when the app is added to the DB.
var VersionSchema = new SimpleSchema({
  number: {
    type: String,
    max: 20,
  },
  version: {
    type: Number,
    optional: true
  },
  packageId: {
    type: String,
  },
  spkId: {
    type: String,
    regEx: SimpleSchema.RegEx.Id
  },
  createdAt: {
    type: Date,
    autoValue: function(doc) {
      if (!this.isSet || this.operator !== '$push') return new Date();
      // if (this.isInsert) {
      //   return new Date();
      // } else if (this.isUpsert) {
      //   return {$setOnInsert: new Date()};
      // } else {
      //   this.unset();
      // }
    },
    // optional: true
  },
  // dateTime: {
  //   type: Date
  // },
  changes: {
    type: String,
    max: 200,
    optional: true
  }
});

var appsBaseSchema = {
  name: {
    type: String,
    max: 200,
    index: true
  },
  categories: {
    type: [String],
    index: true,
    defaultValue: [],
    minCount: 1
  },
  description: {
    type: String,
    max: 5000,
    defaultValue: ''
  },
  image: {
    type: String,
    // regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  screenshots: {
    type: [Object],
    blackbox: true,
    defaultValue: []
  },
  'screenshots.$.url': {
    type: String,
    regEx: SimpleSchema.RegEx.Url
  },
  'screenshots.$.comment': {
    type: String,
    optional: true
  },
  author: {
    type: String,
    regEx: SimpleSchema.RegEx.Id,
    index: true
  },
  webLink: {
    type: String,
    regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  codeLink: {
    type: String,
    regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  spkLink: {
    type: String,
    regEx: SimpleSchema.RegEx.Url,
    optional: true
  },
  price: {
    type: Number,
    min: 0,
    // Autovalue only in place whilst payment methods are suppressed
    // defaultValue: 0,
    autoValue: function() {
      return 0;
    },
    decimal: true,
    index: true
  },
  public: {
    type: Boolean,
    defaultValue: true,
    index: true
  },
  license: {
    type: String,
    optional: true
  },
  versions: {
    type: [VersionSchema],
    defaultValue: [],
    minCount: 1
  },
  replacesApp: {
    type: String,
    regEx: SimpleSchema.RegEx.Id,
    optional: true
  },
  appId: {
    type: String,
    optional: true
  }

};

Schemas.AppsBase = new SimpleSchema(appsBaseSchema);

// appsFullSchema adds the autoValue and defaultValue keys,
// plus any keys that the user shouldn't be allowed to set themselves
var appsFullSchema = _.extend({}, appsBaseSchema, {
  createdAt: {
    type: Date,
    autoValue: function() {
      if (this.isInsert) {
        return new Date();
      } else if (this.isUpsert) {
        return {$setOnInsert: new Date()};
      } else {
        this.unset();
      }
    },
    optional: true
  },
  authorName: {
    type: String,
    autoValue: function() {
      if (this.isInsert) {
        var userId = this.field('author').value,
            user = Meteor.users.findOne(userId);
        return user && user.username;
      }
    }
  },
  ratingsCount: {
    type: Number,
    min: 0,
    defaultValue: 0
  },
  ratings: {
    type: Object,
    defaultValue: {
      broken: 0,
      didntLike: 0,
      jobDone: 0,
      amazing: 0
    },
    blackbox: true
  },
  installLink: {
    type: String,
    optional: true,
    // autoValue: function(doc) {
    //   var versions = this.field('versions');
    //   if (versions) {
    //     var latest = _.last(versions.value),
    //         spk = Spks.findOne({'meta.packageId': latest.packageId});
    //     if (spk && spk.copies.spkS3) return '/install/' + spk.meta.packageId + '?url=https://s3-' +
    //          Meteor.settings.AWSRegion + '.amazonaws.com/' +
    //          Meteor.settings.spkBucket + '/' + spk.copies.spkS3.key;
    //     // need to wait for spkS3 to populate - TODO: is there a better way of doing this?
    //   }
    // }
  },
  // Approval state
  // 0 - Approved
  // 1 - Pending
  // 2 - Revision Requested
  // 3 - Rejected
  approved: {
    type: Number,
    defaultValue: 1,
    index: true
  },
  reviews: {
    type: Object,
    blackbox: true,
    defaultValue: {}
  },
  note: {
    type: String,
    optional: true
  },
  notes: {
    type: [Object],
    blackbox: true,
    optional: true,
    autoValue: function(doc) {
      var note = this.field('note');
      if (note.isSet) {
        var authorObj = Meteor.users.findOne(this.userId),
            app = Apps.findOne(this.docId),
            noteObj =  {
              text: note.value,
              author: this.userId,
              authorName: authorObj ? authorObj.username : 'system',
              admin: Roles.userIsInRole(this.userId, 'admin'),
              byAuthor: this.userId === app.author,
              dateTime: new Date()
            };
        if (this.isInsert) {
          return [noteObj];
        } else {
          return {$push: noteObj};
        }
      } else {
        this.unset();
      }
    }
  },
  lastUpdated: {
    type: Date,
    autoValue: function(doc) {
      if (this.isUpdate && this.userId && !Roles.userIsInRole(this.userId, 'admin')) {
        return new Date();
      } else if (this.isFromTrustedCode) {
        return this.value;
      } else {
        this.unset();
      }
    },
    // denyInsert: true,
    optional: true
  },
  lastUpdatedAdmin: {
    type: Date,
    autoValue: function(doc) {
      if (this.isUpdate && this.userId && Roles.userIsInRole(this.userId, 'admin')) {
        return new Date();
      } else if (this.isFromTrustedCode) {
        return this.value;
      } else {
        this.unset();
      }
    },
    // denyInsert: true,
    optional: true
  },
  installCount: {
    type: Number,
    min: 0,
    defaultValue: 0
  },
  installCountThisWeek: {
    type: Number,
    min: 0,
    defaultValue: 0
  },
  htmlDescription: {
    type: String,
    optional: true,
    autoValue: function(doc) {
      var markdownContent = this.field("description");
      if (Meteor.isServer && markdownContent.isSet) {
        return converter.makeHtml(markdownContent.value);
      }
    }
  },
  adminRequests: {
    // due to a Simple-Schema problem, optionality doesn't seem to extend to
    // subschemas, so if we use `type: Schemas.AppsBase` and `optional: true`
    // it complains that the object has no name, etc., even thought the object
    // itself is supposed to be optional.  This way, we can declare an array
    // of max size 1, and either populate it or not.
    // see: https://github.com/aldeed/meteor-simple-schema/issues/133
    type: [Schemas.AppsBase],
    maxCount: 1,
    defaultValue: []
  }
},
{
  // For some reason, SimpleSchema does not like arrays of prototypes being extended,
  // so we need to add these keys again.
  categories: {
    type: [String],
    index: true,
    defaultValue: [],
    minCount: 1
  },
  screenshots: {
    type: [Object],
    blackbox: true,
    defaultValue: []
  },
  'screenshots.$.url': {
    type: String,
    regEx: SimpleSchema.RegEx.Url
  },
  'screenshots.$.comment': {
    type: String,
    optional: true
  },
  versions: {
    type: [Object],
    defaultValue: [],
    blackbox: true,
    minCount: 1
  },
  flags: {
    type: Object,
    blackbox: true,
    optional: true
  }
});

Schemas.AppsFull = new SimpleSchema(appsFullSchema);

Apps.attachSchema(Schemas.AppsFull);

if (Meteor.isServer) {
  Apps.allow({
    insert: function (userId, doc) {
      return false;
    },

    update: function (userId, doc, fieldNames, modifier) {
      return false;
    },

    remove: function (userId, doc) {
      return false;
    }
  });

  Apps.deny({
    insert: function (userId, doc) {
      return true;
    },

    update: function (userId, doc, fieldNames, modifier) {
      return true;
    },

    remove: function (userId, doc) {
      return true;
    }
  });
}

// Update installed counts
function updateInstallCount() {

  Apps.find().forEach(function(app) {

    var query = {};
    query['installedApps.' + app._id] = {$exists: true};

    Apps.update(app._id, {$set: {
      installCount: Meteor.users.find(query).count()
    }});

  });

}

function updateInstallCountThisWeek() {

  Apps.find().forEach(function(app) {

    var query = {},
        lastWeek = new moment().subtract(7, 'days').toDate();
    query['installedApps.' + app._id + '.dateTime'] = {$gte: lastWeek};

    Apps.update(app._id, {$set: {
      installCountThisWeek: Meteor.users.find(query).count()
    }});

  });

}

// Apps.before.update(function(userId, doc, fieldNames, modifier) {
//
//   modifier = modifier || {};
//   modifier.$set = modifier.$set || {};
//   if (fieldNames.indexOf('reviews') > -1) {
//     _.extend(modifier.$set, _.reduce(modifier.$set, function(counts, val, key) {
//       if (key.slice(0, 7) === 'reviews') {
//         counts.ratingsCount += 1;
//         if (val.rating > 1) counts.ratingsPos += 1;
//       }
//       return counts;
//     }, {ratingsCount: 0, ratingsPos: 0}));
//   }
//
// }, {fetchPrevious: false});

Apps.after.update(function(userId, doc, fieldNames) {

  if (fieldNames.indexOf('reviews') > -1) {
    Apps.update(doc._id, {
      $set: _.reduce(doc.reviews, function(counts, review) {
          if ('rating' in review) {
            counts.ratingsCount += 1;
            counts.ratings[Reviews.invertedRating[review.rating]] += 1;
          }
          return counts;
        }, {
          ratingsCount: 0,
          ratings: {
            broken: 0,
            didntLike: 0,
            jobDone: 0,
            amazing: 0
          }
        })
    });
  }

}, {fetchPrevious: false});
<|endoftext|>


<commit_before>import Component from '@glimmer/component';
import { later } from '@ember/runloop';
import { tracked } from '@glimmer/tracking';
import moment from 'moment';
import config from '../../config/environment';
import { action } from '@ember/object';

export default class DateHeadlineComponent extends Component {

  @tracked headline = null;

  updateInterval = 120000; // 2 minutes

  @action
  scheduleUpdate () {
    const date = moment(this.args.message.date);

    if (date.isSame(moment(), 'day')) {
      this.headline = 'Today';
    } else if (date.isSame(moment().subtract(1, 'day'), 'day')) {
      this.headline = 'Yesterday';
    } else {
      this.headline = this.args.message.date.toLocaleDateString();
    }

    // don't schedule updates during testing, because it makes the tests time out
    if (config.environment === 'test') return;

    if (this.headline === 'Today' || this.headline === 'Yesterday') {
      later(this.scheduleUpdate, this.updateInterval);
    }
  }

}
<commit_msg>Small function refactoring<commit_after>import Component from '@glimmer/component';
import { later } from '@ember/runloop';
import { tracked } from '@glimmer/tracking';
import moment from 'moment';
import config from '../../config/environment';
import { action } from '@ember/object';

export default class DateHeadlineComponent extends Component {

  @tracked headline = null;

  updateInterval = 120000; // 2 minutes

  setHeadline (messageDate) {
    const date = moment(messageDate);

    if (date.isSame(moment(), 'day')) {
      this.headline = 'Today';
    } else if (date.isSame(moment().subtract(1, 'day'), 'day')) {
      this.headline = 'Yesterday';
    } else {
      this.headline = messageDate.toLocaleDateString();
    }
  }

  @action
  scheduleUpdate () {
    this.setHeadline(this.args.message.date);

    // don't schedule updates during testing, because it makes the tests time out
    if (config.environment === 'test') return;

    if (this.headline === 'Today' || this.headline === 'Yesterday') {
      later(this.scheduleUpdate, this.updateInterval);
    }
  }

}
<|endoftext|>


<commit_before>/*==============================================================================
 * (C) Copyright 2015,2016,2017,2018 John J Kauflin, All rights reserved.
 *----------------------------------------------------------------------------
 * DESCRIPTION:
 *----------------------------------------------------------------------------
 * Modification History
 * 2016-10-25 JJK   Added Communications table
 * 2016-11-04 JJK   (Jackson's 14th birthday)
 * 2016-11-05 JJK   Added Admin option to send dues emails
 * 2016-11-12 JJK	Added Dues Notice email function and inserts of
 * 					Dues Notice functions into Communications table
 * 2018-11-07 JJK   Re-factor for JSON based POST for updates
 *============================================================================*/
var communications = (function () {
    'use strict';

    //=================================================================================================================
    // Private variables for the Module
    var hoaCommRecList;
    var parcelId = '';
    var ownerId = '';
    var commId = '';

    //=================================================================================================================
    // Variables cached from the DOM
    var $document = $(document);
    var $moduleDiv = $('#CommPage');
    var $displayPage = $document.find('#navbar a[href="#CommPage"]');
    var $CommListDisplay = $moduleDiv.find("tbody");
    var $EditPage = $("#EditPage");
    var $EditTable = $("#EditTable");
    var $EditTableBody = $("#EditTable").find("tbody");
    var $editValidationError = $(".editValidationError");
    var $EditPageHeader = $("#EditPageHeader");
    var $EditPageButton = $("#EditPageButton");
    var $CommunicationsNew = $("#CommunicationsNew");

    //=================================================================================================================
    // Bind events
    $document.on("click", "#CommunicationsButton", _getHoaCommList);
    $moduleDiv.on("click", ".NewComm", _newComm);
    $EditPage.on("click", ".SaveCommEdit", _saveCommEdit);

    //=================================================================================================================
    // Module methods
    function _getHoaCommList(event) {
        parcelId = event.target.getAttribute("data-parcelId");
        ownerId = event.target.getAttribute("data-ownerId");
        util.waitCursor();
        $.getJSON("getHoaCommList.php", "parcelId=" + parcelId + "&ownerId=" + ownerId, function (outHoaCommRecList) {
            hoaCommRecList = outHoaCommRecList;
            _render();
            util.defaultCursor();
            $displayPage.tab('show');
        });
    }

    //function displayCommList(hoaCommRecList, parcelId, ownerId) {
    function _render() {
        var tr = '';

        $CommunicationsNew.html('New Communication');
        //$("#CommunicationsParcel").html("Parcel Id: " + parcelId + " Owner Id: " + ownerId);

        $.each(hoaCommRecList, function (index, hoaCommRec) {
            if (index == 0) {
                tr = '';
                tr += '
';
                tr += 'CommID	';
                tr += 'Datetime	';
                tr += 'Type	';
                tr += 'Description	';
                tr += '
';
            }
            tr += '
';
            tr += '' + hoaCommRec.CommID + '	';
            tr += '' + hoaCommRec.CreateTs + '	';
            tr += '' + hoaCommRec.CommType + '	';
            tr += '' + hoaCommRec.CommDesc + '	';
            tr += '
';
        });

        $CommListDisplay.html(tr);
    }

    function _newComm(event) {
        util.waitCursor();
        parcelId = event.target.getAttribute("data-parcelId");
        ownerId = event.target.getAttribute("data-ownerId");
        commId = event.target.getAttribute("data-commId");
        $.getJSON("getHoaCommList.php", "parcelId=" + parcelId + "&ownerId=" + ownerId + "&commId=" + commId, function (outHoaCommRecList) {
            _formatCommEdit(outHoaCommRecList[0],parcelId,ownerId,commId);
            util.defaultCursor();
            $EditPage.modal();
        });
    }

    function _formatCommEdit(hoaCommRec, parcelId, ownerId, commId) {
        // Clear the field where we report validation errors
        $editValidationError.empty();
        $EditPageHeader.text("Create Communication");

        var tr = '';
        tr += '
';
        tr += '
Parcel:	' + parcelId + '
';
        tr += '
OwnerID:	' + ownerId + '
';
        //tr += '
CommID:	'+ commId +'
';
        if (hoaCommRec === undefined) {
            tr += '
Datetime:
';
            var selectOption = '<select class="form-control" id="commType">'
                + util.setSelectOption("Issue", "Issue", 1, "bg-success")
                + util.setSelectOption("Dues Question", "Dues Question", 0, "bg-danger")
                + util.setSelectOption("Dues Notice", "Dues Notice", 0, "bg-info")
                + '</select>';
            tr += '
Type: 	' + selectOption + '
';
            tr += '
Description:	' + util.setInputText("commDesc", "", "80") + '
';
        } else {
            tr += '
Datetime:	' + hoaCommRec.CreateTs + '
';
            var selectOption = '<select class="form-control" id="commType">'
                + util.setSelectOption("Issue", "Issue", ("Issue" == hoaCommRec.CommType), "bg-success")
                + util.setSelectOption("Dues Question", "Dues Question", ("Dues Question" == hoaCommRec.CommType), "bg-danger")
                + util.setSelectOption("Dues Notice", "Dues Notice", ("Dues Notice" == hoaCommRec.CommType), "bg-info")
                + '</select>';
            tr += '
Type: 	' + selectOption + '
';
            tr += '
Description:	' + util.setInputText("commDesc", hoaCommRec.CommDesc, "80") + '
';
        }

        tr += '
';
        $EditTableBody.html(tr);

        tr = '<form class="form-inline" role="form">';
        tr += 'Save';
        //tr += 'Delete';
        tr += '<button type="button" class="btn btn-default" data-dismiss="modal">Close</button></form>';
        $EditPageButton.html(tr);

    } // End of function formatCommEdit(hoaCommRec){

    function _saveCommEdit(event) {
        util.waitCursor();

        var paramMap = new Map();
        paramMap.set('parcelId', event.target.getAttribute("data-parcelId"));
        paramMap.set('ownerId', event.target.getAttribute("data-ownerId"));
        paramMap.set('commId', event.target.getAttribute("data-commId"));
        //console.log("util.getJSONfromInputs($EditTable,paramMap) = " + util.getJSONfromInputs($EditTable, paramMap));

        $.ajax("updHoaComm.php", {
            type: "POST",
            contentType: "application/json",
            data: util.getJSONfromInputs($EditTable, paramMap),
            dataType: "json",
            success: function (list) {
                util.defaultCursor();
                // Set the newest list from the update into the module variable (for render)
                hoaCommRecList = list;
                _render();
                $EditPage.modal("hide");
                $displayPage.tab('show');
            },
            error: function () {
                $editValidationError.html("An error occurred in the update - see log");
            }
        });
    }

    //=================================================================================================================
    // This is what is exposed from this Module
    return {
    };

})(); // var communications = (function(){
<commit_msg>Re-factor<commit_after>/*==============================================================================
 * (C) Copyright 2015,2016,2017,2018 John J Kauflin, All rights reserved.
 *----------------------------------------------------------------------------
 * DESCRIPTION:
 *----------------------------------------------------------------------------
 * Modification History
 * 2016-10-25 JJK   Added Communications table
 * 2016-11-04 JJK   (Jackson's 14th birthday)
 * 2016-11-05 JJK   Added Admin option to send dues emails
 * 2016-11-12 JJK	Added Dues Notice email function and inserts of
 * 					Dues Notice functions into Communications table
 * 2018-11-07 JJK   Re-factor for JSON based POST for updates
 *
 *============================================================================*/
var communications = (function () {
    'use strict';

    //=================================================================================================================
    // Private variables for the Module
    var hoaCommRecList;
    var parcelId = '';
    var ownerId = '';
    var commId = '';

    //=================================================================================================================
    // Variables cached from the DOM
    var $document = $(document);
    var $moduleDiv = $('#CommPage');
    var $displayPage = $document.find('#navbar a[href="#CommPage"]');
    var $CommListDisplay = $moduleDiv.find("tbody");
    var $EditPage = $("#EditPage");
    var $EditTable = $("#EditTable");
    var $EditTableBody = $("#EditTable").find("tbody");
    var $editValidationError = $(".editValidationError");
    var $EditPageHeader = $("#EditPageHeader");
    var $EditPageButton = $("#EditPageButton");
    var $CommunicationsNew = $("#CommunicationsNew");

    //=================================================================================================================
    // Bind events
    $document.on("click", "#CommunicationsButton", _getHoaCommList);
    $moduleDiv.on("click", ".NewComm", _newComm);
    $EditPage.on("click", ".SaveCommEdit", _saveCommEdit);

    //=================================================================================================================
    // Module methods
    function _getHoaCommList(event) {
        parcelId = event.target.getAttribute("data-parcelId");
        ownerId = event.target.getAttribute("data-ownerId");
        util.waitCursor();
        $.getJSON("getHoaCommList.php", "parcelId=" + parcelId + "&ownerId=" + ownerId, function (outHoaCommRecList) {
            hoaCommRecList = outHoaCommRecList;
            _render();
            util.defaultCursor();
            $displayPage.tab('show');
        });
    }

    //function displayCommList(hoaCommRecList, parcelId, ownerId) {
    function _render() {
        var tr = '';

        $CommunicationsNew.html('New Communication');
        //$("#CommunicationsParcel").html("Parcel Id: " + parcelId + " Owner Id: " + ownerId);

        $.each(hoaCommRecList, function (index, hoaCommRec) {
            if (index == 0) {
                tr = '';
                tr += '
';
                tr += 'CommID	';
                tr += 'Datetime	';
                tr += 'Type	';
                tr += 'Description	';
                tr += '
';
            }
            tr += '
';
            tr += '' + hoaCommRec.CommID + '	';
            tr += '' + hoaCommRec.CreateTs + '	';
            tr += '' + hoaCommRec.CommType + '	';
            tr += '' + hoaCommRec.CommDesc + '	';
            tr += '
';
        });

        $CommListDisplay.html(tr);
    }

    function _newComm(event) {
        util.waitCursor();
        parcelId = event.target.getAttribute("data-parcelId");
        ownerId = event.target.getAttribute("data-ownerId");
        commId = event.target.getAttribute("data-commId");
        $.getJSON("getHoaCommList.php", "parcelId=" + parcelId + "&ownerId=" + ownerId + "&commId=" + commId, function (outHoaCommRecList) {
            _formatCommEdit(outHoaCommRecList[0],parcelId,ownerId,commId);
            util.defaultCursor();
            $EditPage.modal();
        });
    }

    function _formatCommEdit(hoaCommRec, parcelId, ownerId, commId) {
        // Clear the field where we report validation errors
        $editValidationError.empty();
        $EditPageHeader.text("Create Communication");

        var tr = '';
        tr += '
';
        tr += '
Parcel:	' + parcelId + '
';
        tr += '
OwnerID:	' + ownerId + '
';
        //tr += '
CommID:	'+ commId +'
';
        if (hoaCommRec === undefined) {
            tr += '
Datetime:
';
            var selectOption = '<select class="form-control" id="commType">'
                + util.setSelectOption("Issue", "Issue", 1, "bg-success")
                + util.setSelectOption("Dues Question", "Dues Question", 0, "bg-danger")
                + util.setSelectOption("Dues Notice", "Dues Notice", 0, "bg-info")
                + '</select>';
            tr += '
Type: 	' + selectOption + '
';
            tr += '
Description:	' + util.setInputText("commDesc", "", "80") + '
';
        } else {
            tr += '
Datetime:	' + hoaCommRec.CreateTs + '
';
            var selectOption = '<select class="form-control" id="commType">'
                + util.setSelectOption("Issue", "Issue", ("Issue" == hoaCommRec.CommType), "bg-success")
                + util.setSelectOption("Dues Question", "Dues Question", ("Dues Question" == hoaCommRec.CommType), "bg-danger")
                + util.setSelectOption("Dues Notice", "Dues Notice", ("Dues Notice" == hoaCommRec.CommType), "bg-info")
                + '</select>';
            tr += '
Type: 	' + selectOption + '
';
            tr += '
Description:	' + util.setInputText("commDesc", hoaCommRec.CommDesc, "80") + '
';
        }

        tr += '
';
        $EditTableBody.html(tr);

        tr = '<form class="form-inline" role="form">';
        tr += 'Save';
        //tr += 'Delete';
        tr += '<button type="button" class="btn btn-default" data-dismiss="modal">Close</button></form>';
        $EditPageButton.html(tr);

    } // End of function formatCommEdit(hoaCommRec){

    function _saveCommEdit(event) {
        util.waitCursor();

        var paramMap = new Map();
        paramMap.set('parcelId', event.target.getAttribute("data-parcelId"));
        paramMap.set('ownerId', event.target.getAttribute("data-ownerId"));
        paramMap.set('commId', event.target.getAttribute("data-commId"));
        //console.log("util.getJSONfromInputs($EditTable,paramMap) = " + util.getJSONfromInputs($EditTable, paramMap));

        $.ajax("updHoaComm.php", {
            type: "POST",
            contentType: "application/json",
            data: util.getJSONfromInputs($EditTable, paramMap),
            dataType: "json",
            success: function (list) {
                util.defaultCursor();
                // Set the newest list from the update into the module variable (for render)
                hoaCommRecList = list;
                _render();
                $EditPage.modal("hide");
                $displayPage.tab('show');
            },
            error: function () {
                $editValidationError.html("An error occurred in the update - see log");
            }
        });
    }

    //=================================================================================================================
    // This is what is exposed from this Module
    return {
    };

})(); // var communications = (function(){
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * 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 License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var schema = require('./validator/schematron.js');

module.exports = schema(
  {
    id: schema().isId(),
    timezone: schema().string(),
    type: schema().string().in(['upload'])
  }
);
<commit_msg>Timezone field no longer requires validation for upload type<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * 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 License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var schema = require('./validator/schematron.js');

module.exports = schema(
  {
    id: schema().isId(),
    type: schema().string().in(['upload'])
  }
);
<|endoftext|>