<commit_before>/**
 * @fileoverview
 * Data objects and classes for the http logger
 *
 */

/**
 * @class
 * Data class for http logger
 */
window.HTTPLoggerData = new function()
{
    requestList = [];
    requestMap = {};
    selectedRequestId = null;
    var lastModifiedRequestId = null;

    _views = [ "request_info_raw",
               "response_info_raw",
               "request_info_headers",
               "response_info_headers",
               "request_overview",
               "response_info_body",
               "request_list"
             ];

    /**
     * Get the log as a list
     */
    this.getLog = function()
    {
        return requestList;
    }

    /**
     * Get a specific request
     * @argument {string} id the id of the request
     * @returns {object} data object for the request or null if not found
     */
    this.getRequestById = function(id)
    {
        if (id in requestMap) { return requestMap[id] }
        else { return null }
    }

    /**
     * Clear the log data
     *
     */
    this.clearLog = function()
    {
        requestList = [];
        requestMap = {};
        selectedRequestId = null;
        _updateViews();
    }

    /**
     * Add a request to the log
     */
    this.addRequest = function(request)
    {
        var r = { id:request["request-id"],
                  "request": request,
                  response:null, duration: null
                }

        requestList.push(r);
        requestMap[r.id] = r;
        lastModifiedRequestId = r.id;
        _updateViews();
    }

    /**
     * Add a response object to an existing request
     * fixme: now we silently ignore repsonses to non-exstant requests
     */
    this.addResponse = function(response)
    {
        var r = requestMap[response["request-id"]];
        if (r)
        {
            r.response = response;
            if (r.request.time && r.response.time)
            {
                var s = Math.round(parseFloat(r.request.time))
                var e = Math.round(parseFloat(r.response.time))
                r.duration = e-s;
                opera.postError("duration is " + r.duration)
            }
            else
            {
                opera.postError("times are weird. " + r.request.time + " : " + r.response.time)
            }
            lastModifiedRequestId = r.id;
            _updateViews();
        }
    }

    /**
     * Set the currently selected request, that is, the request that is being
     * inspected
     */
    this.setSelectedRequestId = function(id)
    {
        selectedRequestId = id;
        _updateViews();
    }

    /**
     * Clear the selected request
     */
    this.clearSelectedRequest = function()
    {
        selectedRequestId = null;
        _updateViews();
    }

    /**
     * Get the selected request
     */
    this.getSelectedRequest = function()
    {
        if (selectedRequestId && selectedRequestId in requestMap)
        {
            return requestMap[selectedRequestId];
        }
        else
        {
            return null;
        }
    }

    /**
     * Get the ID of the selected request
     */
    this.getSelectedRequestId = function()
    {
        return selectedRequestId;
    }

    this.getLastModifiedRequestId = function()
    {
        return lastModifiedRequestId;
    }

    /**
     * Update all views that use this as a data source
     */
    var _updateViews = function()
    {
        for (var n=0, e; e=_views[n]; n++)
        {
            if (e in window.views) { window.views[e].update() }
        }
    }

}
<commit_msg>Removed some debug outbut that was left behind<commit_after>/**
 * @fileoverview
 * Data objects and classes for the http logger
 *
 */

/**
 * @class
 * Data class for http logger
 */
window.HTTPLoggerData = new function()
{
    requestList = [];
    requestMap = {};
    selectedRequestId = null;
    var lastModifiedRequestId = null;

    _views = [ "request_info_raw",
               "response_info_raw",
               "request_info_headers",
               "response_info_headers",
               "request_overview",
               "response_info_body",
               "request_list"
             ];

    /**
     * Get the log as a list
     */
    this.getLog = function()
    {
        return requestList;
    }

    /**
     * Get a specific request
     * @argument {string} id the id of the request
     * @returns {object} data object for the request or null if not found
     */
    this.getRequestById = function(id)
    {
        if (id in requestMap) { return requestMap[id] }
        else { return null }
    }

    /**
     * Clear the log data
     *
     */
    this.clearLog = function()
    {
        requestList = [];
        requestMap = {};
        selectedRequestId = null;
        _updateViews();
    }

    /**
     * Add a request to the log
     */
    this.addRequest = function(request)
    {
        var r = { id:request["request-id"],
                  "request": request,
                  response:null, duration: null
                }

        requestList.push(r);
        requestMap[r.id] = r;
        lastModifiedRequestId = r.id;
        _updateViews();
    }

    /**
     * Add a response object to an existing request
     * fixme: now we silently ignore repsonses to non-exstant requests
     */
    this.addResponse = function(response)
    {
        var r = requestMap[response["request-id"]];
        if (r)
        {
            r.response = response;
            if (r.request.time && r.response.time)
            {
                var s = Math.round(parseFloat(r.request.time))
                var e = Math.round(parseFloat(r.response.time))
                r.duration = e-s;
            }
            else
            {
                r.duration = "?";
            }
            lastModifiedRequestId = r.id;
            _updateViews();
        }
    }

    /**
     * Set the currently selected request, that is, the request that is being
     * inspected
     */
    this.setSelectedRequestId = function(id)
    {
        selectedRequestId = id;
        _updateViews();
    }

    /**
     * Clear the selected request
     */
    this.clearSelectedRequest = function()
    {
        selectedRequestId = null;
        _updateViews();
    }

    /**
     * Get the selected request
     */
    this.getSelectedRequest = function()
    {
        if (selectedRequestId && selectedRequestId in requestMap)
        {
            return requestMap[selectedRequestId];
        }
        else
        {
            return null;
        }
    }

    /**
     * Get the ID of the selected request
     */
    this.getSelectedRequestId = function()
    {
        return selectedRequestId;
    }

    this.getLastModifiedRequestId = function()
    {
        return lastModifiedRequestId;
    }

    /**
     * Update all views that use this as a data source
     */
    var _updateViews = function()
    {
        for (var n=0, e; e=_views[n]; n++)
        {
            if (e in window.views) { window.views[e].update() }
        }
    }

}
<|endoftext|>


<commit_before>this.addEventListener("install", function (event) {
    event.waitUntil(
        caches.open('v14').then(function (cache) {
         return cache.addAll(['/html/',
          '/html/index.html',
          '/html/manifest.json',
          '/html/emergency.html',
          '/html/js/911.js',
          '/html/js/all_scr.js',
          '/html/js/app.js',
          '/html/js/bootstrap.min.js',
          '/html/js/emergency.js',
          '/html/js/wow.min.js',
          '/html/rs-plugin/js/jquery.themepunch.tools.min.js',
          '/html/rs-plugin/js/jquery.themepunch.revolution.min.js',
          '/html/rs-plugin/assets/timer.png',
          '/html/js/jquery.min.js',
          '/html/js/jquery.parallax-0.2-min.js',
          '/html/css/adaptive.css',
          '/html/css/animate.css',
          '/html/css/bootstrap.css',
          '/html/css/main.css',
          '/html/css/pushy.css',
          '/html/css/font-awesome.css',
          '/html/rs-plugin/css/settings.css',
          '/html/img/trapped_lady.jpg',
          '/html/img/screaming.jpg',
          '/html/img/location4.jpg',
          '/html/img/embrace3.jpg',
          '/html/img/start/bg.jpg',
          'img/avatar/ava_16.jpg',
          'img/c_logo.jpg',
          'img/avatar/ava_11.jpg',
          'img/avatar/ava_12.jpg',
          'img/avatar/ava_13.jpg',
          'img/avatar/ava_14.jpg',
          'img/avatar/ava_15.jpg',
          '/html/font/fontawesome-webfont.woff?v=4.3.0',
          '/html/font/fontawesome-webfont.woff2?v=4.3.0',
          '/html/rs-plugin/font/revicons.woff?5510888',
          '/html/rs-plugin/font/revicons.ttf?5510888',
          '/html/font/fontawesome-webfont.ttf?v=4.3.0',
          '/html/img/wmb_128x128.png',
          '/html/img/wmb_144x144.png',
          '/html/img/wmb_152x152.png',
          '/html/img/wmb_192x192.png',
          '/html/img/wmb_256x256.png'
          ]);
        })
        );
    });

this.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v14'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});
this.addEventListener('fetch', function (event) {
	event.respondWith(
    caches.match(event.request);

    );
});

<commit_msg>error<commit_after>this.addEventListener("install", function (event) {
    event.waitUntil(
        caches.open('v15').then(function (cache) {
         return cache.addAll(['/html/',
          '/html/index.html',
          '/html/manifest.json',
          '/html/emergency.html',
          '/html/js/911.js',
          '/html/js/all_scr.js',
          '/html/js/app.js',
          '/html/js/bootstrap.min.js',
          '/html/js/emergency.js',
          '/html/js/wow.min.js',
          '/html/rs-plugin/js/jquery.themepunch.tools.min.js',
          '/html/rs-plugin/js/jquery.themepunch.revolution.min.js',
          '/html/rs-plugin/assets/timer.png',
          '/html/js/jquery.min.js',
          '/html/js/jquery.parallax-0.2-min.js',
          '/html/css/adaptive.css',
          '/html/css/animate.css',
          '/html/css/bootstrap.css',
          '/html/css/main.css',
          '/html/css/pushy.css',
          '/html/css/font-awesome.css',
          '/html/rs-plugin/css/settings.css',
          '/html/img/trapped_lady.jpg',
          '/html/img/screaming.jpg',
          '/html/img/location4.jpg',
          '/html/img/embrace3.jpg',
          '/html/img/start/bg.jpg',
          '/html/img/avatar/ava_16.jpg',
          '/html/img/c_logo.jpg',
          '/html/img/avatar/ava_11.jpg',
          '/html/img/avatar/ava_12.jpg',
          '/html/img/avatar/ava_13.jpg',
          '/html/img/avatar/ava_14.jpg',
          '/html/img/avatar/ava_15.jpg',
          '/html/font/fontawesome-webfont.woff?v=4.3.0',
          '/html/font/fontawesome-webfont.woff2?v=4.3.0',
          '/html/rs-plugin/font/revicons.woff?5510888',
          '/html/rs-plugin/font/revicons.ttf?5510888',
          '/html/font/fontawesome-webfont.ttf?v=4.3.0',
          '/html/img/wmb_128x128.png',
          '/html/img/wmb_144x144.png',
          '/html/img/wmb_152x152.png',
          '/html/img/wmb_192x192.png',
          '/html/img/wmb_256x256.png'
          ]);
        })
        );
    });

this.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v15'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});
this.addEventListener('fetch', function (event) {
	event.respondWith(
    caches.match(event.request);

    );
});

<|endoftext|>


<commit_before>import declare from 'dojo/_base/declare';
import array from 'dojo/_base/array';
import lang from 'dojo/_base/lang';
import aspect from 'dojo/aspect';
import GroupUtility from '../GroupUtility';
import _RightDrawerBaseMixin from './_RightDrawerBaseMixin';
import LookupField from 'argos/Fields/LookupField';
import getResource from 'argos/I18n';
import $ from 'jquery';

const resource = getResource('rightDrawerListMixin');

/**
 * @class crm.Views._RightDrawerListMixin
 *
 * List mixin for right drawers.
 *
 * @since 3.0
 * @mixins crm.Views._RightDrawerBaseMixin
 *
 */
