<commit_before>'use strict';

angular.module('umm3601ursamajorApp')
  .factory('Modal', function ($rootScope, $modal) {
    /**
     * Opens a modal
     * @param  {Object} scope      - an object to be merged with modal's scope
     * @param  {String} modalClass - (optional) class(es) to be applied to the modal
     * @return {Object}            - the instance $modal.open() returns
     */
    function openModal(scope, modalClass) {
        console.log("making new modal");
        console.log(modalClass);
      var modalScope = $rootScope.$new();
      scope = scope || {};
      modalClass = modalClass || 'modal-default';

      angular.extend(modalScope, scope);

      return $modal.open({
        templateUrl: 'components/modal/modal.html',
        windowClass: modalClass,
        scope: modalScope
      });
    }

    // Public API here
    return {

      /* Confirmation modals */
      confirm: {

          /**
           * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
           * @param  {Function} del - callback, ran when delete is confirmed
           * @return {Function}     - the function to open the modal (ex. myModalFn)
           */
          reject: function(del) {
              del = del || angular.noop;

              /**
               * Open a delete confirmation modal
               * @param  {String} name   - name or info to show on modal
               * @param  {All}           - any additional args are passed staight to del callback
               */
              return function() {
                  var args = Array.prototype.slice.call(arguments),
                      name = args.shift(),
                      rejectModal;

                  rejectModal = openModal({
                      modal: {
                          dismissable: true,
                          title: 'Confirm Submission Rejection',
                          html: '
Are you sure you want to reject ' + name + ' ?
',
                          buttons: [{
                              classes: 'btn-danger',
                              text: 'Confirm',
                              click: function(e) {
                                  rejectModal.close(e);
                              }
                          }, {
                              classes: 'btn-default',
                              text: 'Cancel',
                              click: function(e) {
                                  rejectModal.dismiss(e);
                              }
                          }]
                      }
                  }, 'modal-danger');

                  rejectModal.result.then(function(event) {
                      del.apply(event, args);
                  });
              };
          },

        /**
         * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
         * @param  {Function} del - callback, ran when delete is confirmed
         * @return {Function}     - the function to open the modal (ex. myModalFn)
         */
        delete: function(del) {
          del = del || angular.noop;

          /**
           * Open a delete confirmation modal
           * @param  {String} name   - name or info to show on modal
           * @param  {All}           - any additional args are passed staight to del callback
           */
            return function() {
                var args = Array.prototype.slice.call(arguments),
                    name = args.shift(),
                    deleteModal;

                deleteModal = openModal({
                    modal: {
                        dismissable: true,
                        title: 'Confirm Delete',
                        html: '
Are you sure you want to delete ' + name + ' ?
',
                        buttons: [{
                            classes: 'btn-danger',
                            text: 'Delete',
                            click: function(e) {
                                deleteModal.close(e);
                            }
                        }, {
                            classes: 'btn-default',
                            text: 'Cancel',
                            click: function(e) {
                                deleteModal.dismiss(e);
                            }
                        }]
                    }
                }, 'modal-danger');

                deleteModal.result.then(function(event) {
                    del.apply(event, args);
                });
            };
        },

        info: function(callback) {
            callback = callback || angular.noop;

            /**
             * Open an info confirmation modal
             * @param {String} content - text for the confirmation modal
             * @param {All}         - Any additional arguments are passed to the callback
             */
            return function() {
                var args = Array.prototype.slice.call(arguments),
                    content = args.shift(),
                    infoModal;

                infoModal = openModal({
                    modal: {
                        dismissable: true,
                        title: "Confirm",
                        html: '
' + content + '
',
                        buttons: [{
                            classes: 'btn-info',
                            text: 'OK',
                            click: function(e) {
                                infoModal.close(e);
                            }
                        }, {
                            classes:"btn-default",
                            text: "Cancel",
                            click: function(e) {
                                infoModal.dismiss(e);
                            }
                        }]
                    }
                }, 'modal-info');

                infoModal.result.then(function(event) {
                   callback.apply(event, args);
                });
            }
        },

          warning: function(callback){
              callback = callback || angular.noop;
              /**
               * Open an info confirmation modal
               * @param {String} content - text for the confirmation modal
               * @param {All}         - Any additional arguments are passed to the callback
               */
              return function() {
                  var args = Array.prototype.slice.call(arguments),
                      content = args.shift(),
                      warningModal;

                  warningModal = openModal({
                      modal: {
                          dismissable: true,
                          title: "Warning!",
                          html: '
' + content + '
',
                          buttons: [{
                              classes:"btn-default",
                              text: "OK",
                              click: function(e) {
                                  warningModal.dismiss(e);
                              }
                          }]
                      }
                  }, 'modal-warning');

                  warningModal.result.then(function(event) {
                      callback.apply(event, args);
                  });
              }
          },

          /** SAVING THIS FOR LATER, FORESEEABLE USE IN THE FUTURE
           *
           */
//          option: function(callbackYes, callbackNo){
//              callbackYes = callbackYes || angular.noop;
//              callbackNo = callbackNo || angular.noop;
//              /**
//               * Open an info confirmation modal
//               * @param {String} content - text for the confirmation modal
//               * @param {All}         - Any additional arguments are passed to the callback
//               */
//              return function() {
//                  var args = Array.prototype.slice.call(arguments),
//                      content = args.shift(),
//                      optionModal;
//                  var clicked = "?";
//
//                  optionModal = openModal({
//                      modal: {
//
//                          dismissable: true,
//                          title: "Confirm",
//                          html: '
' + content + '
',
//                          buttons: [{
//                              classes: 'btn-info',
//                              text: 'Yes',
//                              click: function(e) {
//                                  clicked = "yes";
//                                  optionModal.close(e);
//                              }
//                          }, {
//                              classes: 'btn-default',
//                              text: 'No',
//                              click: function(e) {
//                                  clicked = "no";
//                                  optionModal.close(e);
//                              }
//                          }, {
//                              classes:"btn-default",
//                              text: "Cancel",
//                              click: function(e) {
//                                  optionModal.dismiss(e);
//                              }
//                          }]
//                      }
//                  }, 'modal-info');
//
//                  optionModal.result.then(function(event) {
//                      if(clicked == "yes"){
//                          callbackYes.apply(event, args);
//                      } else {
//                          callbackNo.apply(event, args);
//                      }
//
//                  });
//              }
//          },

          approval: function(callbackYes, callbackNo){
              callbackYes = callbackYes || angular.noop;
              callbackNo = callbackNo || angular.noop;
              /**
               * Open an info confirmation modal
               * @param {String} content - text for the confirmation modal
               * @param {All}         - Any additional arguments are passed to the callback
               */
              return function() {
                  var args = Array.prototype.slice.call(arguments),
                      content = args.shift(),
                      approvalModal;
                  var clicked = "?";

                  approvalModal = openModal({
                      modal: {

                          dismissable: true,
                          title: "Confirm Approval",
                          html: '
' + content + '
',
                          buttons: [{
                              classes: 'btn-success',
                              text: 'Yes',
                              click: function(e) {
                                  clicked = "yes";
                                  approvalModal.close(e);
                              }
                          }, {
                              classes: 'btn-default',
                              text: 'No',
                              click: function(e) {
                                  clicked = "no";
                                  approvalModal.close(e);
                              }
                          }, {
                              classes:"btn-default",
                              text: "Cancel",
                              click: function(e) {
                                  approvalModal.dismiss(e);
                              }
                          }]
                      }
                  }, 'modal-success');

                  approvalModal.result.then(function(event) {
                      if(clicked == "yes"){
                          callbackYes.apply(event, args);
                      } else {
                          callbackNo.apply(event, args);
                      }

                  });
              }
          }

      }
    };
  });
<commit_msg>brought back the option modal<commit_after>'use strict';