const __class = declare('crm.Views._RightDrawerListMixin', [_RightDrawerBaseMixin], {
  // Localization
  hashTagsSectionText: resource.hashTagsSectionText,
  groupsSectionText: resource.groupsSectionText,
  kpiSectionText: resource.kpiSectionText,
  configureGroupsText: resource.configureGroupsText,
  refreshGroupsText: resource.refreshGroupsText,
  layoutsText: resource.layoutsText,

  _hasChangedKPIPrefs: false, // Dirty flag so we know when to reload the widgets
  _hashTagClicked: false,
  groupList: null,
  DRAWER_PAGESIZE: 100,
  groupLookupId: 'groups_configure',
  hasSettings: true,

  setupRightDrawer: function setupRightDrawer() {
    const drawer = App.getView('right_drawer');
    if (drawer) {
      drawer.pageSize = this.DRAWER_PAGESIZE;
      this.groupList = GroupUtility.getGroupPreferences(this.entityName);
      this._finishSetup(drawer);
    }
  },
  refreshRightDrawer: function refreshRightDrawer() {
    const drawer = App.getView('right_drawer');
    if (drawer) {
      drawer.clear();
      drawer.layout = null;
      drawer.setLayout(this.createRightDrawerLayout());
      drawer.refresh();
    }
  },
  _finishSetup: function _finishSetup(drawer) {
    lang.mixin(drawer, this._createActions());
    drawer.setLayout(this.createRightDrawerLayout());
    drawer.getGroupForEntry = function getGroupForEntry(entry) {
      return this.getGroupForRightDrawerEntry(entry);
    }.bind(this);

    if (this.rebuildWidgets) {
      App.viewSettingsModal.element.on('close', () => {
        if (this._hasChangedKPIPrefs) {
          this.destroyWidgets();

          // HACK: Don't rebuild widgets if a hashtag was clicked,
          // because the widget mixin will rebuild on a data refresh anyways.
          // TODO: Fix multiple calls to rebuildWidets() at the same time.
          if (!this._hashTagClicked) {
            this.rebuildWidgets();
          }
          this._hasChangedKPIPrefs = false;
        }

        // Reset state
        this._hashTagClicked = false;
      });
    }
  },
  unloadRightDrawer: function unloadRightDrawer() {
    const drawer = App.getView('right_drawer');
    if (drawer) {
      drawer.setLayout([]);
      drawer.getGroupForEntry = function getGroupForEntry() {};
      App.viewSettingsModal.element.off('close');
    }
  },
  _onSearchExpression: function _onSearchExpression() {
    // TODO: Don't extend this private function - connect to the search widget onSearchExpression instead
    if (this.groupsMode) {
      this._clearGroupMode();
    }

    this.inherited(arguments);
  },
  openSettings: function openSettings() {
    App.viewSettingsModal.open();
  },
  _createActions: function _createActions() {
    // These actions will get mixed into the right drawer view.
    const actions = {
      hashTagClicked: function hashTagClicked(params) {
        const { hashtag } = params;

        if (this.groupsMode) {
          this._clearGroupMode();
        }

        if (hashtag && typeof hashtag === 'string') {
          this._hashTagClicked = true;
          if (hashtag.startsWith('#')) {
            this.setSearchTerm(hashtag);
          } else {
            this.setSearchTerm(`#${hashtag}`);
          }

          this.search();
          App.viewSettingsModal.close();
        }
      }.bind(this),
      kpiClicked: function kpiClicked(params) {
        const metrics = App.getMetricsByResourceKind(this.resourceKind);
        let results;

        if (metrics.length > 0) {
          results = array.filter(metrics, (metric) => {
            return metric.title === unescape(params.title);
          });
        }

        if (results.length > 0) {
          const enabled = !!results[0].enabled;
          results[0].enabled = !enabled;
          App.persistPreferences();
          this._hasChangedKPIPrefs = true;

          $(params.$source).attr('data-enabled', (!enabled).toString());
        }
      }.bind(this),
      groupConfigureClicked: function groupConfigureClicked() {
        this._selectGroups();
        App.viewSettingsModal.close();
      }.bind(this),
      groupClicked: function groupClicked(params) {
        this._startGroupMode();
        const groupId = params.$key;

        const group = array.filter(this.groupList, (item) => {
          return item.$key === groupId;
        })[0];

        if (!group) {
          throw new Error('Expected a group.');
        }
        this.setCurrentGroup(group);
        this.refresh();
        App.viewSettingsModal.close();
      }.bind(this),
      layoutSelectedClicked: function layoutSelectedClicked(params) {
        const name = params.name;
        $(params.$source.parentElement.parentElement).find('a').each((i, a) => {
          $(a).attr('data-enabled', (($(a).attr('data-name') === name)).toString());
        });

        GroupUtility.setSelectedGroupLayoutTemplate(this.entityName, name);
        this._groupInitialized = false;
        this.refresh();
        App.viewSettingsModal.close();
      }.bind(this),

    };

    return actions;
  },
  _selectGroups: function _selectGroups() {
    const view = App.getView(this.groupLookupId);
    view.family = this.entityName;
    view.set('store', null);
    view.clear();
    view.refreshRequired = true;

    const field = new LookupField({
      owner: this,
      view,
      singleSelect: false,
      previousSelections: array.map(this.groupList, (group) => {
        return group.$key;
      }),
    });

    const handle = aspect.after(field, 'complete', function afterComplete() {
      const self = this;
      const list = this.owner;
      const items = [];

      // We will get an object back where the property names are the keys (groupId's)
      // Extract them out, and save the entry, which is the data property on the extracted object
      for (const groupId in self.currentValue) {
        if (self.currentValue.hasOwnProperty(groupId)) {
          const entry = self.currentValue[groupId].data;
          if (entry) {
            items.push(entry);
          }
        }
      }

      const hasDefaultGroup = list.hasDefaultGroup;
      GroupUtility.addToGroupPreferences(items, list.entityName, true);
      const currentGroup = GroupUtility.getDefaultGroup(list.entityName);
      if (currentGroup) {
        list.setCurrentGroup(currentGroup);
      }

      handle.remove();
      self.destroy();

      if (hasDefaultGroup) {
        // We will transition back to the list, pop back open the right drawer so the user is back where they started
        const processDataHandle = aspect.after(list, 'processData', function postProcessData() {
          App.viewSettingsModal.close();
          processDataHandle.remove();
          if (this.transitionHandle) {
            this.transitionHandle.remove();
          }
        }.bind(list));
      } else {
        // Since there was no previous default group, just refresh the list (no need to toggle the right drawer)
        this.transitionHandle = aspect.after(list, 'onTransitionTo', function postOnTransitionTo() {
          this.refreshRequired = true;
          this.clear();
          this.refresh();
          if (this.transitionHandle) {
            this.transitionHandle.remove();
          }
        }.bind(list));
      }
    }.bind(field));

    field.navigateToListView();
  },
  getGroupForRightDrawerEntry: function getGroupForRightDrawerEntry(entry) {
    if (entry.dataProps && entry.dataProps.hashtag && this._hasHashTags() && App.enableHashTags) {
      return {
        tag: 'view',
        title: resource.hashTagsSectionText,
      };
    }

    if ((entry.action === 'groupClicked' || entry.action === 'groupConfigureClicked') && this.groupsEnabled) {
      return {
        tag: 'group',
        title: resource.groupsSectionText,
      };
    }
    if ((entry.action === 'layoutSelectedClicked') && this.groupsEnabled) {
      return {
        tag: 'layoutTemplates',
        title: resource.layoutsText,
      };
    }
    return {
      tag: 'kpi',
      title: resource.kpiSectionText,
    };
  },
  createRightDrawerLayout: function createRightDrawerLayout() {
    const layout = [];

    if (this.groupsEnabled) {
      const groupsSection = {
        id: 'actions',
        children: [],
      };

      groupsSection.children.push({
        name: 'configureGroups',
        action: 'groupConfigureClicked',
        title: resource.configureGroupsText,
        cls: 'group-configuration',
        iconCls: 'fa fa-cog fa-fw ',
      });

      if (this.groupList && this.groupList.length > 0) {
        array.forEach(this.groupList, (group) => {
          groupsSection.children.push({
            name: group.name,
            action: 'groupClicked',
            title: group.displayName,
            dataProps: {
              $key: group.$key,
              title: group.displayName,
            },
          });
        });
      }
      const layoutSection = {
        id: 'actions',
        children: [],
      };
      if (this.groupTemplateLayouts && this.groupTemplateLayouts.length > 0) {
        array.forEach(this.groupTemplateLayouts, (theLayout) => {
          layoutSection.children.push({
            name: theLayout.name,
            action: 'layoutSelectedClicked',
            title: theLayout.displayName,
            dataProps: {
              name: theLayout.name,
              title: theLayout.displayName,
              enabled: theLayout.name === App.preferences[`groups-selected-template-name${this.entityName}`],
            },
          });
        });
      }

      if (this.entityName) {
        layout.push(groupsSection);
        layout.push(layoutSection);
      }
    }

    if (App.enableHashTags) {
      const hashTagsSection = {
        id: 'actions',
        children: [],
      };

      if (this._hasHashTags()) {
        const len = this.searchWidget.hashTagQueries.length;
        for (let i = 0; i < len; i++) {
          const hashTag = this.searchWidget.hashTagQueries[i];
          hashTagsSection.children.push({
            name: hashTag.key,
            action: 'hashTagClicked',
            title: hashTag.tag,
            dataProps: {
              hashtag: hashTag.tag,
            },
          });
        }
      }

      layout.push(hashTagsSection);
    }

    if (this.createMetricWidgetsLayout) {
      const metrics = App.getMetricsByResourceKind(this.resourceKind);

      const kpiSection = {
        id: 'kpi',
        children: [],
      };

      if (metrics.length > 0) {
        array.forEach(metrics, (metric, i) => {
          if (metric.title) {
            kpiSection.children.push({
              name: `KPI${i}`,
              action: 'kpiClicked',
              title: metric.title,
              dataProps: {
                title: escape(metric.title),
                enabled: !!metric.enabled,
              },
            });
          }
        });

        layout.push(kpiSection);
      }
    }

    return layout;
  },
  _hasHashTags: function _hasHashTags() {
    return this.searchWidget && this.searchWidget.hashTagQueries && this.searchWidget.hashTagQueries.length > 0;
  },
});

lang.setObject('Mobile.SalesLogix.Views._RightDrawerListMixin', __class);
export default __class;
<commit_msg>Add default layout selection when preference does not exist<commit_after>import declare from 'dojo/_base/declare';
import array from 'dojo/_base/array';
import lang from 'dojo/_base/lang';
import aspect from 'dojo/aspect';
import GroupUtility from '../GroupUtility';
import _RightDrawerBaseMixin from './_RightDrawerBaseMixin';
import LookupField from 'argos/Fields/LookupField';
import getResource from 'argos/I18n';
import $ from 'jquery';

const resource = getResource('rightDrawerListMixin');

/**
 * @class crm.Views._RightDrawerListMixin
 *
 * List mixin for right drawers.
 *
 * @since 3.0
 * @mixins crm.Views._RightDrawerBaseMixin
 *
 */