angular.module('umm3601ursamajorApp')
  .factory('Modal', function ($rootScope, $modal) {
    /**
     * Opens a modal
     * @param  {Object} scope      - an object to be merged with modal's scope
     * @param  {String} modalClass - (optional) class(es) to be applied to the modal
     * @return {Object}            - the instance $modal.open() returns
     */
    function openModal(scope, modalClass) {
        console.log("making new modal");
        console.log(modalClass);
      var modalScope = $rootScope.$new();
      scope = scope || {};
      modalClass = modalClass || 'modal-default';

      angular.extend(modalScope, scope);

      return $modal.open({
        templateUrl: 'components/modal/modal.html',
        windowClass: modalClass,
        scope: modalScope
      });
    }

    // Public API here
    return {

      /* Confirmation modals */
      confirm: {

          /**
           * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
           * @param  {Function} del - callback, ran when delete is confirmed
           * @return {Function}     - the function to open the modal (ex. myModalFn)
           */
          reject: function(del) {
              del = del || angular.noop;

              /**
               * Open a delete confirmation modal
               * @param  {String} name   - name or info to show on modal
               * @param  {All}           - any additional args are passed staight to del callback
               */
              return function() {
                  var args = Array.prototype.slice.call(arguments),
                      name = args.shift(),
                      rejectModal;

                  rejectModal = openModal({
                      modal: {
                          dismissable: true,
                          title: 'Confirm Submission Rejection',
                          html: '
Are you sure you want to reject ' + name + ' ?
',
                          buttons: [{
                              classes: 'btn-danger',
                              text: 'Confirm',
                              click: function(e) {
                                  rejectModal.close(e);
                              }
                          }, {
                              classes: 'btn-default',
                              text: 'Cancel',
                              click: function(e) {
                                  rejectModal.dismiss(e);
                              }
                          }]
                      }
                  }, 'modal-danger');

                  rejectModal.result.then(function(event) {
                      del.apply(event, args);
                  });
              };
          },

        /**
         * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
         * @param  {Function} del - callback, ran when delete is confirmed
         * @return {Function}     - the function to open the modal (ex. myModalFn)
         */
        delete: function(del) {
          del = del || angular.noop;

          /**
           * Open a delete confirmation modal
           * @param  {String} name   - name or info to show on modal
           * @param  {All}           - any additional args are passed staight to del callback
           */
            return function() {
                var args = Array.prototype.slice.call(arguments),
                    name = args.shift(),
                    deleteModal;

                deleteModal = openModal({
                    modal: {
                        dismissable: true,
                        title: 'Confirm Delete',
                        html: '
Are you sure you want to delete ' + name + ' ?
',
                        buttons: [{
                            classes: 'btn-danger',
                            text: 'Delete',
                            click: function(e) {
                                deleteModal.close(e);
                            }
                        }, {
                            classes: 'btn-default',
                            text: 'Cancel',
                            click: function(e) {
                                deleteModal.dismiss(e);
                            }
                        }]
                    }
                }, 'modal-danger');

                deleteModal.result.then(function(event) {
                    del.apply(event, args);
                });
            };
        },

        info: function(callback) {
            callback = callback || angular.noop;

            /**
             * Open an info confirmation modal
             * @param {String} content - text for the confirmation modal
             * @param {All}         - Any additional arguments are passed to the callback
             */
            return function() {
                var args = Array.prototype.slice.call(arguments),
                    content = args.shift(),
                    infoModal;

                infoModal = openModal({
                    modal: {
                        dismissable: true,
                        title: "Confirm",
                        html: '
' + content + '
',
                        buttons: [{
                            classes: 'btn-info',
                            text: 'OK',
                            click: function(e) {
                                infoModal.close(e);
                            }
                        }, {
                            classes:"btn-default",
                            text: "Cancel",
                            click: function(e) {
                                infoModal.dismiss(e);
                            }
                        }]
                    }
                }, 'modal-info');

                infoModal.result.then(function(event) {
                   callback.apply(event, args);
                });
            }
        },

          warning: function(callback){
              callback = callback || angular.noop;
              /**
               * Open an info confirmation modal
               * @param {String} content - text for the confirmation modal
               * @param {All}         - Any additional arguments are passed to the callback
               */
              return function() {
                  var args = Array.prototype.slice.call(arguments),
                      content = args.shift(),
                      warningModal;

                  warningModal = openModal({
                      modal: {
                          dismissable: true,
                          title: "Warning!",
                          html: '
' + content + '
',
                          buttons: [{
                              classes:"btn-default",
                              text: "OK",
                              click: function(e) {
                                  warningModal.dismiss(e);
                              }
                          }]
                      }
                  }, 'modal-warning');

                  warningModal.result.then(function(event) {
                      callback.apply(event, args);
                  });
              }
          },

          option: function(callbackYes, callbackNo){
              callbackYes = callbackYes || angular.noop;
              callbackNo = callbackNo || angular.noop;
              /**
               * Open an info confirmation modal
               * @param {String} content - text for the confirmation modal
               * @param {All}         - Any additional arguments are passed to the callback
               */
              return function() {
                  var args = Array.prototype.slice.call(arguments),
                      content = args.shift(),
                      optionModal;
                  var clicked = "?";

                  optionModal = openModal({
                      modal: {

                          dismissable: true,
                          title: "Confirm",
                          html: '
' + content + '
',
                          buttons: [{
                              classes: 'btn-info',
                              text: 'Yes',
                              click: function(e) {
                                  clicked = "yes";
                                  optionModal.close(e);
                              }
                          }, {
                              classes: 'btn-default',
                              text: 'No',
                              click: function(e) {
                                  clicked = "no";
                                  optionModal.close(e);
                              }
                          }, {
                              classes:"btn-default",
                              text: "Cancel",
                              click: function(e) {
                                  optionModal.dismiss(e);
                              }
                          }]
                      }
                  }, 'modal-info');

                  optionModal.result.then(function(event) {
                      if(clicked == "yes"){
                          callbackYes.apply(event, args);
                      } else {
                          callbackNo.apply(event, args);
                      }

                  });
              }
          },

          approval: function(callbackYes, callbackNo){
              callbackYes = callbackYes || angular.noop;
              callbackNo = callbackNo || angular.noop;
              /**
               * Open an info confirmation modal
               * @param {String} content - text for the confirmation modal
               * @param {All}         - Any additional arguments are passed to the callback
               */
              return function() {
                  var args = Array.prototype.slice.call(arguments),
                      content = args.shift(),
                      approvalModal;
                  var clicked = "?";

                  approvalModal = openModal({
                      modal: {

                          dismissable: true,
                          title: "Confirm Approval",
                          html: '
' + content + '
',
                          buttons: [{
                              classes: 'btn-success',
                              text: 'Yes',
                              click: function(e) {
                                  clicked = "yes";
                                  approvalModal.close(e);
                              }
                          }, {
                              classes: 'btn-default',
                              text: 'No',
                              click: function(e) {
                                  clicked = "no";
                                  approvalModal.close(e);
                              }
                          }, {
                              classes:"btn-default",
                              text: "Cancel",
                              click: function(e) {
                                  approvalModal.dismiss(e);
                              }
                          }]
                      }
                  }, 'modal-success');

                  approvalModal.result.then(function(event) {
                      if(clicked == "yes"){
                          callbackYes.apply(event, args);
                      } else {
                          callbackNo.apply(event, args);
                      }

                  });
              }
          }

      }
    };
  });
<|endoftext|>


<commit_before>const getDefaultConfig = (randomizer, window) => {
    const defaultClickTypes = [
        'click',
        'click',
        'click',
        'click',
        'click',
        'click',
        'dblclick',
        'dblclick',
        'mousedown',
        'mouseup',
        'mouseover',
        'mouseover',
        'mouseover',
        'mousemove',
        'mouseout',
    ];

    const defaultPositionSelector = () => {
        return [
            randomizer.natural({
                max: Math.max(0, window.document.documentElement.clientWidth - 1),
            }),
            randomizer.natural({
                max: Math.max(0, window.document.documentElement.clientHeight - 1),
            }),
        ];
    };

    const defaultShowAction = (x, y) => {
        const document = window.document;
        const body = document.body;
        const clickSignal = document.createElement('div');
        clickSignal.style.zIndex = 2000;
        clickSignal.style.border = '3px solid red';
        // todo clean chrome/mozilla hack
        clickSignal.style['border-radius'] = '50%'; // Chrome
        clickSignal.style.borderRadius = '50%'; // Mozilla
        clickSignal.style.width = '40px';
        clickSignal.style.height = '40px';
        clickSignal.style['box-sizing'] = 'border-box';
        clickSignal.style.position = 'absolute';
        clickSignal.style.webkitTransition = 'opacity 1s ease-out';
        clickSignal.style.mozTransition = 'opacity 1s ease-out';
        clickSignal.style.transition = 'opacity 1s ease-out';
        clickSignal.style.left = x - 20 + 'px';
        clickSignal.style.top = y - 20 + 'px';
        const element = body.appendChild(clickSignal);
        setTimeout(() => {
            body.removeChild(element);
        }, 1000);
        setTimeout(() => {
            element.style.opacity = 0;
        }, 50);
    };

    const defaultCanClick = () => {
        return true;
    };
    return {
        clickTypes: defaultClickTypes,
        positionSelector: defaultPositionSelector,
        showAction: defaultShowAction,
        canClick: defaultCanClick,
        maxNbTries: 10,
        log: false,
    };
};

export default (userConfig) => ({ logger, randomizer, window }) => {
    const config = {
        ...getDefaultConfig(randomizer, window),
        ...userConfig,
    };

    return () => {
        let position;
        let posX;
        let posY;
        let targetElement;
        let nbTries = 0;

        do {
            position = config.positionSelector();
            posX = position[0];
            posY = position[1];
            targetElement = window.document.elementFromPoint(posX, posY);
            nbTries++;
            if (nbTries > config.maxNbTries) return;
        } while (!targetElement || !config.canClick(targetElement));

        const evt = window.document.createEvent('MouseEvents');
        const clickType = randomizer.pick(config.clickTypes);
        // todo remove deprecated https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent
        evt.initMouseEvent(clickType, true, true, window, 0, 0, 0, posX, posY, false, false, false, false, 0, null);
        targetElement.dispatchEvent(evt);

        if (typeof config.showAction === 'function') {
            config.showAction(posX, posY, clickType);
        }

        if (logger && config.log) {
            logger.log('gremlin', 'clicker   ', clickType, 'at', posX, posY);
        }
    };
};
<commit_msg>fix: clicker gremlin in Cypress<commit_after>const getDefaultConfig = (randomizer, window) => {
    const document = window.document;
    const body = document.body;

    const defaultClickTypes = [
        'click',
        'click',
        'click',
        'click',
        'click',
        'click',
        'dblclick',
        'dblclick',
        'mousedown',
        'mouseup',
        'mouseover',
        'mouseover',
        'mouseover',
        'mousemove',
        'mouseout',
    ];

    const defaultPositionSelector = () => {
        return [
            randomizer.natural({
                max: Math.max(0, document.documentElement.clientWidth - 1),
            }),
            randomizer.natural({
                max: Math.max(0, document.documentElement.clientHeight - 1),
            }),
        ];
    };

    const defaultShowAction = (x, y) => {
        const clickSignal = document.createElement('div');
        clickSignal.style.zIndex = 2000;
        clickSignal.style.border = '3px solid red';
        // todo clean chrome/mozilla hack
        clickSignal.style['border-radius'] = '50%'; // Chrome
        clickSignal.style.borderRadius = '50%'; // Mozilla
        clickSignal.style.width = '40px';
        clickSignal.style.height = '40px';
        clickSignal.style['box-sizing'] = 'border-box';
        clickSignal.style.position = 'absolute';
        clickSignal.style.webkitTransition = 'opacity 1s ease-out';
        clickSignal.style.mozTransition = 'opacity 1s ease-out';
        clickSignal.style.transition = 'opacity 1s ease-out';
        clickSignal.style.left = x - 20 + 'px';
        clickSignal.style.top = y - 20 + 'px';
        const element = body.appendChild(clickSignal);
        setTimeout(() => {
            body.removeChild(element);
        }, 1000);
        setTimeout(() => {
            element.style.opacity = 0;
        }, 50);
    };

    const defaultCanClick = () => {
        return true;
    };
    return {
        clickTypes: defaultClickTypes,
        positionSelector: defaultPositionSelector,
        showAction: defaultShowAction,
        canClick: defaultCanClick,
        maxNbTries: 10,
        log: false,
    };
};

export default (userConfig) => ({ logger, randomizer, window }) => {
    const document = window.document;
    const config = {
        ...getDefaultConfig(randomizer, window),
        ...userConfig,
    };

    return () => {
        let position;
        let posX;
        let posY;
        let targetElement;
        let nbTries = 0;

        do {
            position = config.positionSelector();
            posX = position[0];
            posY = position[1];
            targetElement = document.elementFromPoint(posX, posY);
            nbTries++;
            if (nbTries > config.maxNbTries) return;
        } while (!targetElement || !config.canClick(targetElement));

        const evt = document.createEvent('MouseEvents');
        const clickType = randomizer.pick(config.clickTypes);
        // todo remove deprecated https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent
        evt.initMouseEvent(clickType, true, true, window, 0, 0, 0, posX, posY, false, false, false, false, 0, null);
        targetElement.dispatchEvent(evt);

        if (typeof config.showAction === 'function') {
            config.showAction(posX, posY, clickType);
        }

        if (logger && config.log) {
            logger.log('gremlin', 'clicker   ', clickType, 'at', posX, posY);
        }
    };
};
<|endoftext|>


<commit_before>function parseStateRef(ref) {
  var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
  if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
  return { state: parsed[1], paramExpr: parsed[3] || null };
}

function stateContext(el) {
  var stateData = el.parent().inheritedData('$uiView');

  if (stateData && stateData.state && stateData.state.name) {
    return stateData.state;
  }
}

/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-sref
 *
 * @requires ui.router.state.$state
 * @requires $timeout
 *
 * @restrict A
 *
 * @description
 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
 * URL, the directive will automatically generate & update the `href` attribute via
 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
 * the link will trigger a state transition with optional parameters.
 *
 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
 * handled natively by the browser.
 *
 * You can also use relative state paths within ui-sref, just like the relative
 * paths passed to `$state.go()`. You just need to be aware that the path is relative
 * to the state that the link lives in, in other words the state that loaded the
 * template containing the link.
 *
 * @example
 *

 * Home | About
 *
 *

 *

 *     {{ contact.name }}
 *

 *

 *

 *
 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
 */
$StateRefDirective.$inject = ['$state', '$timeout'];
function $StateRefDirective($state, $timeout) {
  return {
    restrict: 'A',
    require: '?^uiSrefActive',
    link: function(scope, element, attrs, uiSrefActive) {
      var ref = parseStateRef(attrs.uiSref);
      var params = null, url = null, base = stateContext(element) || $state.$current;
      var isForm = element[0].nodeName === "FORM";
      var attr = isForm ? "action" : "href", nav = true;

      var update = function(newVal) {
        if (newVal) params = newVal;
        if (!nav) return;

        var newHref = $state.href(ref.state, params, { relative: base });

        if (!newHref) {
          nav = false;
          return false;
        }
        element[0][attr] = newHref;
        if (uiSrefActive) {
          uiSrefActive.$$setStateInfo(ref.state, params);
        }
      };

      if (ref.paramExpr) {
        scope.$watch(ref.paramExpr, function(newVal, oldVal) {
          if (newVal !== params) update(newVal);
        }, true);
        params = scope.$eval(ref.paramExpr);
      }
      update();

      if (isForm) return;

      element.bind("click", function(e) {
        var button = e.which || e.button;
        if ((button === 0 || button == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey && !element.attr('target')) {
          // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
          $timeout(function() {
            $state.go(ref.state, params, { relative: base });
          });
          e.preventDefault();
        }
      });
    }
  };
}

/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-sref-active
 *
 * @requires ui.router.state.$state
 * @requires ui.router.state.$stateParams
 * @requires $interpolate
 *
 * @restrict A
 *
 * @description
 * A directive working alongside ui-sref to add classes to an element when the
 * related ui-sref directive's state is active, and removing them when it is inactive.
 * The primary use-case is to simplify the special appearance of navigation menus
 * relying on `ui-sref`, by having the "active" state's menu button appear different,
 * distinguishing it from the inactive menu items.
 *
 * @example
 *

 *

 *

 *     @bilbobaggins
 *

 *
 *

 *

 */
$StateActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
function $StateActiveDirective($state, $stateParams, $interpolate) {
  return {
    restrict: "A",
    controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
      var state, params, activeClass;

      // There probably isn't much point in $observing this
      activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope);

      // Allow uiSref to communicate with uiSrefActive
      this.$$setStateInfo = function(newState, newParams) {
        state = $state.get(newState, stateContext($element));
        params = newParams;
        update();
      };

      $scope.$on('$stateChangeSuccess', update);

      // Update route state
      function update() {
        if ($state.$current.self === state && matchesParams()) {
          $element.addClass(activeClass);
        } else {
          $element.removeClass(activeClass);
        }
      }

      function matchesParams() {
        return !params || equalForKeys(params, $stateParams);
      }
    }]
  };
}

angular.module('ui.router.state')
  .directive('uiSref', $StateRefDirective)
  .directive('uiSrefActive', $StateActiveDirective);
<commit_msg>Fix uiSrefActive state for urlless navigation<commit_after>function parseStateRef(ref) {
  var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
  if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
  return { state: parsed[1], paramExpr: parsed[3] || null };
}

function stateContext(el) {
  var stateData = el.parent().inheritedData('$uiView');

  if (stateData && stateData.state && stateData.state.name) {
    return stateData.state;
  }
}

/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-sref
 *
 * @requires ui.router.state.$state
 * @requires $timeout
 *
 * @restrict A
 *
 * @description
 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
 * URL, the directive will automatically generate & update the `href` attribute via
 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
 * the link will trigger a state transition with optional parameters.
 *
 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
 * handled natively by the browser.
 *
 * You can also use relative state paths within ui-sref, just like the relative
 * paths passed to `$state.go()`. You just need to be aware that the path is relative
 * to the state that the link lives in, in other words the state that loaded the
 * template containing the link.
 *
 * @example
 *

 * Home | About
 *
 *

 *

 *     {{ contact.name }}
 *

 *

 *

 *
 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
 */
$StateRefDirective.$inject = ['$state', '$timeout'];
function $StateRefDirective($state, $timeout) {
  return {
    restrict: 'A',
    require: '?^uiSrefActive',
    link: function(scope, element, attrs, uiSrefActive) {
      var ref = parseStateRef(attrs.uiSref);
      var params = null, url = null, base = stateContext(element) || $state.$current;
      var isForm = element[0].nodeName === "FORM";
      var attr = isForm ? "action" : "href", nav = true;

      var update = function(newVal) {
        if (newVal) params = newVal;
        if (!nav) return;

        var newHref = $state.href(ref.state, params, { relative: base });

        if (uiSrefActive) {
          uiSrefActive.$$setStateInfo(ref.state, params);
        }
        if (!newHref) {
          nav = false;
          return false;
        }
        element[0][attr] = newHref;
      };

      if (ref.paramExpr) {
        scope.$watch(ref.paramExpr, function(newVal, oldVal) {
          if (newVal !== params) update(newVal);
        }, true);
        params = scope.$eval(ref.paramExpr);
      }
      update();

      if (isForm) return;

      element.bind("click", function(e) {
        var button = e.which || e.button;
        if ((button === 0 || button == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey && !element.attr('target')) {
          // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
          $timeout(function() {
            $state.go(ref.state, params, { relative: base });
          });
          e.preventDefault();
        }
      });
    }
  };
}

/**
 * @ngdoc directive
 * @name ui.router.state.directive:ui-sref-active
 *
 * @requires ui.router.state.$state
 * @requires ui.router.state.$stateParams
 * @requires $interpolate
 *
 * @restrict A
 *
 * @description
 * A directive working alongside ui-sref to add classes to an element when the
 * related ui-sref directive's state is active, and removing them when it is inactive.
 * The primary use-case is to simplify the special appearance of navigation menus
 * relying on `ui-sref`, by having the "active" state's menu button appear different,
 * distinguishing it from the inactive menu items.
 *
 * @example
 *

 *

 *

 *     @bilbobaggins
 *

 *
 *

 *

 */
$StateActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
function $StateActiveDirective($state, $stateParams, $interpolate) {
  return {
    restrict: "A",
    controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
      var state, params, activeClass;

      // There probably isn't much point in $observing this
      activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope);

      // Allow uiSref to communicate with uiSrefActive
      this.$$setStateInfo = function(newState, newParams) {
        state = $state.get(newState, stateContext($element));
        params = newParams;
        update();
      };

      $scope.$on('$stateChangeSuccess', update);

      // Update route state
      function update() {
        if ($state.$current.self === state && matchesParams()) {
          $element.addClass(activeClass);
        } else {
          $element.removeClass(activeClass);
        }
      }

      function matchesParams() {
        return !params || equalForKeys(params, $stateParams);
      }
    }]
  };
}