const __class = declare('crm.Views._RightDrawerListMixin', [_RightDrawerBaseMixin], {
  // Localization
  hashTagsSectionText: resource.hashTagsSectionText,
  groupsSectionText: resource.groupsSectionText,
  kpiSectionText: resource.kpiSectionText,
  configureGroupsText: resource.configureGroupsText,
  refreshGroupsText: resource.refreshGroupsText,
  layoutsText: resource.layoutsText,

  _hasChangedKPIPrefs: false, // Dirty flag so we know when to reload the widgets
  _hashTagClicked: false,
  groupList: null,
  DRAWER_PAGESIZE: 100,
  groupLookupId: 'groups_configure',
  hasSettings: true,

  setupRightDrawer: function setupRightDrawer() {
    const drawer = App.getView('right_drawer');
    if (drawer) {
      drawer.pageSize = this.DRAWER_PAGESIZE;
      this.groupList = GroupUtility.getGroupPreferences(this.entityName);
      this._finishSetup(drawer);
    }
  },
  refreshRightDrawer: function refreshRightDrawer() {
    const drawer = App.getView('right_drawer');
    if (drawer) {
      drawer.clear();
      drawer.layout = null;
      drawer.setLayout(this.createRightDrawerLayout());
      drawer.refresh();
    }
  },
  _finishSetup: function _finishSetup(drawer) {
    lang.mixin(drawer, this._createActions());
    drawer.setLayout(this.createRightDrawerLayout());
    drawer.getGroupForEntry = function getGroupForEntry(entry) {
      return this.getGroupForRightDrawerEntry(entry);
    }.bind(this);

    if (this.rebuildWidgets) {
      App.viewSettingsModal.element.on('close', () => {
        if (this._hasChangedKPIPrefs) {
          this.destroyWidgets();

          // HACK: Don't rebuild widgets if a hashtag was clicked,
          // because the widget mixin will rebuild on a data refresh anyways.
          // TODO: Fix multiple calls to rebuildWidets() at the same time.
          if (!this._hashTagClicked) {
            this.rebuildWidgets();
          }
          this._hasChangedKPIPrefs = false;
        }

        // Reset state
        this._hashTagClicked = false;
      });
    }
  },
  unloadRightDrawer: function unloadRightDrawer() {
    const drawer = App.getView('right_drawer');
    if (drawer) {
      drawer.setLayout([]);
      drawer.getGroupForEntry = function getGroupForEntry() {};
      App.viewSettingsModal.element.off('close');
    }
  },
  _onSearchExpression: function _onSearchExpression() {
    // TODO: Don't extend this private function - connect to the search widget onSearchExpression instead
    if (this.groupsMode) {
      this._clearGroupMode();
    }

    this.inherited(arguments);
  },
  openSettings: function openSettings() {
    App.viewSettingsModal.open();
  },
  _createActions: function _createActions() {
    // These actions will get mixed into the right drawer view.
    const actions = {
      hashTagClicked: function hashTagClicked(params) {
        const { hashtag } = params;

        if (this.groupsMode) {
          this._clearGroupMode();
        }

        if (hashtag && typeof hashtag === 'string') {
          this._hashTagClicked = true;
          if (hashtag.startsWith('#')) {
            this.setSearchTerm(hashtag);
          } else {
            this.setSearchTerm(`#${hashtag}`);
          }

          this.search();
          App.viewSettingsModal.close();
        }
      }.bind(this),
      kpiClicked: function kpiClicked(params) {
        const metrics = App.getMetricsByResourceKind(this.resourceKind);
        let results;

        if (metrics.length > 0) {
          results = array.filter(metrics, (metric) => {
            return metric.title === unescape(params.title);
          });
        }

        if (results.length > 0) {
          const enabled = !!results[0].enabled;
          results[0].enabled = !enabled;
          App.persistPreferences();
          this._hasChangedKPIPrefs = true;

          $(params.$source).attr('data-enabled', (!enabled).toString());
        }
      }.bind(this),
      groupConfigureClicked: function groupConfigureClicked() {
        this._selectGroups();
        App.viewSettingsModal.close();
      }.bind(this),
      groupClicked: function groupClicked(params) {
        this._startGroupMode();
        const groupId = params.$key;

        const group = array.filter(this.groupList, (item) => {
          return item.$key === groupId;
        })[0];

        if (!group) {
          throw new Error('Expected a group.');
        }
        this.setCurrentGroup(group);
        this.refresh();
        App.viewSettingsModal.close();
      }.bind(this),
      layoutSelectedClicked: function layoutSelectedClicked(params) {
        const name = params.name;
        $(params.$source.parentElement.parentElement).find('a').each((i, a) => {
          $(a).attr('data-enabled', (($(a).attr('data-name') === name)).toString());
        });

        GroupUtility.setSelectedGroupLayoutTemplate(this.entityName, name);
        this._groupInitialized = false;
        this.refresh();
        App.viewSettingsModal.close();
      }.bind(this),

    };

    return actions;
  },
  _selectGroups: function _selectGroups() {
    const view = App.getView(this.groupLookupId);
    view.family = this.entityName;
    view.set('store', null);
    view.clear();
    view.refreshRequired = true;

    const field = new LookupField({
      owner: this,
      view,
      singleSelect: false,
      previousSelections: array.map(this.groupList, (group) => {
        return group.$key;
      }),
    });

    const handle = aspect.after(field, 'complete', function afterComplete() {
      const self = this;
      const list = this.owner;
      const items = [];

      // We will get an object back where the property names are the keys (groupId's)
      // Extract them out, and save the entry, which is the data property on the extracted object
      for (const groupId in self.currentValue) {
        if (self.currentValue.hasOwnProperty(groupId)) {
          const entry = self.currentValue[groupId].data;
          if (entry) {
            items.push(entry);
          }
        }
      }

      const hasDefaultGroup = list.hasDefaultGroup;
      GroupUtility.addToGroupPreferences(items, list.entityName, true);
      const currentGroup = GroupUtility.getDefaultGroup(list.entityName);
      if (currentGroup) {
        list.setCurrentGroup(currentGroup);
      }

      handle.remove();
      self.destroy();

      if (hasDefaultGroup) {
        // We will transition back to the list, pop back open the right drawer so the user is back where they started
        const processDataHandle = aspect.after(list, 'processData', function postProcessData() {
          App.viewSettingsModal.close();
          processDataHandle.remove();
          if (this.transitionHandle) {
            this.transitionHandle.remove();
          }
        }.bind(list));
      } else {
        // Since there was no previous default group, just refresh the list (no need to toggle the right drawer)
        this.transitionHandle = aspect.after(list, 'onTransitionTo', function postOnTransitionTo() {
          this.refreshRequired = true;
          this.clear();
          this.refresh();
          if (this.transitionHandle) {
            this.transitionHandle.remove();
          }
        }.bind(list));
      }
    }.bind(field));

    field.navigateToListView();
  },
  getGroupForRightDrawerEntry: function getGroupForRightDrawerEntry(entry) {
    if (entry.dataProps && entry.dataProps.hashtag && this._hasHashTags() && App.enableHashTags) {
      return {
        tag: 'view',
        title: resource.hashTagsSectionText,
      };
    }

    if ((entry.action === 'groupClicked' || entry.action === 'groupConfigureClicked') && this.groupsEnabled) {
      return {
        tag: 'group',
        title: resource.groupsSectionText,
      };
    }
    if ((entry.action === 'layoutSelectedClicked') && this.groupsEnabled) {
      return {
        tag: 'layoutTemplates',
        title: resource.layoutsText,
      };
    }
    return {
      tag: 'kpi',
      title: resource.kpiSectionText,
    };
  },
  createRightDrawerLayout: function createRightDrawerLayout() {
    const layout = [];

    if (this.groupsEnabled) {
      const groupsSection = {
        id: 'actions',
        children: [],
      };

      groupsSection.children.push({
        name: 'configureGroups',
        action: 'groupConfigureClicked',
        title: resource.configureGroupsText,
        cls: 'group-configuration',
        iconCls: 'fa fa-cog fa-fw ',
      });

      if (this.groupList && this.groupList.length > 0) {
        array.forEach(this.groupList, (group) => {
          groupsSection.children.push({
            name: group.name,
            action: 'groupClicked',
            title: group.displayName,
            dataProps: {
              $key: group.$key,
              title: group.displayName,
            },
          });
        });
      }
      const layoutSection = {
        id: 'actions',
        children: [],
      };
      if (this.groupTemplateLayouts && this.groupTemplateLayouts.length > 0) {
        let layoutSelected = false;
        array.forEach(this.groupTemplateLayouts, (theLayout) => {
          if (!layoutSelected) {
            layoutSelected = theLayout.name === App.preferences[`groups-selected-template-name${this.entityName}`];
          }
          layoutSection.children.push({
            name: theLayout.name,
            action: 'layoutSelectedClicked',
            title: theLayout.displayName,
            dataProps: {
              name: theLayout.name,
              title: theLayout.displayName,
              enabled: theLayout.name === App.preferences[`groups-selected-template-name${this.entityName}`],
            },
          });
        });
        if (!layoutSelected && layoutSection.children.length) {
          layoutSection.children[0].dataProps.enabled = true;
        }
      }

      if (this.entityName) {
        layout.push(groupsSection);
        layout.push(layoutSection);
      }
    }

    if (App.enableHashTags) {
      const hashTagsSection = {
        id: 'actions',
        children: [],
      };

      if (this._hasHashTags()) {
        const len = this.searchWidget.hashTagQueries.length;
        for (let i = 0; i < len; i++) {
          const hashTag = this.searchWidget.hashTagQueries[i];
          hashTagsSection.children.push({
            name: hashTag.key,
            action: 'hashTagClicked',
            title: hashTag.tag,
            dataProps: {
              hashtag: hashTag.tag,
            },
          });
        }
      }

      layout.push(hashTagsSection);
    }

    if (this.createMetricWidgetsLayout) {
      const metrics = App.getMetricsByResourceKind(this.resourceKind);

      const kpiSection = {
        id: 'kpi',
        children: [],
      };

      if (metrics.length > 0) {
        array.forEach(metrics, (metric, i) => {
          if (metric.title) {
            kpiSection.children.push({
              name: `KPI${i}`,
              action: 'kpiClicked',
              title: metric.title,
              dataProps: {
                title: escape(metric.title),
                enabled: !!metric.enabled,
              },
            });
          }
        });

        layout.push(kpiSection);
      }
    }

    return layout;
  },
  _hasHashTags: function _hasHashTags() {
    return this.searchWidget && this.searchWidget.hashTagQueries && this.searchWidget.hashTagQueries.length > 0;
  },
});

lang.setObject('Mobile.SalesLogix.Views._RightDrawerListMixin', __class);
export default __class;
<|endoftext|>


<commit_before>/* eslint global-require: 0 */
import {RegExpUtils} from 'nylas-exports';
import Task from './task';
import Actions from '../actions';
import Message from '../models/message';
import NylasAPI from '../nylas-api';
import {APIError} from '../errors';
import SoundRegistry from '../../sound-registry';
import DatabaseStore from '../stores/database-store';
import AccountStore from '../stores/account-store';
import BaseDraftTask from './base-draft-task';
import MultiSendToIndividualTask from './multi-send-to-individual-task';
import MultiSendSessionCloseTask from './multi-send-session-close-task';
import SyncbackMetadataTask from './syncback-metadata-task';
import NotifyPluginsOfSendTask from './notify-plugins-of-send-task';