angular.module('ui.router.state')
  .directive('uiSref', $StateRefDirective)
  .directive('uiSrefActive', $StateActiveDirective);
<|endoftext|>


<commit_before>/*
Copyright 2017 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";

import q from "q";
import {MatrixInMemoryStore} from "./memory";
import User from "../models/user";
import Room from "../models/room";
import {MatrixEvent} from "../models/event";
import utils from "../utils";

/**
 * This is an internal module. See {@link IndexedDBStore} for the public class.
 * @module store/indexeddb
 */

const VERSION = 1;

/**
 * Construct a new Indexed Database store backend. This requires a call to
 * connect() before this store can be used.
 * @constructor
 * @param {Object} indexedDBInterface The Indexed DB interface e.g
 * window.indexedDB
 */
const IndexedDBStoreBackend = function IndexedDBStoreBackend(indexedDBInterface) {
    this.indexedDB = indexedDBInterface;
    this.db = null;
};

IndexedDBStoreBackend.prototype = {
    /**
     * Attempt to connect to the database. This can fail if the user does not
     * grant permission.
     * @return {Promise} Resolves if successfully connected.
     */
    connect: function() {
        if (this.db) {
            return q();
        }
        const req = this.indexedDB.open("matrix-js-sdk", VERSION);
        req.onupgradeneeded = (ev) => {
            const db = ev.target.result;
            const oldVersion = ev.oldVersion;
            if (oldVersion < 1) { // The database did not previously exist.
                createDatabase(db);
            }
            // Expand as needed.
        };
        return promiseifyRequest(req).then((ev) => {
            this.db = ev.target.result;
        });
    },

    /**
     * Clear the entire database. This should be used when logging out of a client
     * to prevent mixing data between accounts.
     * @return {Promise} Resolved when the database is cleared.
     */
    clearDatabase: function() {
        return promiseifyRequest(this.indexedDB.deleteDatabase("matrix-js-sdk"));
    },

    /**
     * Persist a list of Room objects. Rooms with the same 'roomId' will be replaced.
     * @param {Room[]} rooms An array of rooms
     * @return {Promise} Resolves if the rooms were persisted.
     */
    persistRooms: function(rooms) {
        return this._upsert("rooms", rooms);
    },

    /**
     * Persist a sync token. This will replace any existing sync token.
     * @param {string} syncToken The token to persist.
     * @return {Promise} Resolves if the token was persisted.
     */
    persistSyncToken: function(syncToken) {
        const obj = {
            clobber: "-", // constant key so will always clobber
            syncToken: syncToken,
        };
        return this._upsert("config", [obj]);
    },

    /**
     * Persist a list of account data events. Events with the same 'type' will
     * be replaced.
     * @param {MatrixEvent[]} accountData An array of user-scoped account data events
     * @return {Promise} Resolves if the events were persisted.
     */
    persistAccountData: function(accountData) {
        return this._upsert("accountData", accountData);
    },

    /**
     * Persist a list of User objects. Users with the same 'userId' will be
     * replaced.
     * @param {User[]} users An array of users
     * @return {Promise} Resolves if the users were persisted.
     */
    persistUsers: function(users) {
        return this._upsert("users", users);
    },

    /**
     * Load all the users from the database. This is not cached.
     * @return {Promise<User[]>} A list of users.
     */
    loadUsers: function() {
        return this._deserializeAll("users", User);
    },

    /**
     * Load all the rooms from the database. This is not cached.
     * @return {Promise<Room[]>} A list of rooms.
     */
    loadRooms: function() {
        return this._deserializeAll("rooms", Room);
    },

    /**
     * Load all the account data events from the database. This is not cached.
     * @return {Promise<MatrixEvent[]>} A list of events.
     */
    loadAccountData: function() {
        return this._deserializeAll("accountData", MatrixEvent);
    },

    /**
     * Load the sync token from the database.
     * @return {Promise<?string>} The sync token
     */
    loadSyncToken: function() {
        return q.try(() => {
            const txn = this.db.transaction(["config"], "readonly");
            const store = txn.objectStore("config");
            const results = selectQuery(store, undefined, (cursor) => {
                return cursor.value;
            });
            if (results.length > 1) {
                console.warn("loadSyncToken: More than 1 config row found.");
            }
            return (results.length > 0 ? results[0].syncToken : null);
        });
    },

    _upsert: function(storeName, rows) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readwrite");
            const store = txn.objectStore(storeName);
            for (let i = 0; i < rows.length; i++) {
                if (typeof rows[i].serialize === "function") {
                    store.put(rows[i].serialize()); // put == UPSERT
                } else {
                    store.put(rows[i]); // put == UPSERT
                }
            }
            return promiseifyTxn(txn);
        });
    },

    _deserializeAll: function(storeName, Cls) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readonly");
            const store = txn.objectStore(storeName);
            return selectQuery(store, undefined, (cursor) => {
                return Cls.deserialize(cursor.value);
            });
        });
    },
};

/**
 * Construct a new Indexed Database store, which extends MatrixInMemoryStore.
 *
 * This store functions like a MatrixInMemoryStore except it periodically persists
 * the contents of the store to an IndexedDB backend.
 *
 * All data is still kept in-memory but can be loaded from disk by calling
 * startup(). This can make startup times quicker as a complete
 * sync from the server is not required. This does not reduce memory usage as all
 * the data is eagerly fetched when startup() is called.
 *

 * let opts = { localStorage: window.localStorage };
 * let store = new IndexedDBStore(new IndexedDBStoreBackend(window.indexedDB), opts);
 * await store.startup(); // load from indexed db
 * let client = sdk.createClient({
 *     store: store,
 * });
 * client.startClient();
 * client.on("sync", function(state, prevState, data) {
 *     if (state === "PREPARED") {
 *         console.log("Started up, now with go faster stripes!");
 *     }
 * });
 *

 *
 * @constructor
 * @extends MatrixInMemoryStore
 * @param {IndexedDBStoreBackend} backend The indexed db backend instance.
 * @param {Object=} opts Options for MatrixInMemoryStore.
 * @prop {IndexedDBStoreBackend} backend The backend instance. Call through to
 * this API if you need to perform specific indexeddb actions like deleting the
 * database.
 */
const IndexedDBStore = function IndexedDBStore(backend, opts) {
    MatrixInMemoryStore.call(this, opts);
    this.backend = backend;
    this.startedUp = false;
};
utils.inherits(IndexedDBStore, MatrixInMemoryStore);

/**
 * @return {Promise} Resolved when loaded from indexed db.
  */
IndexedDBStore.prototype.startup = function() {
    if (this.startedUp) {
        return q();
    }
    return this.backend.connect().then(() => {
        return this.backend.loadUsers();
    }).then((users) => {
        users.forEach((u) => {
            this.storeUser(u);
        });
    });
};

function createDatabase(db) {
    // Make room store, clobber based on room ID. (roomId property of Room objects)
    db.createObjectStore("rooms", { keyPath: ["roomId"] });

    // Make user store, clobber based on user ID. (userId property of User objects)
    db.createObjectStore("users", { keyPath: ["userId"] });

    // Make account data store, clobber based on event type.
    // (event.type property of MatrixEvent objects)
    db.createObjectStore("accountData", { keyPath: ["event.type"] });

    // Make configuration store (sync tokens, etc), always clobber (const key).
    db.createObjectStore("config", { keyPath: ["clobber"] });
}

/**
 * Helper method to collect results from a Cursor and promiseify it.
 * @param {ObjectStore|Index} store The store to perform openCursor on.
 * @param {IDBKeyRange=} keyRange Optional key range to apply on the cursor.
 * @param {Function} resultMapper A function which is repeatedly called with a
 * Cursor.
 * Return the data you want to keep.
 * @return {Promise<T[]>} Resolves to an array of whatever you returned from
 * resultMapper.
 */
function selectQuery(store, keyRange, resultMapper) {
    const query = store.openCursor(keyRange);
    return q.Promise((resolve, reject) => { /*eslint new-cap: 0*/
        const results = [];
        query.onerror = (event) => {
            reject(new Error("Query failed: " + event.target.errorCode));
        };
        // collect results
        query.onsuccess = (event) => {
            const cursor = event.target.result;
            if (!cursor) {
                resolve(results);
                return; // end of results
            }
            results.push(resultMapper(cursor));
            cursor.continue();
        };
    });
}

function promiseifyTxn(txn) {
    return new q.Promise((resolve, reject) => {
        txn.oncomplete = function(event) {
            resolve(event);
        };
        txn.onerror = function(event) {
            reject(event);
        };
    });
}

function promiseifyRequest(req) {
    return new q.Promise((resolve, reject) => {
        req.onsuccess = function(event) {
            resolve(event);
        };
        req.onerror = function(event) {
            reject(event);
        };
    });
}

module.exports.IndexedDBStore = IndexedDBStore;
module.exports.IndexedDBStoreBackend = IndexedDBStoreBackend;
<commit_msg>Periodically update the database<commit_after>/*
Copyright 2017 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";

import q from "q";
import {MatrixInMemoryStore} from "./memory";
import User from "../models/user";
import Room from "../models/room";
import {MatrixEvent} from "../models/event";
import utils from "../utils";

/**
 * This is an internal module. See {@link IndexedDBStore} for the public class.
 * @module store/indexeddb
 */

const VERSION = 1;
const WRITE_DELAY_MS = 1000 * 60; // once a minute

/**
 * Construct a new Indexed Database store backend. This requires a call to
 * connect() before this store can be used.
 * @constructor
 * @param {Object} indexedDBInterface The Indexed DB interface e.g
 * window.indexedDB
 */
const IndexedDBStoreBackend = function IndexedDBStoreBackend(indexedDBInterface) {
    this.indexedDB = indexedDBInterface;
    this.db = null;
};

IndexedDBStoreBackend.prototype = {
    /**
     * Attempt to connect to the database. This can fail if the user does not
     * grant permission.
     * @return {Promise} Resolves if successfully connected.
     */
    connect: function() {
        if (this.db) {
            return q();
        }
        const req = this.indexedDB.open("matrix-js-sdk", VERSION);
        req.onupgradeneeded = (ev) => {
            const db = ev.target.result;
            const oldVersion = ev.oldVersion;
            if (oldVersion < 1) { // The database did not previously exist.
                createDatabase(db);
            }
            // Expand as needed.
        };
        return promiseifyRequest(req).then((ev) => {
            this.db = ev.target.result;
        });
    },

    /**
     * Clear the entire database. This should be used when logging out of a client
     * to prevent mixing data between accounts.
     * @return {Promise} Resolved when the database is cleared.
     */
    clearDatabase: function() {
        return promiseifyRequest(this.indexedDB.deleteDatabase("matrix-js-sdk"));
    },

    /**
     * Persist a list of Room objects. Rooms with the same 'roomId' will be replaced.
     * @param {Room[]} rooms An array of rooms
     * @return {Promise} Resolves if the rooms were persisted.
     */
    persistRooms: function(rooms) {
        return this._upsert("rooms", rooms);
    },

    /**
     * Persist a sync token. This will replace any existing sync token.
     * @param {string} syncToken The token to persist.
     * @return {Promise} Resolves if the token was persisted.
     */
    persistSyncToken: function(syncToken) {
        const obj = {
            clobber: "-", // constant key so will always clobber
            syncToken: syncToken,
        };
        return this._upsert("config", [obj]);
    },

    /**
     * Persist a list of account data events. Events with the same 'type' will
     * be replaced.
     * @param {MatrixEvent[]} accountData An array of user-scoped account data events
     * @return {Promise} Resolves if the events were persisted.
     */
    persistAccountData: function(accountData) {
        return this._upsert("accountData", accountData);
    },

    /**
     * Persist a list of User objects. Users with the same 'userId' will be
     * replaced.
     * @param {User[]} users An array of users
     * @return {Promise} Resolves if the users were persisted.
     */
    persistUsers: function(users) {
        return this._upsert("users", users);
    },

    /**
     * Load all the users from the database. This is not cached.
     * @return {Promise<User[]>} A list of users.
     */
    loadUsers: function() {
        return this._deserializeAll("users", User);
    },

    /**
     * Load all the rooms from the database. This is not cached.
     * @return {Promise<Room[]>} A list of rooms.
     */
    loadRooms: function() {
        return this._deserializeAll("rooms", Room);
    },

    /**
     * Load all the account data events from the database. This is not cached.
     * @return {Promise<MatrixEvent[]>} A list of events.
     */
    loadAccountData: function() {
        return this._deserializeAll("accountData", MatrixEvent);
    },

    /**
     * Load the sync token from the database.
     * @return {Promise<?string>} The sync token
     */
    loadSyncToken: function() {
        return q.try(() => {
            const txn = this.db.transaction(["config"], "readonly");
            const store = txn.objectStore("config");
            const results = selectQuery(store, undefined, (cursor) => {
                return cursor.value;
            });
            if (results.length > 1) {
                console.warn("loadSyncToken: More than 1 config row found.");
            }
            return (results.length > 0 ? results[0].syncToken : null);
        });
    },

    _upsert: function(storeName, rows) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readwrite");
            const store = txn.objectStore(storeName);
            for (let i = 0; i < rows.length; i++) {
                if (typeof rows[i].serialize === "function") {
                    store.put(rows[i].serialize()); // put == UPSERT
                } else {
                    store.put(rows[i]); // put == UPSERT
                }
            }
            return promiseifyTxn(txn);
        });
    },

    _deserializeAll: function(storeName, Cls) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readonly");
            const store = txn.objectStore(storeName);
            return selectQuery(store, undefined, (cursor) => {
                return Cls.deserialize(cursor.value);
            });
        });
    },
};

/**
 * Construct a new Indexed Database store, which extends MatrixInMemoryStore.
 *
 * This store functions like a MatrixInMemoryStore except it periodically persists
 * the contents of the store to an IndexedDB backend.
 *
 * All data is still kept in-memory but can be loaded from disk by calling
 * startup(). This can make startup times quicker as a complete
 * sync from the server is not required. This does not reduce memory usage as all
 * the data is eagerly fetched when startup() is called.
 *

 * let opts = { localStorage: window.localStorage };
 * let store = new IndexedDBStore(new IndexedDBStoreBackend(window.indexedDB), opts);
 * await store.startup(); // load from indexed db
 * let client = sdk.createClient({
 *     store: store,
 * });
 * client.startClient();
 * client.on("sync", function(state, prevState, data) {
 *     if (state === "PREPARED") {
 *         console.log("Started up, now with go faster stripes!");
 *     }
 * });
 *

 *
 * @constructor
 * @extends MatrixInMemoryStore
 * @param {IndexedDBStoreBackend} backend The indexed db backend instance.
 * @param {Object=} opts Options for MatrixInMemoryStore.
 * @prop {IndexedDBStoreBackend} backend The backend instance. Call through to
 * this API if you need to perform specific indexeddb actions like deleting the
 * database.
 */
const IndexedDBStore = function IndexedDBStore(backend, opts) {
    MatrixInMemoryStore.call(this, opts);
    this.backend = backend;
    this.startedUp = false;
    this._syncTs = Date.now(); // updated when writes to the database are performed
};
utils.inherits(IndexedDBStore, MatrixInMemoryStore);

/**
 * @return {Promise} Resolved when loaded from indexed db.
  */
IndexedDBStore.prototype.startup = function() {
    if (this.startedUp) {
        return q();
    }
    return this.backend.connect().then(() => {
        return q.all([
            this.backend.loadUsers(),
            this.backend.loadAccountData(),
            this.backend.loadRooms(),
            this.backend.loadSyncToken(),
        ]);
    }).then((values) => {
        console.log("Loaded data from database. Reticulating splines...");
        const [users, accountData, rooms, syncToken] = values;
        users.forEach((u) => {
            this.storeUser(u);
        });
        this.storeAccountDataEvents(accountData);
        rooms.forEach((r) => {
            this.storeRoom(r);
        });
        this._syncTs = Date.now(); // pretend we've written so we don't rewrite
        this.setSyncToken(syncToken);
    });
};

/**
 * Set a new sync token and possibly write to the database.
 * Overrides MatrixInMemoryStore.
 * @param {string} token The new sync token
 * @return {?Promise} A promise if this sync token triggered a write to the
 * database, else null. Promise resolves after the write completes.
 */
IndexedDBStore.prototype.setSyncToken = function(token) {
    MatrixInMemoryStore.prototype.setSyncToken.call(this, token);
    const now = Date.now();
    if (now - this._syncTs > WRITE_DELAY_MS) {
        // save contents to the database.
        console.log("TODO: Write to database.");
        this._syncTs = Date.now();
        return q();
    }
};

function createDatabase(db) {
    // Make room store, clobber based on room ID. (roomId property of Room objects)
    db.createObjectStore("rooms", { keyPath: ["roomId"] });

    // Make user store, clobber based on user ID. (userId property of User objects)
    db.createObjectStore("users", { keyPath: ["userId"] });

    // Make account data store, clobber based on event type.
    // (event.type property of MatrixEvent objects)
    db.createObjectStore("accountData", { keyPath: ["event.type"] });

    // Make configuration store (sync tokens, etc), always clobber (const key).
    db.createObjectStore("config", { keyPath: ["clobber"] });
}

/**
 * Helper method to collect results from a Cursor and promiseify it.
 * @param {ObjectStore|Index} store The store to perform openCursor on.
 * @param {IDBKeyRange=} keyRange Optional key range to apply on the cursor.
 * @param {Function} resultMapper A function which is repeatedly called with a
 * Cursor.
 * Return the data you want to keep.
 * @return {Promise<T[]>} Resolves to an array of whatever you returned from
 * resultMapper.
 */