// TODO
// Refactor this to consolidate error handling across all Sending tasks
export default class SendDraftTask extends BaseDraftTask {

  constructor(draftClientId, {playSound = true, emitError = true, allowMultiSend = true} = {}) {
    super(draftClientId);
    this.draft = null;
    this.message = null;
    this.emitError = emitError
    this.playSound = playSound
    this.allowMultiSend = allowMultiSend
  }

  label() {
    return "Sending message...";
  }

  performRemote() {
    return this.refreshDraftReference()
    .then(this.assertDraftValidity)
    .then(this.sendMessage)
    .then(this.updatePluginMetadata)
    .then(this.onSuccess)
    .catch(this.onError);
  }

  assertDraftValidity = () => {
    if (!this.draft.from[0]) {
      return Promise.reject(new Error("SendDraftTask - you must populate `from` before sending."));
    }

    const account = AccountStore.accountForEmail(this.draft.from[0].email);
    if (!account) {
      return Promise.reject(new Error("SendDraftTask - you can only send drafts from a configured account."));
    }
    if (this.draft.accountId !== account.id) {
      return Promise.reject(new Error("The from address has changed since you started sending this draft. Double-check the draft and click 'Send' again."));
    }
    if (this.draft.uploads && (this.draft.uploads.length > 0)) {
      return Promise.reject(new Error("Files have been added since you started sending this draft. Double-check the draft and click 'Send' again.."));
    }
    return Promise.resolve();
  }

  usingMultiSend = () => {
    if (!this.allowMultiSend) {
      return false;
    }

    // Sending individual bodies for too many participants can cause us
    // to hit the smtp rate limit.
    if (this.draft.participants({includeFrom: false}).length > 10) {
      return false;
    }

    const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking')
    const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking')

    const pluginsAvailable = (openTrackingId && linkTrackingId);
    if (!pluginsAvailable) {
      return false;
    }
    const pluginsInUse = (this.draft.metadataForPluginId(openTrackingId) || this.draft.metadataForPluginId(linkTrackingId));
    const providerCompatible = (AccountStore.accountForId(this.draft.accountId).provider !== "eas");
    return pluginsInUse && providerCompatible;
  }

  sendMessage = () => {
    return this.usingMultiSend() ? this.sendWithMultipleBodies() : this.sendWithSingleBody();
  }

  sendWithMultipleBodies = () => {
    const draft = this.draft.clone();
    draft.body = this.stripTrackingFromBody(draft.body);

    return NylasAPI.makeRequest({
      path: "/send-multiple",
      accountId: this.draft.accountId,
      method: 'POST',
      body: draft.toJSON(),
      timeout: 1000 * 60 * 5, // We cannot hang up a send - won't know if it sent
      returnsModel: false,
    })
    .then((responseJSON) => {
      return this.createMessageFromResponse(responseJSON);
    })
    .then(() => {
      const recipients = this.message.participants({includeFrom: false, includeBcc: true})
      recipients.forEach((recipient) => {
        const t1 = new MultiSendToIndividualTask({
          message: this.message,
          recipient: recipient,
        });
        Actions.queueTask(t1);
      });
      const t2 = new MultiSendSessionCloseTask({
        message: this.message,
        draft: draft,
      });
      Actions.queueTask(t2);
    })
    .catch((err) => {
      return this.onSendError(err, this.sendWithMultipleBodies);
    })
  }

  // This function returns a promise that resolves to the draft when the draft has
  // been sent successfully.
  sendWithSingleBody = () => {
    return NylasAPI.makeRequest({
      path: "/send",
      accountId: this.draft.accountId,
      method: 'POST',
      body: this.draft.toJSON(),
      timeout: 1000 * 60 * 5, // We cannot hang up a send - won't know if it sent
      returnsModel: false,
    })
    .then((responseJSON) => {
      return this.createMessageFromResponse(responseJSON)
    })
    .catch((err) => {
      return this.onSendError(err, this.sendWithSingleBody);
    })
  }

  updatePluginMetadata = () => {
    this.message.pluginMetadata.forEach((m) => {
      const t1 = new SyncbackMetadataTask(this.message.clientId,
          this.message.constructor.name, m.pluginId);
      Actions.queueTask(t1);
    });

    if (this.message.pluginMetadata.length > 0) {
      const t2 = new NotifyPluginsOfSendTask({
        accountId: this.message.accountId,
        messageId: this.message.id,
        messageClientId: this.message.clientId,
      });
      Actions.queueTask(t2);
    }

    return Promise.resolve();
  }

  createMessageFromResponse = (responseJSON) => {
    this.message = new Message().fromJSON(responseJSON);
    this.message.clientId = this.draft.clientId;
    this.message.body = this.draft.body;
    this.message.draft = false;
    this.message.clonePluginMetadataFrom(this.draft);

    return DatabaseStore.inTransaction((t) =>
      this.refreshDraftReference().then(() => {
        return t.persistModel(this.message);
      })
    );
  }

  stripTrackingFromBody(text) {
    let body = text.replace(/<img class="n1-open"[^<]+src="([a-zA-Z0-9-_:\/.]*)">/g, () => {
      return "";
    });
    body = body.replace(RegExpUtils.urlLinkTagRegex(), (match, prefix, url, suffix, content, closingTag) => {
      const param = url.split("?")[1];
      if (param) {
        const link = decodeURIComponent(param.split("=")[1]);
        return `${prefix}${link}${suffix}${content}${closingTag}`;
      }
      return match;
    });
    return body;
  }

  onSuccess = () => {
    // TODO: This code is duplicated into the MultiSendSessionCloseTask!
    // We should create a Task that always runs when send is complete.
    if (!this.usingMultiSend()) {
      Actions.recordUserEvent("Draft Sent")
      Actions.sendDraftSuccess({message: this.message, messageClientId: this.message.clientId, draftClientId: this.draft.clientId});
      NylasAPI.makeDraftDeletionRequest(this.draft);

      // Play the sending sound
      if (this.playSound && NylasEnv.config.get("core.sending.sounds")) {
        SoundRegistry.playSound('send');
      }
    }
    return Promise.resolve(Task.Status.Success);
  }

  onSendError = (err, retrySend) => {
    let shouldRetry = false;
    // If the message you're "replying to" has been deleted
    if (err.message && err.message.indexOf('Invalid message public id') === 0) {
      this.draft.replyToMessageId = null;
      shouldRetry = true
    }

    // If the thread has been deleted
    if (err.message && err.message.indexOf('Invalid thread') === 0) {
      this.draft.threadId = null;
      this.draft.replyToMessageId = null;
      shouldRetry = true
    }

    Actions.recordUserEvent("Draft Sending Errored", {
      error: err.message,
      shouldRetry: shouldRetry,
    })

    if (shouldRetry) {
      return retrySend()
    }

    return Promise.reject(err);
  }

  onError = (err) => {
    if (err instanceof BaseDraftTask.DraftNotFoundError) {
      return Promise.resolve(Task.Status.Continue);
    }

    let message = err.message;

    if (err instanceof APIError) {
      message = `Sorry, this message could not be sent. Please try again, and make sure your message is addressed correctly and is not too large.`;
      if (err.statusCode === 402 && err.body.message) {
        if (err.body.message.indexOf('at least one recipient') !== -1) {
          message = `This message could not be delivered to at least one recipient. (Note: other recipients may have received this message - you should check Sent Mail before re-sending this message.)`;
        } else {
          message = `Sorry, this message could not be sent because it was rejected by your mail provider. (${err.body.message})`;
          if (err.body.server_error) {
            message += `\n\n${err.body.server_error}`;
          }
        }
      }
    }

    if (this.emitError) {
      Actions.sendDraftFailed({
        threadId: this.draft.threadId,
        draftClientId: this.draft.clientId,
        errorMessage: message,
      });
    }
    NylasEnv.reportError(err);

    return Promise.resolve([Task.Status.Failed, err]);
  }
}
<commit_msg>fix(send): Check for number of BCC participants before using multi-send<commit_after>/* eslint global-require: 0 */
import {RegExpUtils} from 'nylas-exports';
import Task from './task';
import Actions from '../actions';
import Message from '../models/message';
import NylasAPI from '../nylas-api';
import {APIError} from '../errors';
import SoundRegistry from '../../sound-registry';
import DatabaseStore from '../stores/database-store';
import AccountStore from '../stores/account-store';
import BaseDraftTask from './base-draft-task';
import MultiSendToIndividualTask from './multi-send-to-individual-task';
import MultiSendSessionCloseTask from './multi-send-session-close-task';
import SyncbackMetadataTask from './syncback-metadata-task';
import NotifyPluginsOfSendTask from './notify-plugins-of-send-task';

// TODO
// Refactor this to consolidate error handling across all Sending tasks
export default class SendDraftTask extends BaseDraftTask {

  constructor(draftClientId, {playSound = true, emitError = true, allowMultiSend = true} = {}) {
    super(draftClientId);
    this.draft = null;
    this.message = null;
    this.emitError = emitError
    this.playSound = playSound
    this.allowMultiSend = allowMultiSend
  }

  label() {
    return "Sending message...";
  }

  performRemote() {
    return this.refreshDraftReference()
    .then(this.assertDraftValidity)
    .then(this.sendMessage)
    .then(this.updatePluginMetadata)
    .then(this.onSuccess)
    .catch(this.onError);
  }

  assertDraftValidity = () => {
    if (!this.draft.from[0]) {
      return Promise.reject(new Error("SendDraftTask - you must populate `from` before sending."));
    }

    const account = AccountStore.accountForEmail(this.draft.from[0].email);
    if (!account) {
      return Promise.reject(new Error("SendDraftTask - you can only send drafts from a configured account."));
    }
    if (this.draft.accountId !== account.id) {
      return Promise.reject(new Error("The from address has changed since you started sending this draft. Double-check the draft and click 'Send' again."));
    }
    if (this.draft.uploads && (this.draft.uploads.length > 0)) {
      return Promise.reject(new Error("Files have been added since you started sending this draft. Double-check the draft and click 'Send' again.."));
    }
    return Promise.resolve();
  }

  usingMultiSend = () => {
    if (!this.allowMultiSend) {
      return false;
    }

    // Sending individual bodies for too many participants can cause us
    // to hit the smtp rate limit.
    if (this.draft.participants({includeFrom: false, includeBcc: true}).length > 10) {
      return false;
    }

    const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking')
    const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking')

    const pluginsAvailable = (openTrackingId && linkTrackingId);
    if (!pluginsAvailable) {
      return false;
    }
    const pluginsInUse = (this.draft.metadataForPluginId(openTrackingId) || this.draft.metadataForPluginId(linkTrackingId));
    const providerCompatible = (AccountStore.accountForId(this.draft.accountId).provider !== "eas");
    return pluginsInUse && providerCompatible;
  }

  sendMessage = () => {
    return this.usingMultiSend() ? this.sendWithMultipleBodies() : this.sendWithSingleBody();
  }

  sendWithMultipleBodies = () => {
    const draft = this.draft.clone();
    draft.body = this.stripTrackingFromBody(draft.body);

    return NylasAPI.makeRequest({
      path: "/send-multiple",
      accountId: this.draft.accountId,
      method: 'POST',
      body: draft.toJSON(),
      timeout: 1000 * 60 * 5, // We cannot hang up a send - won't know if it sent
      returnsModel: false,
    })
    .then((responseJSON) => {
      return this.createMessageFromResponse(responseJSON);
    })
    .then(() => {
      const recipients = this.message.participants({includeFrom: false, includeBcc: true})
      recipients.forEach((recipient) => {
        const t1 = new MultiSendToIndividualTask({
          message: this.message,
          recipient: recipient,
        });
        Actions.queueTask(t1);
      });
      const t2 = new MultiSendSessionCloseTask({
        message: this.message,
        draft: draft,
      });
      Actions.queueTask(t2);
    })
    .catch((err) => {
      return this.onSendError(err, this.sendWithMultipleBodies);
    })
  }

  // This function returns a promise that resolves to the draft when the draft has
  // been sent successfully.
  sendWithSingleBody = () => {
    return NylasAPI.makeRequest({
      path: "/send",
      accountId: this.draft.accountId,
      method: 'POST',
      body: this.draft.toJSON(),
      timeout: 1000 * 60 * 5, // We cannot hang up a send - won't know if it sent
      returnsModel: false,
    })
    .then((responseJSON) => {
      return this.createMessageFromResponse(responseJSON)
    })
    .catch((err) => {
      return this.onSendError(err, this.sendWithSingleBody);
    })
  }

  updatePluginMetadata = () => {
    this.message.pluginMetadata.forEach((m) => {
      const t1 = new SyncbackMetadataTask(this.message.clientId,
          this.message.constructor.name, m.pluginId);
      Actions.queueTask(t1);
    });

    if (this.message.pluginMetadata.length > 0) {
      const t2 = new NotifyPluginsOfSendTask({
        accountId: this.message.accountId,
        messageId: this.message.id,
        messageClientId: this.message.clientId,
      });
      Actions.queueTask(t2);
    }

    return Promise.resolve();
  }

  createMessageFromResponse = (responseJSON) => {
    this.message = new Message().fromJSON(responseJSON);
    this.message.clientId = this.draft.clientId;
    this.message.body = this.draft.body;
    this.message.draft = false;
    this.message.clonePluginMetadataFrom(this.draft);

    return DatabaseStore.inTransaction((t) =>
      this.refreshDraftReference().then(() => {
        return t.persistModel(this.message);
      })
    );
  }

  stripTrackingFromBody(text) {
    let body = text.replace(/<img class="n1-open"[^<]+src="([a-zA-Z0-9-_:\/.]*)">/g, () => {
      return "";
    });
    body = body.replace(RegExpUtils.urlLinkTagRegex(), (match, prefix, url, suffix, content, closingTag) => {
      const param = url.split("?")[1];
      if (param) {
        const link = decodeURIComponent(param.split("=")[1]);
        return `${prefix}${link}${suffix}${content}${closingTag}`;
      }
      return match;
    });
    return body;
  }

  onSuccess = () => {
    // TODO: This code is duplicated into the MultiSendSessionCloseTask!
    // We should create a Task that always runs when send is complete.
    if (!this.usingMultiSend()) {
      Actions.recordUserEvent("Draft Sent")
      Actions.sendDraftSuccess({message: this.message, messageClientId: this.message.clientId, draftClientId: this.draft.clientId});
      NylasAPI.makeDraftDeletionRequest(this.draft);

      // Play the sending sound
      if (this.playSound && NylasEnv.config.get("core.sending.sounds")) {
        SoundRegistry.playSound('send');
      }
    }
    return Promise.resolve(Task.Status.Success);
  }

  onSendError = (err, retrySend) => {
    let shouldRetry = false;
    // If the message you're "replying to" has been deleted
    if (err.message && err.message.indexOf('Invalid message public id') === 0) {
      this.draft.replyToMessageId = null;
      shouldRetry = true
    }

    // If the thread has been deleted
    if (err.message && err.message.indexOf('Invalid thread') === 0) {
      this.draft.threadId = null;
      this.draft.replyToMessageId = null;
      shouldRetry = true
    }

    Actions.recordUserEvent("Draft Sending Errored", {
      error: err.message,
      shouldRetry: shouldRetry,
    })

    if (shouldRetry) {
      return retrySend()
    }

    return Promise.reject(err);
  }

  onError = (err) => {
    if (err instanceof BaseDraftTask.DraftNotFoundError) {
      return Promise.resolve(Task.Status.Continue);
    }

    let message = err.message;

    if (err instanceof APIError) {
      message = `Sorry, this message could not be sent. Please try again, and make sure your message is addressed correctly and is not too large.`;
      if (err.statusCode === 402 && err.body.message) {
        if (err.body.message.indexOf('at least one recipient') !== -1) {
          message = `This message could not be delivered to at least one recipient. (Note: other recipients may have received this message - you should check Sent Mail before re-sending this message.)`;
        } else {
          message = `Sorry, this message could not be sent because it was rejected by your mail provider. (${err.body.message})`;
          if (err.body.server_error) {
            message += `\n\n${err.body.server_error}`;
          }
        }
      }
    }

    if (this.emitError) {
      Actions.sendDraftFailed({
        threadId: this.draft.threadId,
        draftClientId: this.draft.clientId,
        errorMessage: message,
      });
    }
    NylasEnv.reportError(err);

    return Promise.resolve([Task.Status.Failed, err]);
  }
}
<|endoftext|>


<commit_before>/* jslint node: true */
module.exports = function (grunt) {
  if (process.env.APPVEYOR_REPO_BRANCH !== 'master' || !process.env.APPVEYOR_REPO_TAG) {
    grunt.log.writeln('The release will only be created from a tag on the master branch.');
    return;
  }

  return {
    zip: {
      options: {
        archive: 'Exceptionless.UI.' + process.env.APPVEYOR_BUILD_VERSION + '.zip'
      },
      files: [
        {
          expand: true,
          cwd: 'dist/',
          src: ['**/*'],
          dest: './'
        }
      ]
    }
  };
};
<commit_msg>Create release artifacts on different branches<commit_after>/* jslint node: true */
module.exports = function () {
  return {
    zip: {
      options: {
        archive: 'Exceptionless.UI.' + process.env.APPVEYOR_BUILD_VERSION + '.zip'
      },
      files: [
        {
          expand: true,
          cwd: 'dist/',
          src: ['**/*'],
          dest: './'
        }
      ]
    }
  };
};
<|endoftext|>


<commit_before>/**
 * Created by rinesnow on 16/3/29.
 */
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var session= require('express-session');
var moment = require('moment');
var authentication = require('./authentication.js');

var app = express();

//连接数据库
var mongoose = require('mongoose');
var models = require('./models/models');

var User = models.User;
var Note = models.Note;

mongoose.connect('mongodb://localhost:27017/notes');
mongoose.connection.on('error',console.error.bind(console,'连接数据库失败!'));

//定义EJS模版引擎和末班文件位置
app.set('views', path.join(__dirname,'views'));
app.set('view engine','ejs');

//定义静态文件目录
app.use(express.static(path.join(__dirname,'public')));

//定义数据解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

//建立session模型
app.use(session({
    secret:'liuchong',//?
    name:'intern',
    //一周内免登录
    cookie:{maxAge:1000 * 60 * 60*24*7},//设置session的保存时间为 60min*24小时*7一周
    resave: false,
    saveUnintiallized:true
}));

//添加  页面提示功能
app.use(function(req, res, next) {
    res.locals.user = req.session.user;//???
    var err = req.session.error;//赋值给err变量,
    //var success = req.session.success;//赋值给err变量,

    delete req.session.error;//然后清空session内的数据
    res.locals.message = '';//初始化
    //res.locals.success = '';//初始化

    if (err) {
        //html通过js被操作,在html中用{%-message%}调用
        res.locals.message = '
' + err + '
';
    }
    //if (success) {
    //    //html通过js被操作,在html中用{%-message%}调用
    //    res.locals.success = '
' + success + '
';
    //}

    next();//?
});

//响应首页get请求
app.get('/',authentication.ifAuthorized);//检测是否登录了
app.get('/',function(req,res){
//openid???
    // var openid=authentication.getOpenid;
    // console.log('在／中'+openid);

    console.log( '这是req1'+res.openid);
    console.log( '这是req2'+req.openid);

    console.log( '这是req3'+openid);

    Note.find({author:openid}).exec(function(err,allNotes){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        res.render('index',{
           title:'首页',
            openid: openid,
            notes:allNotes
        });
    })
});

//登出
app.get('/quit',function(req,res){
    //退出功能只需将session中的user删除即可。
    // req.session.user = null;
    console.log('退出!');
    return res.redirect('/login');
});

//发布

//响应发布get请求
app.get('/post',authentication.ifAuthorized);//检测是否登录了
app.get('/post',function(req,res){
   console.log('发布!');
    res.render('post',{
        user: req.session.user,//也要加?
        title:'发布'
    });
});
//响应发布post请求
app.post('/post',function(req,res){

     User.findOne({openid:openid},function(err,user){
           if(err){
               console.log(err);
           }
            if(user){//有这个user 那么直接跳转
                return res.redirect('/');
            }
        });
   var note = new Note({
       title:req.body.title,
       author:openid,
       tag: req.body.tag,
       content: req.body.content
   });
    note.save(function(err,doc){
       if(err){
           console.log(err);
           return res.redirect('/post');
       }
        console.log('文章发表成功!')
        return res.redirect('/');
    });
});
//

//博客细节

app.get('/detail/',authentication.ifAuthorized);//检测是否登录了
app.get('/detail/:_id',function(req,res){//:id?
   console.log('查看笔记!');
    Note.findOne({_id:req.params._id}).exec(function(err,art){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        if(art) {
            res.render('detail', {
                title: '笔记详情',
                user: req.session.user,
                art: art,
                moment:moment
            });
            console.log(art);
        }
    });
});

// app.get('https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx75d11b4f981b1ded&secret=7a915c525f39451f3af19407012459ad&code=CODE&grant_type=authorization_code',function(req,res){

// });//获取appid

app.listen(3000, function(req,res){
    console.log('app is running at port 3000');
});<commit_msg>yonghu id<commit_after>/**
 * Created by rinesnow on 16/3/29.
 */
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var session= require('express-session');
var moment = require('moment');
var authentication = require('./authentication.js');

var app = express();

//连接数据库
var mongoose = require('mongoose');
var models = require('./models/models');

var User = models.User;
var Note = models.Note;

mongoose.connect('mongodb://localhost:27017/notes');
mongoose.connection.on('error',console.error.bind(console,'连接数据库失败!'));

//定义EJS模版引擎和末班文件位置
app.set('views', path.join(__dirname,'views'));
app.set('view engine','ejs');

//定义静态文件目录
app.use(express.static(path.join(__dirname,'public')));

//定义数据解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

//建立session模型
app.use(session({
    secret:'liuchong',//?
    name:'intern',
    //一周内免登录
    cookie:{maxAge:1000 * 60 * 60*24*7},//设置session的保存时间为 60min*24小时*7一周
    resave: false,
    saveUnintiallized:true
}));