function selectQuery(store, keyRange, resultMapper) {
    const query = store.openCursor(keyRange);
    return q.Promise((resolve, reject) => { /*eslint new-cap: 0*/
        const results = [];
        query.onerror = (event) => {
            reject(new Error("Query failed: " + event.target.errorCode));
        };
        // collect results
        query.onsuccess = (event) => {
            const cursor = event.target.result;
            if (!cursor) {
                resolve(results);
                return; // end of results
            }
            results.push(resultMapper(cursor));
            cursor.continue();
        };
    });
}

function promiseifyTxn(txn) {
    return new q.Promise((resolve, reject) => {
        txn.oncomplete = function(event) {
            resolve(event);
        };
        txn.onerror = function(event) {
            reject(event);
        };
    });
}

function promiseifyRequest(req) {
    return new q.Promise((resolve, reject) => {
        req.onsuccess = function(event) {
            resolve(event);
        };
        req.onerror = function(event) {
            reject(event);
        };
    });
}

module.exports.IndexedDBStore = IndexedDBStore;
module.exports.IndexedDBStoreBackend = IndexedDBStoreBackend;
<|endoftext|>


<commit_before>
export const changeAmount = (store, payload) => {
  store.items = store.items.map(item => {
    if (item._id === payload._id) {
      item.amount = payload.amount
    }

    return item
  })
}

export const getItems = (store, payload) => {
  store.items = payload
}

export const removeItem = (store, payload) => {
  store.items = store.items.filter(itm => itm._id !== payload._id)
}

export const updateItem = (store, payload) => {
  store.items = store.items.map(item => {
    if (item._id === payload._id) {
      item = payload
    }

    return item
  })
}
<commit_msg>Refatorado mutations.<commit_after>function update (items, payload) {
  return items.map(item => {
    if (item._id === payload._id) item = payload
    return item
  })
}

export const changeAmount = (store, payload) => {
  store.items = update(store.items, payload)
}

export const getItems = (store, payload) => {
  store.items = payload
}

export const removeItem = (store, payload) => {
  store.items = store.items.filter(itm => itm._id !== payload._id)
}

export const updateItem = (store, payload) => {
  store.items = update(store.items, payload)
}
<|endoftext|>


<commit_before>const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Constants = require('../util/Constants');
const Presence = require('./Presence').Presence;
const Snowflake = require('../util/Snowflake');
const util = require('util');

/**
 * Represents a user on Discord.
 * @implements {TextBasedChannel}
 */
class User {
  constructor(client, data) {
    /**
     * The client that created the instance of the user
     * @name User#client
     * @type {Client}
     * @readonly
     */
    Object.defineProperty(this, 'client', { value: client });

    if (data) this.setup(data);
  }

  setup(data) {
    /**
     * The ID of the user
     * @type {Snowflake}
     */
    this.id = data.id;

    /**
     * The username of the user
     * @type {string}
     */
    this.username = data.username;

    /**
     * A discriminator based on username for the user
     * @type {string}
     */
    this.discriminator = data.discriminator;

    /**
     * The ID of the user's avatar
     * @type {string}
     */
    this.avatar = data.avatar;

    /**
     * Whether or not the user is a bot
     * @type {boolean}
     */
    this.bot = Boolean(data.bot);

    /**
     * The ID of the last message sent by the user, if one was sent
     * @type {?Snowflake}
     */
    this.lastMessageID = null;

    /**
     * The Message object of the last message sent by the user, if one was sent
     * @type {?Message}
     */
    this.lastMessage = null;
  }

  patch(data) {
    for (const prop of ['id', 'username', 'discriminator', 'avatar', 'bot']) {
      if (typeof data[prop] !== 'undefined') this[prop] = data[prop];
    }
    if (data.token) this.client.token = data.token;
  }

  /**
   * The timestamp the user was created at
   * @type {number}
   * @readonly
   */
  get createdTimestamp() {
    return Snowflake.deconstruct(this.id).timestamp;
  }

  /**
   * The time the user was created
   * @type {Date}
   * @readonly
   */
  get createdAt() {
    return new Date(this.createdTimestamp);
  }

  /**
   * The presence of this user
   * @type {Presence}
   * @readonly
   */
  get presence() {
    if (this.client.presences.has(this.id)) return this.client.presences.get(this.id);
    for (const guild of this.client.guilds.values()) {
      if (guild.presences.has(this.id)) return guild.presences.get(this.id);
    }
    return new Presence(undefined, this.client);
  }

  /**
   * A link to the user's avatar
   * @type {?string}
   * @readonly
   */
  get avatarURL() {
    if (!this.avatar) return null;
    return Constants.Endpoints.User(this).Avatar(this.client.options.http.cdn, this.avatar);
  }

  /**
   * A link to the user's default avatar
   * @type {string}
   * @readonly
   */
  get defaultAvatarURL() {
    const avatars = Object.keys(Constants.DefaultAvatars);
    const avatar = avatars[this.discriminator % avatars.length];
    return Constants.Endpoints.CDN(this.client.options.http.host).Asset(`${Constants.DefaultAvatars[avatar]}.png`);
  }

  /**
   * A link to the user's avatar if they have one. Otherwise a link to their default avatar will be returned
   * @type {string}
   * @readonly
   */
  get displayAvatarURL() {
    return this.avatarURL || this.defaultAvatarURL;
  }

  /**
   * The Discord "tag" for this user
   * @type {string}
   * @readonly
   */
  get tag() {
    return `${this.username}#${this.discriminator}`;
  }

  /**
   * The note that is set for the user
   * <warn>This is only available when using a user account.</warn>
   * @type {?string}
   * @readonly
   */
  get note() {
    return this.client.user.notes.get(this.id) || null;
  }

  /**
   * Check whether the user is typing in a channel.
   * @param {ChannelResolvable} channel The channel to check in
   * @returns {boolean}
   */
  typingIn(channel) {
    channel = this.client.resolver.resolveChannel(channel);
    return channel._typing.has(this.id);
  }

  /**
   * Get the time that the user started typing.
   * @param {ChannelResolvable} channel The channel to get the time in
   * @returns {?Date}
   */
  typingSinceIn(channel) {
    channel = this.client.resolver.resolveChannel(channel);
    return channel._typing.has(this.id) ? new Date(channel._typing.get(this.id).since) : null;
  }

  /**
   * Get the amount of time the user has been typing in a channel for (in milliseconds), or -1 if they're not typing.
   * @param {ChannelResolvable} channel The channel to get the time in
   * @returns {number}
   */
  typingDurationIn(channel) {
    channel = this.client.resolver.resolveChannel(channel);
    return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1;
  }

  /**
   * The DM between the client's user and this user
   * @type {?DMChannel}
   * @readonly
   */
  get dmChannel() {
    return this.client.channels.find(c => c.type === 'dm' && c.recipient.id === this.id);
  }

  /**
   * Creates a DM channel between the client and the user.
   * @returns {Promise<DMChannel>}
   */
  createDM() {
    return this.client.rest.methods.createDM(this);
  }

  /**
   * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
   * @returns {Promise<DMChannel>}
   */
  deleteDM() {
    return this.client.rest.methods.deleteChannel(this);
  }

  /**
   * Sends a friend request to the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  addFriend() {
    return this.client.rest.methods.addFriend(this);
  }

  /**
   * Removes the user from your friends.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  removeFriend() {
    return this.client.rest.methods.removeFriend(this);
  }

  /**
   * Blocks the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  block() {
    return this.client.rest.methods.blockUser(this);
  }

  /**
   * Unblocks the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  unblock() {
    return this.client.rest.methods.unblockUser(this);
  }

  /**
   * Get the profile of the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<UserProfile>}
   */
  fetchProfile() {
    return this.client.rest.methods.fetchUserProfile(this);
  }

  /**
   * Sets a note for the user.
   * <warn>This is only available when using a user account.</warn>
   * @param {string} note The note to set for the user
   * @returns {Promise<User>}
   */
  setNote(note) {
    return this.client.rest.methods.setNote(this, note);
  }

  /**
   * Checks if the user is equal to another. It compares ID, username, discriminator, avatar, and bot flags.
   * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
   * @param {User} user User to compare with
   * @returns {boolean}
   */
  equals(user) {
    let equal = user &&
      this.id === user.id &&
      this.username === user.username &&
      this.discriminator === user.discriminator &&
      this.avatar === user.avatar &&
      this.bot === Boolean(user.bot);

    return equal;
  }

  /**
   * When concatenated with a string, this automatically concatenates the user's mention instead of the User object.
   * @returns {string}
   * @example
   * // logs: Hello from <@123456789>!
   * console.log(`Hello from ${user}!`);
   */
  toString() {
    return `<@${this.id}>`;
  }

  // These are here only for documentation purposes - they are implemented by TextBasedChannel
  /* eslint-disable no-empty-function */
  send() {}
  sendMessage() {}
  sendEmbed() {}
  sendFile() {}
  sendCode() {}
}

TextBasedChannel.applyToClass(User);

User.prototype.block =
  util.deprecate(User.prototype.block, 'User#block: userbot methods will be removed');

User.prototype.unblock =
  util.deprecate(User.prototype.unblock, 'User#unblock: userbot methods will be removed');