//添加  页面提示功能
app.use(function(req, res, next) {
    res.locals.user = req.session.user;//???
    var err = req.session.error;//赋值给err变量,
    //var success = req.session.success;//赋值给err变量,

    delete req.session.error;//然后清空session内的数据
    res.locals.message = '';//初始化
    //res.locals.success = '';//初始化

    if (err) {
        //html通过js被操作,在html中用{%-message%}调用
        res.locals.message = '
' + err + '
';
    }
    //if (success) {
    //    //html通过js被操作,在html中用{%-message%}调用
    //    res.locals.success = '
' + success + '
';
    //}

    next();//?
});

   var OAuth = require('wechat-oauth');
   var client = new OAuth('wx75d11b4f981b1ded', '7a915c525f39451f3af19407012459ad');
 // var openid='hehehe';
app.use(function ifAuthorized(req,res,next){

    // var url = client.getAuthorizeURL('www.coderwitkey.com', 'STATE', 'snsapi_userinfo');
    var query = require('url').parse(req.url,true).query;
          console.log(query);

    var code = query.code;
          console.log(code);

    client.getUserByCode(code,function (err, result) {
      // var accessToken = result.data.access_token;

            console.log("这是openid1"+openid);

      openid = result.openid;//必须要手动点击URL，原地刷新没用的。

      console.log(result);
      // console.log(accessToken);

      console.log("这是openid2"+openid););

       User.findOne({openid:openid},function(err,user){
           if(err){
               console.log(err);
           }
            if(user){//有这个user 那么直接跳转
                return res.redirect('/');
            }
            // //对密码进行md5加密
            // var md5 = crypto.createHash('md5'),
            //     md5newopenid = md5.update(openid).digest('hex');
            //新建user对象用于保存数据
            var newUser = new User({
                openid:openid//openid 作为key存入，以后再用用户信息就用openid调。
            });

            newUser.save(function(err,doc){
                if(err){
                    console.log("保存用户信息失败！"+err);
                }
                console.log('保存用户信息成功!');//怎么实现弹出框!!!!??????
                return res.redirect('/');
            });
        });
    });
    next();
}

//响应首页get请求
// app.get('/',authentication.ifAuthorized);//检测是否登录了
app.get('/',function(req,res){
//openid???
    // var openid=authentication.getOpenid;
    // console.log('在／中'+openid);

    console.log( '这是req1'+res.openid);
    console.log( '这是req2'+req.openid);

    console.log( '这是req3'+openid);

    Note.find({author:openid}).exec(function(err,allNotes){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        res.render('index',{
           title:'首页',
            openid: openid,
            notes:allNotes
        });
    })
});

//登出
app.get('/quit',function(req,res){
    //退出功能只需将session中的user删除即可。
    // req.session.user = null;
    console.log('退出!');
    return res.redirect('/login');
});

//发布

//响应发布get请求
// app.get('/post',authentication.ifAuthorized);//检测是否登录了
app.get('/post',function(req,res){
   console.log('发布!');
    res.render('post',{
        user: req.session.user,//也要加?
        title:'发布'
    });
});
//响应发布post请求
app.post('/post',function(req,res){

     User.findOne({openid:openid},function(err,user){
           if(err){
               console.log(err);
           }
            if(user){//有这个user 那么直接跳转
                return res.redirect('/');
            }
        });
   var note = new Note({
       title:req.body.title,
       author:openid,
       tag: req.body.tag,
       content: req.body.content
   });
    note.save(function(err,doc){
       if(err){
           console.log(err);
           return res.redirect('/post');
       }
        console.log('文章发表成功!')
        return res.redirect('/');
    });
});
//

//博客细节

// app.get('/detail/',authentication.ifAuthorized);//检测是否登录了
app.get('/detail/:_id',function(req,res){//:id?
   console.log('查看笔记!');
    Note.findOne({_id:req.params._id}).exec(function(err,art){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        if(art) {
            res.render('detail', {
                title: '笔记详情',
                user: req.session.user,
                art: art,
                moment:moment
            });
            console.log(art);
        }
    });
});

// app.get('https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx75d11b4f981b1ded&secret=7a915c525f39451f3af19407012459ad&code=CODE&grant_type=authorization_code',function(req,res){

// });//获取appid

app.listen(3000, function(req,res){
    console.log('app is running at port 3000');
});<|endoftext|>


<commit_before>/**
 * Created by rinesnow on 16/3/29.
 */
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var session= require('express-session');
var moment = require('moment');

var app = express();

//连接数据库
var mongoose = require('mongoose');
var models = require('./models/models');

var User = models.User;
var Intern = models.Intern;

mongoose.connect('mongodb://localhost:27017/interns');
mongoose.connection.on('error',console.error.bind(console,'连接数据库失败!'));

//定义EJS模版引擎和末班文件位置
app.set('views', path.join(__dirname,'views'));
app.set('view engine','ejs');

//定义静态文件目录
app.use(express.static(path.join(__dirname,'public')));

//定义数据解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

//建立session模型
app.use(session({
    secret:'liuchong',
    name:'intern',
    //一周内免登录
    cookie:{maxAge:1000 * 60 * 60*24*7},//设置session的保存时间为 60min*24小时*7一周
    resave: false,
    saveUnintiallized:true
}));

//添加  页面提示功能   目前没用
app.use(function(req, res, next) {
    res.locals.user = req.session.user;//???
    var err = req.session.error;//赋值给err变量,
    //var success = req.session.success;//赋值给err变量,

    delete req.session.error;//然后清空session内的数据
    res.locals.message = '';//初始化
    //res.locals.success = '';//初始化

    if (err) {
        //html通过js被操作,在html中用{%-message%}调用
        res.locals.message = '
' + err + '
';
    }
    next();//
});
//添加  页面提示功能   目前没用

var OAuth = require('wechat-oauth');
var client = new OAuth('wx75d11b4f981b1ded', '7a915c525f39451f3af19407012459ad');
app.get('/',function ifAuthorized(req,res){

    // var url = client.getAuthorizeURL('www.coderwitkey.com', 'STATE', 'snsapi_userinfo');
    var query = require('url').parse(req.url,true).query;
          console.log(query);

    if(req.session.code==code){//为了避免程序崩，因为刷新授权会崩
        // res.render('error',{//一个error界面空的
        //      title:'请点左上角的X，重新进入。'
        // });
    }else{
         var code = query.code;
         req.session.code=code;

          console.log(code);
          client.getUserByCode(code,function (err, result) {
      // var openid = result.openid;//必须要手动点击URL，原地刷新没用的。
       console.log('这是result:'+result.openid);
       User.findOne({openid:result.openid},function(err,user){
           if(err){
               console.log('这是err'+err);
           }
            if(user){//有这个user 那么直接跳转
                console.log(user.nickname)
                console.log(user.openid)
                req.session.user = user;
                res.render('login',{
                    user: req.session.user,//也要加?
                    title:'内推推推'
                });
                // return res.redirect('/');
            }
            // //对密码进行md5加密
            // var md5 = crypto.createHash('md5'),
            //     md5newopenid = md5.update(openid).digest('hex');
            //新建user对象用于保存数据
            else{
                var newUser = new User({

                    openid:result.openid,//openid 作为key存入，以后再用用户信息就用openid调。
                    nickname:result.nickname,
                    headimg:result.headimgurl
                });

                newUser.save(function(err,doc){
                    if(err){
                        console.log("保存用户信息失败！"+err);
                    }
                    console.log('保存用户信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                        // user: req.session.user,//也要加?
                        title:'内推推推'
                    });
                    // return res.redirect('/');
                });
            }
        });
    });
    // }

});

//发布

//响应发布get请求
app.get('/post',function(req,res){
   console.log('发布!');
    res.render('post',{
        user: req.session.user,//也要加?
        title:'发布'
    });
});
//响应发布post请求
app.post('/post',function(req,res){

            var newIntern = new Intern({
                openid:req.session.user.openid,//用户的id
                company:req.body.company,
                email:req.body.email,
                category:req.body.category,
                content:req.body.content,
            });

            newIntern.save(function(err,doc){
                if(err){
                    console.log(err);
                    return res.redirect('/post');
                }
                else{
                    console.log('保存实习信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                            // user: req.session.user,//也要加?
                            title:'内推推推'
                    });
                }

            });
});
//

app.get('/list',function(req,res){
     Intern.find(function(err,allInterns){
        if(err){
            console.log(err);
            res.render('login',{
                    // user: req.session.user,//也要加?
              title:'内推推推'
            });
        }
        else{
            res.render('list',{
            title:'内推列表',
            interns:allInterns,
            moment:moment
            });
        }

    })
});

app.get('/detail/:_id',function(req,res){//:id?
   console.log('查看实习!');
    Intern.findOne({_id:req.params._id}).exec(function(err,intern){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        if(intern) {
            res.render('detail', {
                title: '内推详情',
                intern: intern,
                moment:moment
            });
            console.log(intern);
        }
    });
});

app.listen(3000, function(req,res){
    console.log('app is running at port 3000');
});<commit_msg>new version<commit_after>/**
 * Created by rinesnow on 16/3/29.
 */
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var session= require('express-session');
var moment = require('moment');

var app = express();

//连接数据库
var mongoose = require('mongoose');
var models = require('./models/models');

var User = models.User;
var Intern = models.Intern;

mongoose.connect('mongodb://localhost:27017/interns');
mongoose.connection.on('error',console.error.bind(console,'连接数据库失败!'));

//定义EJS模版引擎和末班文件位置
app.set('views', path.join(__dirname,'views'));
app.set('view engine','ejs');

//定义静态文件目录
app.use(express.static(path.join(__dirname,'public')));

//定义数据解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

//建立session模型
app.use(session({
    secret:'liuchong',
    name:'intern',
    //一周内免登录
    cookie:{maxAge:1000 * 60 * 60*24*7},//设置session的保存时间为 60min*24小时*7一周
    resave: false,
    saveUnintiallized:true
}));

//添加  页面提示功能   目前没用
app.use(function(req, res, next) {
    res.locals.user = req.session.user;//???
    var err = req.session.error;//赋值给err变量,
    //var success = req.session.success;//赋值给err变量,

    delete req.session.error;//然后清空session内的数据
    res.locals.message = '';//初始化
    //res.locals.success = '';//初始化

    if (err) {
        //html通过js被操作,在html中用{%-message%}调用
        res.locals.message = '
' + err + '
';
    }
    next();//
});
//添加  页面提示功能   目前没用

var OAuth = require('wechat-oauth');
var client = new OAuth('wx75d11b4f981b1ded', '7a915c525f39451f3af19407012459ad');
app.get('/',function ifAuthorized(req,res){

    // var url = client.getAuthorizeURL('www.coderwitkey.com', 'STATE', 'snsapi_userinfo');
    var query = require('url').parse(req.url,true).query;
          console.log(query);

    if(req.session.code==code){//为了避免程序崩，因为刷新授权会崩
        // res.render('error',{//一个error界面空的
        //      title:'请点左上角的X，重新进入。'
        // });
    }else{
         var code = query.code;
         req.session.code=code;

          console.log(code);
          client.getUserByCode(code,function (err, result) {
      // var openid = result.openid;//必须要手动点击URL，原地刷新没用的。
       console.log('这是result:'+result.openid);
       User.findOne({openid:result.openid},function(err,user){
           if(err){
               console.log('这是err'+err);
           }
            if(user){//有这个user 那么直接跳转
                console.log(user.nickname)
                console.log(user.openid)
                req.session.user = user;
                res.render('login',{
                    user: req.session.user,//也要加?
                    title:'内推推推'
                });
                // return res.redirect('/');
            }
            // //对密码进行md5加密
            // var md5 = crypto.createHash('md5'),
            //     md5newopenid = md5.update(openid).digest('hex');
            //新建user对象用于保存数据
            else{
                var newUser = new User({

                    openid:result.openid,//openid 作为key存入，以后再用用户信息就用openid调。
                    nickname:result.nickname,
                    headimg:result.headimgurl
                });

                newUser.save(function(err,doc){
                    if(err){
                        console.log("保存用户信息失败！"+err);
                    }
                    console.log('保存用户信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                        // user: req.session.user,//也要加?
                        title:'内推推推'
                    });
                    // return res.redirect('/');
                });
            }
        });
    });
  }

});

//发布

//响应发布get请求
app.get('/post',function(req,res){
   console.log('发布!');
    res.render('post',{
        user: req.session.user,//也要加?
        title:'发布'
    });
});
//响应发布post请求
app.post('/post',function(req,res){

            var newIntern = new Intern({
                openid:req.session.user.openid,//用户的id
                company:req.body.company,
                email:req.body.email,
                category:req.body.category,
                content:req.body.content,
            });

            newIntern.save(function(err,doc){
                if(err){
                    console.log(err);
                    return res.redirect('/post');
                }
                else{
                    console.log('保存实习信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                            // user: req.session.user,//也要加?
                            title:'内推推推'
                    });
                }

            });
});
//

app.get('/list',function(req,res){
     Intern.find(function(err,allInterns){
        if(err){
            console.log(err);
            res.render('login',{
                    // user: req.session.user,//也要加?
              title:'内推推推'
            });
        }
        else{
            res.render('list',{
            title:'内推列表',
            interns:allInterns,
            moment:moment
            });
        }

    })
});

app.get('/detail/:_id',function(req,res){//:id?
   console.log('查看实习!');
    Intern.findOne({_id:req.params._id}).exec(function(err,intern){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        if(intern) {
            res.render('detail', {
                title: '内推详情',
                intern: intern,
                moment:moment
            });
            console.log(intern);
        }
    });
});

app.listen(3000, function(req,res){
    console.log('app is running at port 3000');
});<|endoftext|>


<commit_before>const Alexa = require('alexa-sdk');
const Consts = require('../consts.js');
const GameFlow = require('../game/gameFlow.js');

module.exports = Alexa.CreateStateHandler(Consts.GAME_STATES.START, {
    'Start': function (newGame) {
        GameFlow.start(this);
    },
    'AMAZON.YesIntent': function () {
        GameFlow.start(this);
    },

    'AMAZON.HelpIntent': function () {
        this.handler.state = Consts.GAME_STATES.HELP;
        this.emitWithState('helpTheUser', true);
    },
    'Unhandled': function () {
        const speechOutput = `Just play the game`;
        this.emit(':ask', speechOutput, speechOutput);
    },
    'SessionEndedRequest': function () {
        const speechOutput = 'OK, Goodbye!';
        this.emit(':tell', speechOutput);
    }
});
<commit_msg>preliminary handling of resume game<commit_after>const Alexa = require('alexa-sdk');
const Consts = require('../consts.js');
const GameFlow = require('../game/gameFlow.js');

module.exports = Alexa.CreateStateHandler(Consts.GAME_STATES.START, {
    'Start': function (newGame) {
        GameFlow.start(this);
    },
    'AMAZON.YesIntent': function () {
        GameFlow.start(this);
    },

    'PlayerMoveTaxi': function () {
        if (this.attributes["turn"]) {
            this.handler.state = Consts.GAME_STATES.TURN;
            this.emitWithState('PlayerMoveTaxi', true);
        }
        else {
            // game not started
            this.emitWithState('Start', true);
        }
    },
    'PlayerMoveBus': function () {
        this.handler.state = Consts.GAME_STATES.TURN;
        this.emitWithState('PlayerMoveBus', true);
    },
    'PlayerMoveTrain': function () {
        this.handler.state = Consts.GAME_STATES.TURN;
        this.emitWithState('PlayerMoveTrain', true);
    },
    'PlayerMoveAny': function () {
        this.handler.state = Consts.GAME_STATES.TURN;
        this.emitWithState('PlayerMoveAny', true);
    },

    'AMAZON.HelpIntent': function () {
        this.handler.state = Consts.GAME_STATES.HELP;
        this.emitWithState('helpTheUser', true);
    },
    'Unhandled': function () {
        const speechOutput = `Just play the game`;
        this.emit(':ask', speechOutput, speechOutput);
    },
    'SessionEndedRequest': function () {
        const speechOutput = 'OK, Goodbye!';
        this.emit(':tell', speechOutput);
    }
});
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Navbar from './components/Navbar'
import { AppsActions } from './store/apps'
import { Box, Flex, Type } from 'blockstack-ui'
import { Hover } from 'react-powerplug'
import { Spinner } from '@components/ui/components/spinner'

const Loading = ({ ...rest }) => (
  <Flex
    flexDirection="column"
    alignItems="center"
    width={1}
    justifyContent="center"
    p={4}
    {...rest}
  >
    <Box width="48px" mx="auto">
      <Spinner color="black" size={48} />
    </Box>
    <Box py={4} textAlign="center">
      <Type fontWeight="500" fontSize={2} opacity={0.5}>
        Fetching apps...
      </Type>
    </Box>
  </Flex>
)

const Content = ({ topApps, allApps, ...rest }) => (
  <Box maxWidth={1280} width="100%" mx="auto" p={[1, 2, 4]} {...rest}>
    <AppsSection title="Popular Apps" apps={topApps} limit={24} />
    {allApps
      .sort((a, b) => a.label.localeCompare(b.label))
      .map(category => {
        const apps = category.apps.sort((a, b) => a.name.localeCompare(b.name))
        return <AppsSection title={category.label} apps={apps} />
      })}
  </Box>
)

const AppsSection = ({ title, apps, limit, category, ...rest }) => {
  let appsList = apps.filter(app => app.imgixImageUrl)
  if (limit) {
    appsList = appsList.filter((app, i) => i <= limit - 1)
  }
  if (category) {
    appsList = appsList.filter(app => app.category === category)
  }
  return (
    <Box
      width={1}
      mx="auto"
      p={4}
      mb={4}
      borderBottom="1px solid rgba(15,15,15,0.1)"
      {...rest}
    >
      <Box textAlign="center">

{title}

      </Box>
      <Flex
        pt={4}
        justifyContent={['space-between', 'center']}
        alignItems="flex-start"
        flexWrap="wrap"
      >
        {appsList.map(app => (
          <AppItem
            key={app.name}
            name={app.name}
            imgixImageUrl={app.imgixImageUrl}
            website={app.website}
            description={app.description}
          />
        ))}
      </Flex>
    </Box>
  )
}

const Image = props => (
  <Box
    is="img"
    borderRadius="15px"
    boxShadow="0px 0px 35px 0px rgba(0, 0, 0, 0.15)"
    bg="white"
    {...props}
  />
)

const AppItem = ({ website, imgixImageUrl, name, description }) => {
  const descMaxLength = 55
  let desc = description.trim()

  if (description[description.length - 1] !== '.') {
    desc += '.'
  }

  if (description.length > descMaxLength) {
    desc = <>{desc.substring(0, descMaxLength).trim()}…</>
  }

  return (
    <Hover>
      {({ bind, hovered }) => (
        <Box
          width={['calc(50% - 10px)', 1 / 3, 1 / 3, 1 / 4]}
          maxWidth={['160px', 'unset', 'unset', 'unset']}
          pr={[0, 3, 3, 4]}
          flexShrink={0}
          mb={[5]}
          transition="0.15s all ease-in-out"
          transform={hovered ? 'translateY(-5px)' : 'none'}
          color="blue.dark"
          {...bind}
        >
          <Flex
            maxWidth="100%"
            width={1}
            is="a"
            href={website}
            target="_blank"
            alignItems="center"
            flexDirection={['column', 'row', 'row', 'row']}
            textAlign={['center', 'left', 'left', 'left']}
            style={{
              textDecoration: 'none',
              color: 'black !important'
            }}
          >
            <Image
              maxWidth="100%"
              size="64px"
              display="block"
              flexShrink="0"
              src={imgixImageUrl}
              alt={name}
            />
            <Box ml={[0, 3, 3, 3]} mt={[3, 0, 0, 0]}>
              <Box>
                <Type
                  is="h3"
                  fontSize={[2]}
                  lineHeight={1.45}
                  fontWeight="600"
                  color="black !important"
                  mb={1}
                >
                  {name}
                </Type>
              </Box>
              <Box
                opacity={0.75}
                color="black !important"
                style={{
                  hyphens: 'auto'
                }}
              >
                {desc}
              </Box>
            </Box>
          </Flex>
        </Box>
      )}
    </Hover>
  )
}

const HomeScreenPage = props => {
  const loading = props.apps && props.apps.loading && !props.apps.topApps.length
  return (
    <Box>
      <Navbar hideBackToHomeLink activeTab="home" />
      <Box className="home-screen">
        {loading ? (
          <Loading />
        ) : (
          <Content
            allApps={!loading && props.apps.appsByCategory}
            topApps={!loading && props.apps.topApps}
          />
        )}
      </Box>
    </Box>
  )
}

Content.propTypes = {
  topApps: PropTypes.array.isRequired,
  allApps: PropTypes.array.isRequired
}

AppsSection.propTypes = {
  title: PropTypes.string.isRequired,
  apps: PropTypes.array.isRequired,
  limit: PropTypes.number.isRequired,
  category: PropTypes.string.isRequired
}
AppItem.propTypes = {
  website: PropTypes.string.isRequired,
  imgixImageUrl: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired
}

HomeScreenPage.propTypes = {
  apps: PropTypes.object.isRequired,
  refreshAppList: PropTypes.func.isRequired,
  doFetchApps: PropTypes.func.isRequired,
  appListLastUpdated: PropTypes.number,
  api: PropTypes.object.isRequired,
  instanceIdentifier: PropTypes.string,
  instanceCreationDate: PropTypes.number
}

const mapStateToProps = state => ({
  apps: state.apps,
  appListLastUpdated: state.apps.lastUpdated,
  api: state.settings.api,
  instanceIdentifier: state.apps.instanceIdentifier,
  instanceCreationDate: state.apps.instanceCreationDate
})

const mapDispatchToProps = dispatch =>
  bindActionCreators({ ...AppsActions }, dispatch)

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(HomeScreenPage)
<commit_msg>drop shadows and text alignments<commit_after>import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Navbar from './components/Navbar'
import { AppsActions } from './store/apps'
import { Box, Flex, Type } from 'blockstack-ui'
import { Hover } from 'react-powerplug'
import { Spinner } from '@components/ui/components/spinner'

const Loading = ({ ...rest }) => (
  <Flex
    flexDirection="column"
    alignItems="center"
    width={1}
    justifyContent="center"
    p={4}
    {...rest}
  >
    <Box width="48px" mx="auto">
      <Spinner color="black" size={48} />
    </Box>
    <Box py={4} textAlign="center">
      <Type fontWeight="500" fontSize={2} opacity={0.5}>
        Fetching apps...
      </Type>
    </Box>
  </Flex>
)