User.prototype.addFriend =
  util.deprecate(User.prototype.addFriend, 'User#addFriend: userbot methods will be removed');

User.prototype.removeFriend =
  util.deprecate(User.prototype.removeFriend, 'User#removeFriend: userbot methods will be removed');

User.prototype.setNote =
  util.deprecate(User.prototype.setNote, 'User#setNote, userbot methods will be removed');

User.prototype.fetchProfile =
  util.deprecate(User.prototype.fetchProfile, 'User#fetchProfile: userbot methods will be removed');

module.exports = User;
<commit_msg>docs(User): clarify what User#tag represents<commit_after>const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Constants = require('../util/Constants');
const Presence = require('./Presence').Presence;
const Snowflake = require('../util/Snowflake');
const util = require('util');

/**
 * Represents a user on Discord.
 * @implements {TextBasedChannel}
 */
class User {
  constructor(client, data) {
    /**
     * The client that created the instance of the user
     * @name User#client
     * @type {Client}
     * @readonly
     */
    Object.defineProperty(this, 'client', { value: client });

    if (data) this.setup(data);
  }

  setup(data) {
    /**
     * The ID of the user
     * @type {Snowflake}
     */
    this.id = data.id;

    /**
     * The username of the user
     * @type {string}
     */
    this.username = data.username;

    /**
     * A discriminator based on username for the user
     * @type {string}
     */
    this.discriminator = data.discriminator;

    /**
     * The ID of the user's avatar
     * @type {string}
     */
    this.avatar = data.avatar;

    /**
     * Whether or not the user is a bot
     * @type {boolean}
     */
    this.bot = Boolean(data.bot);

    /**
     * The ID of the last message sent by the user, if one was sent
     * @type {?Snowflake}
     */
    this.lastMessageID = null;

    /**
     * The Message object of the last message sent by the user, if one was sent
     * @type {?Message}
     */
    this.lastMessage = null;
  }

  patch(data) {
    for (const prop of ['id', 'username', 'discriminator', 'avatar', 'bot']) {
      if (typeof data[prop] !== 'undefined') this[prop] = data[prop];
    }
    if (data.token) this.client.token = data.token;
  }

  /**
   * The timestamp the user was created at
   * @type {number}
   * @readonly
   */
  get createdTimestamp() {
    return Snowflake.deconstruct(this.id).timestamp;
  }

  /**
   * The time the user was created
   * @type {Date}
   * @readonly
   */
  get createdAt() {
    return new Date(this.createdTimestamp);
  }

  /**
   * The presence of this user
   * @type {Presence}
   * @readonly
   */
  get presence() {
    if (this.client.presences.has(this.id)) return this.client.presences.get(this.id);
    for (const guild of this.client.guilds.values()) {
      if (guild.presences.has(this.id)) return guild.presences.get(this.id);
    }
    return new Presence(undefined, this.client);
  }

  /**
   * A link to the user's avatar
   * @type {?string}
   * @readonly
   */
  get avatarURL() {
    if (!this.avatar) return null;
    return Constants.Endpoints.User(this).Avatar(this.client.options.http.cdn, this.avatar);
  }

  /**
   * A link to the user's default avatar
   * @type {string}
   * @readonly
   */
  get defaultAvatarURL() {
    const avatars = Object.keys(Constants.DefaultAvatars);
    const avatar = avatars[this.discriminator % avatars.length];
    return Constants.Endpoints.CDN(this.client.options.http.host).Asset(`${Constants.DefaultAvatars[avatar]}.png`);
  }

  /**
   * A link to the user's avatar if they have one. Otherwise a link to their default avatar will be returned
   * @type {string}
   * @readonly
   */
  get displayAvatarURL() {
    return this.avatarURL || this.defaultAvatarURL;
  }

  /**
   * The Discord "tag" (e.g. `hydrabolt#0001`) for this user
   * @type {string}
   * @readonly
   */
  get tag() {
    return `${this.username}#${this.discriminator}`;
  }

  /**
   * The note that is set for the user
   * <warn>This is only available when using a user account.</warn>
   * @type {?string}
   * @readonly
   */
  get note() {
    return this.client.user.notes.get(this.id) || null;
  }

  /**
   * Check whether the user is typing in a channel.
   * @param {ChannelResolvable} channel The channel to check in
   * @returns {boolean}
   */
  typingIn(channel) {
    channel = this.client.resolver.resolveChannel(channel);
    return channel._typing.has(this.id);
  }

  /**
   * Get the time that the user started typing.
   * @param {ChannelResolvable} channel The channel to get the time in
   * @returns {?Date}
   */
  typingSinceIn(channel) {
    channel = this.client.resolver.resolveChannel(channel);
    return channel._typing.has(this.id) ? new Date(channel._typing.get(this.id).since) : null;
  }

  /**
   * Get the amount of time the user has been typing in a channel for (in milliseconds), or -1 if they're not typing.
   * @param {ChannelResolvable} channel The channel to get the time in
   * @returns {number}
   */
  typingDurationIn(channel) {
    channel = this.client.resolver.resolveChannel(channel);
    return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1;
  }

  /**
   * The DM between the client's user and this user
   * @type {?DMChannel}
   * @readonly
   */
  get dmChannel() {
    return this.client.channels.find(c => c.type === 'dm' && c.recipient.id === this.id);
  }

  /**
   * Creates a DM channel between the client and the user.
   * @returns {Promise<DMChannel>}
   */
  createDM() {
    return this.client.rest.methods.createDM(this);
  }

  /**
   * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
   * @returns {Promise<DMChannel>}
   */
  deleteDM() {
    return this.client.rest.methods.deleteChannel(this);
  }

  /**
   * Sends a friend request to the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  addFriend() {
    return this.client.rest.methods.addFriend(this);
  }

  /**
   * Removes the user from your friends.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  removeFriend() {
    return this.client.rest.methods.removeFriend(this);
  }

  /**
   * Blocks the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  block() {
    return this.client.rest.methods.blockUser(this);
  }

  /**
   * Unblocks the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<User>}
   */
  unblock() {
    return this.client.rest.methods.unblockUser(this);
  }

  /**
   * Get the profile of the user.
   * <warn>This is only available when using a user account.</warn>
   * @returns {Promise<UserProfile>}
   */
  fetchProfile() {
    return this.client.rest.methods.fetchUserProfile(this);
  }

  /**
   * Sets a note for the user.
   * <warn>This is only available when using a user account.</warn>
   * @param {string} note The note to set for the user
   * @returns {Promise<User>}
   */
  setNote(note) {
    return this.client.rest.methods.setNote(this, note);
  }

  /**
   * Checks if the user is equal to another. It compares ID, username, discriminator, avatar, and bot flags.
   * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
   * @param {User} user User to compare with
   * @returns {boolean}
   */
  equals(user) {
    let equal = user &&
      this.id === user.id &&
      this.username === user.username &&
      this.discriminator === user.discriminator &&
      this.avatar === user.avatar &&
      this.bot === Boolean(user.bot);

    return equal;
  }

  /**
   * When concatenated with a string, this automatically concatenates the user's mention instead of the User object.
   * @returns {string}
   * @example
   * // logs: Hello from <@123456789>!
   * console.log(`Hello from ${user}!`);
   */
  toString() {
    return `<@${this.id}>`;
  }

  // These are here only for documentation purposes - they are implemented by TextBasedChannel
  /* eslint-disable no-empty-function */
  send() {}
  sendMessage() {}
  sendEmbed() {}
  sendFile() {}
  sendCode() {}
}

TextBasedChannel.applyToClass(User);

User.prototype.block =
  util.deprecate(User.prototype.block, 'User#block: userbot methods will be removed');

User.prototype.unblock =
  util.deprecate(User.prototype.unblock, 'User#unblock: userbot methods will be removed');

User.prototype.addFriend =
  util.deprecate(User.prototype.addFriend, 'User#addFriend: userbot methods will be removed');

User.prototype.removeFriend =
  util.deprecate(User.prototype.removeFriend, 'User#removeFriend: userbot methods will be removed');

User.prototype.setNote =
  util.deprecate(User.prototype.setNote, 'User#setNote, userbot methods will be removed');

User.prototype.fetchProfile =
  util.deprecate(User.prototype.fetchProfile, 'User#fetchProfile: userbot methods will be removed');

module.exports = User;
<|endoftext|>


<commit_before>function serializeUser(user) {
  if (!user) {
    return 'Unknown';
  }

  return {
    avatarUri: user.avatarURL,
    username: user.username,
    discriminator: user.discriminator,
    id: user.id,
  };
}

function serializeGuild(guild) {
  if (!guild) {
    return 'Direct Message';
  }

  return {
    name: guild.name,
    memberCount: guild.memberCount,
    joinedAt: guild.joinedAt,
    iconUri: guild.iconURL,
    createdAt: guild.createdAt,
    id: guild.id,
  };
}

function serializeChannel(channel) {
  if (!channel) {
    return 'Unknown';
  }

  if (!channel.guild) {
    return 'Direct Message';
  }

  return {
    name: channel.name,
    id: channel.id,
    guild: serializeGuild(channel.guild),
  };
}

function serializeMessage(msg) {
  if (!msg) {
    return 'None';
  }

  return {
    content: msg.content,
    embeds: msg.embeds,
    id: msg.id,
    author: serializeUser(msg.author),
    channel: serializeChannel(msg.channel),
  };
}