const Content = ({ topApps, allApps, ...rest }) => (
  <Box maxWidth={1280} width="100%" mx="auto" p={[1, 2, 4]} {...rest}>
    <AppsSection title="Popular Apps" apps={topApps} limit={24} />
    {allApps
      .sort((a, b) => a.label.localeCompare(b.label))
      .map(category => {
        const apps = category.apps.sort((a, b) => a.name.localeCompare(b.name))
        return <AppsSection title={category.label} apps={apps} />
      })}
  </Box>
)

const AppsSection = ({ title, apps, limit, category, ...rest }) => {
  let appsList = apps.filter(app => app.imgixImageUrl)
  if (limit) {
    appsList = appsList.filter((app, i) => i <= limit - 1)
  }
  if (category) {
    appsList = appsList.filter(app => app.category === category)
  }
  return (
    <Box
      width={1}
      mx="auto"
      p={4}
      mb={4}
      borderBottom="1px solid rgba(15,15,15,0.1)"
      {...rest}
    >
      <Box textAlign="center">

{title}

      </Box>
      <Flex pt={4} flexWrap="wrap" justifyContent={['space-between', 'center']}>
        {appsList.map(app => (
          <AppItem
            key={app.name}
            name={app.name}
            imgixImageUrl={app.imgixImageUrl}
            website={app.website}
            description={app.description}
          />
        ))}
      </Flex>
    </Box>
  )
}

const Image = props => (
  <Box
    is="img"
    borderRadius="15px"
    boxShadow="1px 0px 18px 0px rgba(0, 0, 0, 0.1)"
    bg="white"
    {...props}
  />
)

const AppItem = ({ website, imgixImageUrl, name, description }) => {
  const descMaxLength = 55
  let desc = description.trim()

  if (description[description.length - 1] !== '.') {
    desc += '.'
  }

  if (description.length > descMaxLength) {
    desc = <>{desc.substring(0, descMaxLength).trim()}…</>
  }

  return (
    <Hover>
      {({ bind, hovered }) => (
        <Flex
          width={['calc(50% - 10px)', 1 / 3, 1 / 3, 1 / 4]}
          maxWidth={['160px', 'unset', 'unset', 'unset']}
          pr={[0, 3, 3, 4]}
          mb={5}
          flexShrink={0}
          flexGrow="1"
          alignSelf="stretch"
          transition="0.15s all ease-in-out"
          transform={hovered ? 'translateY(-5px)' : 'none'}
          color="blue.dark"
          {...bind}
        >
          <Flex
            maxWidth="100%"
            width={1}
            is="a"
            flexGrow={1}
            href={website}
            target="_blank"
            alignItems="center"
            flexDirection={['column', 'row', 'row', 'row']}
            textAlign={['center', 'left', 'left', 'left']}
            style={{
              textDecoration: 'none',
              color: 'black !important'
            }}
          >
            <Image
              maxWidth="100%"
              size="64px"
              display="block"
              flexShrink="0"
              src={imgixImageUrl}
              alt={name}
            />
            <Box ml={[0, 3, 3, 3]} mt={[3, 0, 0, 0]}>
              <Box>
                <Type
                  is="h3"
                  fontSize={[2]}
                  lineHeight={1.45}
                  fontWeight="600"
                  color="black !important"
                  mb={1}
                >
                  {name}
                </Type>
              </Box>
              <Box
                opacity={0.75}
                color="black !important"
                style={{
                  hyphens: 'auto'
                }}
              >
                {desc}
              </Box>
            </Box>
          </Flex>
        </Flex>
      )}
    </Hover>
  )
}

const HomeScreenPage = props => {
  const loading = props.apps && props.apps.loading && !props.apps.topApps.length
  return (
    <Box>
      <Navbar hideBackToHomeLink activeTab="home" />
      <Box className="home-screen">
        {loading ? (
          <Loading />
        ) : (
          <Content
            allApps={!loading && props.apps.appsByCategory}
            topApps={!loading && props.apps.topApps}
          />
        )}
      </Box>
    </Box>
  )
}

Content.propTypes = {
  topApps: PropTypes.array.isRequired,
  allApps: PropTypes.array.isRequired
}

AppsSection.propTypes = {
  title: PropTypes.string.isRequired,
  apps: PropTypes.array.isRequired,
  limit: PropTypes.number.isRequired,
  category: PropTypes.string.isRequired
}
AppItem.propTypes = {
  website: PropTypes.string.isRequired,
  imgixImageUrl: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired
}

HomeScreenPage.propTypes = {
  apps: PropTypes.object.isRequired,
  refreshAppList: PropTypes.func.isRequired,
  doFetchApps: PropTypes.func.isRequired,
  appListLastUpdated: PropTypes.number,
  api: PropTypes.object.isRequired,
  instanceIdentifier: PropTypes.string,
  instanceCreationDate: PropTypes.number
}

const mapStateToProps = state => ({
  apps: state.apps,
  appListLastUpdated: state.apps.lastUpdated,
  api: state.settings.api,
  instanceIdentifier: state.apps.instanceIdentifier,
  instanceCreationDate: state.apps.instanceCreationDate
})

const mapDispatchToProps = dispatch =>
  bindActionCreators({ ...AppsActions }, dispatch)

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


<commit_before>export const APP_HEADER_ITEMS = [
  {
    id: 'data',
    label: 'Data',
    route: 'data',
    pathnames: ['/app/explore', '/app/explore-detail', '/app/pulse'],
    children: [
      { label: 'Explore Datasets', route: 'explore' },
      { label: 'Planet Pulse', href: '/data/pulse' },
      {
        label: 'App Gallery',
        route: 'get_involved_detail',
        params: { id: 'apps' },
        logEvent: true
      }
    ]
  },
  {
    id: 'dashboards',
    label: 'Dashboards',
    route: 'dashboards',
    pathnames: ['/app/About', '/app/Partners'],
    children: [
      { label: 'Cities', route: 'dashboards_detail', params: { id: 'cities' } },
      { label: 'Climate', route: 'dashboards_detail', params: { id: 'climate' } },
      { label: 'Energy', route: 'dashboards_detail', params: { id: 'energy' } },
      { label: 'Food', route: 'dashboards_detail', params: { id: 'food' } },
      { label: 'Forests', route: 'dashboards_detail', params: { id: 'forests' } },
      { label: 'Society', route: 'dashboards_detail', params: { id: 'society' } },
      { label: 'Oceans', route: 'dashboards_detail', params: { id: 'oceans' } },
      { label: 'Water', route: 'dashboards_detail', params: { id: 'water' } }
    ]
  },
  {
    id: 'blog',
    label: 'Blog',
    href: 'https://blog.resourcewatch.org'
  },
  {
    id: 'about',
    label: 'About',
    route: 'about',
    pathnames: ['/app/About', '/app/Partners'],
    children: [
      { label: 'Partners', route: 'about_partners' },
      { label: 'FAQs', route: 'about_faqs' },
      { label: 'How to', route: 'about_howto' },
      { label: 'Contact us', route: 'about_contact-us' }
    ]
  },
  {
    id: 'get_involved',
    label: 'Get Involved',
    route: 'get_involved',
    pathnames: ['/app/get-involved', '/app/get-involved-detail'],
    children: [
      {
        label: 'Suggest a story',
        route: 'get_involved_detail',
        params: { id: 'suggest-a-story' }
      },
      {
        label: 'Contribute data',
        route: 'get_involved_detail',
        params: { id: 'contribute-data' }
      },
      {
        label: 'Join the community',
        route: 'get_involved_detail',
        params: { id: 'join-the-community' }
      },
      {
        label: 'Develop your app',
        route: 'get_involved_detail',
        params: { id: 'develop-your-app' }
      },
      {
        label: 'Sign up',
        route: 'sign-in',
        params: {}
      }
    ]
  },
  {
    id: 'search',
    label: 'Search'
  },
  {
    user: false,
    id: 'myrw',
    label: 'Log in'
  },
  {
    user: true,
    id: 'myrw',
    route: 'myrw',
    label: 'My Resource Watch',
    children: [
      { label: 'Profile', route: 'myrw', params: { tab: 'profile' } },
      { label: 'Admin', route: 'admin_home', admin: true },
      { label: 'Logout', id: 'logout' }
    ]
  }
];

export default { APP_HEADER_ITEMS };
<commit_msg>header-menu dashboards pathname fix<commit_after>export const APP_HEADER_ITEMS = [
  {
    id: 'data',
    label: 'Data',
    route: 'data',
    pathnames: ['/app/explore', '/app/explore-detail', '/app/pulse'],
    children: [
      { label: 'Explore Datasets', route: 'explore' },
      { label: 'Planet Pulse', href: '/data/pulse' },
      {
        label: 'App Gallery',
        route: 'get_involved_detail',
        params: { id: 'apps' },
        logEvent: true
      }
    ]
  },
  {
    id: 'dashboards',
    label: 'Dashboards',
    route: 'dashboards',
    pathnames: ['/app/dashboards', '/app/dashboards-detail'],
    children: [
      { label: 'Cities', route: 'dashboards_detail', params: { id: 'cities' } },
      { label: 'Climate', route: 'dashboards_detail', params: { id: 'climate' } },
      { label: 'Energy', route: 'dashboards_detail', params: { id: 'energy' } },
      { label: 'Food', route: 'dashboards_detail', params: { id: 'food' } },
      { label: 'Forests', route: 'dashboards_detail', params: { id: 'forests' } },
      { label: 'Society', route: 'dashboards_detail', params: { id: 'society' } },
      { label: 'Oceans', route: 'dashboards_detail', params: { id: 'oceans' } },
      { label: 'Water', route: 'dashboards_detail', params: { id: 'water' } }
    ]
  },
  {
    id: 'blog',
    label: 'Blog',
    href: 'https://blog.resourcewatch.org'
  },
  {
    id: 'about',
    label: 'About',
    route: 'about',
    pathnames: ['/app/About', '/app/Partners'],
    children: [
      { label: 'Partners', route: 'about_partners' },
      { label: 'FAQs', route: 'about_faqs' },
      { label: 'How to', route: 'about_howto' },
      { label: 'Contact us', route: 'about_contact-us' }
    ]
  },
  {
    id: 'get_involved',
    label: 'Get Involved',
    route: 'get_involved',
    pathnames: ['/app/get-involved', '/app/get-involved-detail'],
    children: [
      {
        label: 'Suggest a story',
        route: 'get_involved_detail',
        params: { id: 'suggest-a-story' }
      },
      {
        label: 'Contribute data',
        route: 'get_involved_detail',
        params: { id: 'contribute-data' }
      },
      {
        label: 'Join the community',
        route: 'get_involved_detail',
        params: { id: 'join-the-community' }
      },
      {
        label: 'Develop your app',
        route: 'get_involved_detail',
        params: { id: 'develop-your-app' }
      },
      {
        label: 'Sign up',
        route: 'sign-in',
        params: {}
      }
    ]
  },
  {
    id: 'search',
    label: 'Search'
  },
  {
    user: false,
    id: 'myrw',
    label: 'Log in'
  },
  {
    user: true,
    id: 'myrw',
    route: 'myrw',
    label: 'My Resource Watch',
    children: [
      { label: 'Profile', route: 'myrw', params: { tab: 'profile' } },
      { label: 'Admin', route: 'admin_home', admin: true },
      { label: 'Logout', id: 'logout' }
    ]
  }
];

export default { APP_HEADER_ITEMS };
<|endoftext|>