function serializeErr(err) {
  return {
    message: err.message,
    code: err.code,
    stack: err.stack,
    asString: err.toString(),
  };
}

module.exports = {
  user: serializeUser,
  guild: serializeGuild,
  channel: serializeChannel,
  message: serializeMessage,
  err: serializeErr,
};
<commit_msg>fix error when no err passed to serializeErr (apparently that can happen)<commit_after>function serializeUser(user) {
  if (!user) {
    return 'Unknown';
  }

  return {
    avatarUri: user.avatarURL,
    username: user.username,
    discriminator: user.discriminator,
    id: user.id,
  };
}

function serializeGuild(guild) {
  if (!guild) {
    return 'Direct Message';
  }

  return {
    name: guild.name,
    memberCount: guild.memberCount,
    joinedAt: guild.joinedAt,
    iconUri: guild.iconURL,
    createdAt: guild.createdAt,
    id: guild.id,
  };
}

function serializeChannel(channel) {
  if (!channel) {
    return 'Unknown';
  }

  if (!channel.guild) {
    return 'Direct Message';
  }

  return {
    name: channel.name,
    id: channel.id,
    guild: serializeGuild(channel.guild),
  };
}

function serializeMessage(msg) {
  if (!msg) {
    return 'None';
  }

  return {
    content: msg.content,
    embeds: msg.embeds,
    id: msg.id,
    author: serializeUser(msg.author),
    channel: serializeChannel(msg.channel),
  };
}

function serializeErr(err) {
  if (!err) {
    return 'None';
  }

  return {
    message: err.message,
    code: err.code,
    stack: err.stack,
    asString: err.toString(),
  };
}

module.exports = {
  user: serializeUser,
  guild: serializeGuild,
  channel: serializeChannel,
  message: serializeMessage,
  err: serializeErr,
};
<|endoftext|>


<commit_before>const {app} = require('electron')
const nslog = require('nslog')
const path = require('path')
const temp = require('temp').track()
const parseCommandLine = require('./parse-command-line')
const startCrashReporter = require('../crash-reporter-start')
const atomPaths = require('../atom-paths')
const fs = require('fs')
const CSON = require('season')
const Config = require('../config')

module.exports = function start (resourcePath, devResourcePath, startTime) {
  global.shellStartTime = startTime

  process.on('uncaughtException', function (error = {}) {
    if (error.message != null) {
      console.log(error.message)
    }

    if (error.stack != null) {
      console.log(error.stack)
    }
  })

  process.on('unhandledRejection', function (error = {}) {
    if (error.message != null) {
      console.log(error.message)
    }

    if (error.stack != null) {
      console.log(error.stack)
    }
  })

  const previousConsoleLog = console.log
  console.log = nslog

  app.commandLine.appendSwitch('enable-experimental-web-platform-features')

  const args = parseCommandLine(process.argv.slice(1))
  args.resourcePath = normalizeDriveLetterName(resourcePath)
  args.devResourcePath = normalizeDriveLetterName(devResourcePath)

  atomPaths.setAtomHome(app.getPath('home'))
  atomPaths.setUserData(app)
  setupCompileCache()

  const config = getConfig()
  const colorProfile = config.get('core.colorProfile')
  if (colorProfile && colorProfile !== 'default') {
    app.commandLine.appendSwitch('force-color-profile', colorProfile)
  }

  if (handleStartupEventWithSquirrel()) {
    return
  } else if (args.test && args.mainProcess) {
    app.setPath('userData', temp.mkdirSync('atom-user-data-dir-for-main-process-tests'))
    console.log = previousConsoleLog
    app.on('ready', function () {
      const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner'))
      testRunner(args.pathsToOpen)
    })
    return
  }

  // NB: This prevents Win10 from showing dupe items in the taskbar
  app.setAppUserModelId('com.squirrel.atom.' + process.arch)

  function addPathToOpen (event, pathToOpen) {
    event.preventDefault()
    args.pathsToOpen.push(pathToOpen)
  }

  function addUrlToOpen (event, urlToOpen) {
    event.preventDefault()
    args.urlsToOpen.push(urlToOpen)
  }

  app.on('open-file', addPathToOpen)
  app.on('open-url', addUrlToOpen)
  app.on('will-finish-launching', startCrashReporter)

  if (args.userDataDir != null) {
    app.setPath('userData', args.userDataDir)
  } else if (args.test || args.benchmark || args.benchmarkTest) {
    app.setPath('userData', temp.mkdirSync('atom-test-data'))
  }

  app.on('ready', function () {
    app.removeListener('open-file', addPathToOpen)
    app.removeListener('open-url', addUrlToOpen)
    const AtomApplication = require(path.join(args.resourcePath, 'src', 'main-process', 'atom-application'))
    AtomApplication.open(args)
  })
}

function handleStartupEventWithSquirrel () {
  if (process.platform !== 'win32') {
    return false
  }

  const SquirrelUpdate = require('./squirrel-update')
  const squirrelCommand = process.argv[1]
  return SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
}

function setupCompileCache () {
  const CompileCache = require('../compile-cache')
  CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
  CompileCache.install(process.resourcesPath, require)
}

function getConfig () {
  const config = new Config()

  let configFilePath
  if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.json'))) {
    configFilePath = path.join(process.env.ATOM_HOME, 'config.json')
  } else if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.cson'))) {
    configFilePath = path.join(process.env.ATOM_HOME, 'config.cson')
  }

  if (configFilePath) {
    const configFileData = CSON.readFileSync(configFilePath)
    config.resetUserSettings(configFileData)
  }

  return config
}

function normalizeDriveLetterName (filePath) {
  if (process.platform === 'win32' && filePath) {
    return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':')
  } else {
    return filePath
  }
}
<commit_msg>Don't require compile cache in main process<commit_after>const {app} = require('electron')
const nslog = require('nslog')
const path = require('path')
const temp = require('temp').track()
const parseCommandLine = require('./parse-command-line')
const startCrashReporter = require('../crash-reporter-start')
const atomPaths = require('../atom-paths')
const fs = require('fs')
const CSON = require('season')
const Config = require('../config')

module.exports = function start (resourcePath, devResourcePath, startTime) {
  global.shellStartTime = startTime

  process.on('uncaughtException', function (error = {}) {
    if (error.message != null) {
      console.log(error.message)
    }

    if (error.stack != null) {
      console.log(error.stack)
    }
  })

  process.on('unhandledRejection', function (error = {}) {
    if (error.message != null) {
      console.log(error.message)
    }

    if (error.stack != null) {
      console.log(error.stack)
    }
  })

  const previousConsoleLog = console.log
  console.log = nslog

  app.commandLine.appendSwitch('enable-experimental-web-platform-features')

  const args = parseCommandLine(process.argv.slice(1))
  args.resourcePath = normalizeDriveLetterName(resourcePath)
  args.devResourcePath = normalizeDriveLetterName(devResourcePath)

  atomPaths.setAtomHome(app.getPath('home'))
  atomPaths.setUserData(app)

  const config = getConfig()
  const colorProfile = config.get('core.colorProfile')
  if (colorProfile && colorProfile !== 'default') {
    app.commandLine.appendSwitch('force-color-profile', colorProfile)
  }

  if (handleStartupEventWithSquirrel()) {
    return
  } else if (args.test && args.mainProcess) {
    app.setPath('userData', temp.mkdirSync('atom-user-data-dir-for-main-process-tests'))
    console.log = previousConsoleLog
    app.on('ready', function () {
      const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner'))
      testRunner(args.pathsToOpen)
    })
    return
  }

  // NB: This prevents Win10 from showing dupe items in the taskbar
  app.setAppUserModelId('com.squirrel.atom.' + process.arch)

  function addPathToOpen (event, pathToOpen) {
    event.preventDefault()
    args.pathsToOpen.push(pathToOpen)
  }

  function addUrlToOpen (event, urlToOpen) {
    event.preventDefault()
    args.urlsToOpen.push(urlToOpen)
  }

  app.on('open-file', addPathToOpen)
  app.on('open-url', addUrlToOpen)
  app.on('will-finish-launching', startCrashReporter)

  if (args.userDataDir != null) {
    app.setPath('userData', args.userDataDir)
  } else if (args.test || args.benchmark || args.benchmarkTest) {
    app.setPath('userData', temp.mkdirSync('atom-test-data'))
  }

  app.on('ready', function () {
    app.removeListener('open-file', addPathToOpen)
    app.removeListener('open-url', addUrlToOpen)
    const AtomApplication = require(path.join(args.resourcePath, 'src', 'main-process', 'atom-application'))
    AtomApplication.open(args)
  })
}

function handleStartupEventWithSquirrel () {
  if (process.platform !== 'win32') {
    return false
  }

  const SquirrelUpdate = require('./squirrel-update')
  const squirrelCommand = process.argv[1]
  return SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
}

function getConfig () {
  const config = new Config()

  let configFilePath
  if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.json'))) {
    configFilePath = path.join(process.env.ATOM_HOME, 'config.json')
  } else if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.cson'))) {
    configFilePath = path.join(process.env.ATOM_HOME, 'config.cson')
  }

  if (configFilePath) {
    const configFileData = CSON.readFileSync(configFilePath)
    config.resetUserSettings(configFileData)
  }

  return config
}

function normalizeDriveLetterName (filePath) {
  if (process.platform === 'win32' && filePath) {
    return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':')
  } else {
    return filePath
  }
}
<|endoftext|>