<commit_before>/**
* Main controller for topology edition.
* It loads topology and manage common server communication and topology refresh through common parent scope.
*/
define(function (require) {
  'use strict';

  var modules = require('modules');
  var _ = require('lodash');
  var angular = require('angular');

  require('scripts/topology/controllers/topology');
  require('scripts/topology/controllers/editor_browser');
  require('scripts/topology/controllers/editor_workflow');
  require('scripts/topology/controllers/editor_history');
  require('scripts/topology/controllers/editor_git_modal');
  require('scripts/topology/controllers/editor_validation');
  require('scripts/topology/controllers/editor_inputs_variables');

  require('scripts/tosca/services/tosca_cardinalities_service');
  require('scripts/topology/services/topology_json_processor');
  require('scripts/topology/services/topology_recovery_service');
  require('scripts/topology/services/topology_services');

  // manage websockets for topology editor
  // require('scripts/topology/services/topology_editor_events_services');
  require('scripts/common/directives/parsing_errors');

  modules.get('a4c-topology-editor', ['a4c-common', 'ui.bootstrap', 'a4c-tosca', 'a4c-styles', 'cfp.hotkeys']).controller('TopologyEditorCtrl',
    ['$scope', '$state', '$stateParams', '$alresource', '$uibModal', '$translate', 'toaster', 'hotkeys', 'menu', 'topologyServices', 'topologyJsonProcessor', 'toscaService', 'toscaCardinalitiesService', 'topologyRecoveryServices',
    function($scope, $state, $stateParams, $alresource, $uibModal, $translate, toaster, hotkeys, menu,  topologyServices, topologyJsonProcessor, toscaService, toscaCardinalitiesService, topologyRecoveryServices) {
      // This controller acts as a specific layout for the topology edition.
      $scope.menu = menu;
      $scope.topologyId = $stateParams.archiveId;

      $scope.getShortName = toscaService.simpleName;
      // this allow to avoid file edition in the ui-ace.
      $scope.released = false;

      // Initial load of the topology
      topologyServices.dao.get({ topologyId: $scope.topologyId },
        function(result) {
          if (_.undefined(result.error)) {
            $scope.refreshTopology(result.data, null, true);
            return;
          }
          // case there actually is an error
          var lastOperationId = 'null';
          if (_.undefined($scope.topology) && _.defined(result.data.lastOperationId)) {
            lastOperationId = result.data.lastOperationId;
          } else {
            lastOperationId = $scope.getLastOperationId(true);
          }
          topologyRecoveryServices.handleTopologyRecovery(result.data, $scope.topologyId, lastOperationId).then(function(recoveryResult){
            if (_.definedPath(recoveryResult, 'data')) {
              $scope.refreshTopology(recoveryResult.data, null, true);
              return;
            }
          });
        });

      /**
      * Add bounds information to the requirements and capabilities in the topology based on relationships.
      */
      function fillBounds(topology) {
        _.each(topology.nodeTemplates, function(nodeTemplate) {
          toscaCardinalitiesService.fillRequirementBounds($scope.topology.nodeTypes, nodeTemplate);
          toscaCardinalitiesService.fillCapabilityBounds($scope.topology.nodeTypes, $scope.topology.topology.nodeTemplates, nodeTemplate);
        });
      }

      // Version selection management is below, find here topology update handling
      /**
      * refreshTopology has to be triggered when the topology is updated.
      * Added to the scope as right now every operation returns the full and update topology.
      */
      $scope.refreshTopology = function(topologyDTO, selectedNodeTemplateName, initial) {
        $scope.topology = topologyDTO;
        if(topologyDTO.topology.workspace === 'ALIEN_GLOBAL_WORKSPACE') {
          $scope.workspaces = ['ALIEN_GLOBAL_WORKSPACE'];
        } else {
          $scope.workspaces = [topologyDTO.topology.workspace, 'ALIEN_GLOBAL_WORKSPACE'];
        }
        // Process the topology to enrich it with some additional data
        _.each(topologyDTO.topology.nodeTemplates, function(value, key){
          value.name = key;
        });
        // enrich objects to add maps for the fields that are currently mapped as array of map entries.
        topologyJsonProcessor.process($scope.topology);
        fillBounds($scope.topology.topology);
        _.each($scope.topology.topology.inputs, function(value, key){
          value.inputId = key;
        });

        // trigger refresh event so child scope can update what they need. Initial flag allows to know if this is the initial loading of the topology.
        $scope.$broadcast('topologyRefreshedEvent', {
          topology: topologyDTO,
          initial: initial,
          selectedNodeTemplateName: selectedNodeTemplateName
        });
      };

      $scope.getLastOperationId = function(nullAsString) {
        if(_.get($scope.topology, 'lastOperationIndex')>= 0) {
          return $scope.topology.operations[$scope.topology.lastOperationIndex].id;
        }
        return _.defined(nullAsString) && nullAsString ? 'null' : null;
      };

      var editorResource = $alresource('rest/latest/editor/:topologyId/execute');
      $scope.execute = function(operation, successCallback, errorCallback, selectedNodeTemplateName, isPropertyEdit) {
        operation.previousOperationId = $scope.getLastOperationId();
        // execute operations, create is a post
        return editorResource.create({
          topologyId: $scope.topologyId
        }, angular.toJson(operation), function(result) {
          if(_.defined(result.error) && result.error.code === 860) {
            // Topology recovery
            topologyRecoveryServices.handleTopologyRecovery(result.data, $scope.topologyId, $scope.getLastOperationId(true)).then(function(recoveryResult) {
              if(_.definedPath(recoveryResult, 'data')) {
                $scope.refreshTopology(recoveryResult.data, selectedNodeTemplateName);
                if(_.defined(successCallback)) {
                  successCallback(recoveryResult);
                }
                return;
              }
            });
            return result;
          }

          if(_.undefined(result.error)) {
            if(_.defined(isPropertyEdit)) {
              // If the call is related to a property value edition this may be a complex one and we should not perform full topology override.
              $scope.topology.operations = result.data.operations;
              $scope.topology.lastOperationIndex = result.data.lastOperationIndex;
            } else {
              $scope.refreshTopology(result.data, selectedNodeTemplateName);
            }
          }
          if(_.defined(successCallback)) {
            successCallback(result);
          }
          return result;
        }, function(error) {
          if(_.defined(errorCallback)) {
            errorCallback(error);
          }
          return error;
        }).$promise;
      };

      var editorSaveResource = $alresource('rest/latest/editor/:topologyId');
      $scope.save = function() {
        if($scope.topology.operations.length === 0 || $scope.topology.lastOperationIndex===-1) {
          // nothing to save
          return;
        }
        editorSaveResource.create({
          topologyId: $scope.topologyId,
          lastOperationId: $scope.getLastOperationId(true)
        }, null, function(result) {
          if(_.undefined(result.error)) {
            $scope.refreshTopology(result.data);
          }
        });
      };

      var editorUndoResource = $alresource('rest/latest/editor/:topologyId/undo');
      function undoRedo(at) {
        editorUndoResource.create({
          topologyId: $scope.topologyId,
          at: at,
          lastOperationId: $scope.getLastOperationId(true)
        }, null, function(result) {
          if(_.undefined(result.error)) {
            $scope.refreshTopology(result.data);
            return;
          }

          //case there actually is an error
          topologyRecoveryServices.handleTopologyRecovery(result.data, $scope.topologyId, $scope.getLastOperationId(true)).then(function(recoveryResult){
            if(_.definedPath(recoveryResult, 'data')){
              $scope.refreshTopology(recoveryResult.data);
              return;
            }
          });

        });
      }
      $scope.undo = function() {
        if(0 === ($scope.topology.lastOperationIndex + 1)) {
          return;
        }
        var at = $scope.topology.lastOperationIndex - 1;
        undoRedo(at);
      };
      $scope.redo = function() {
        if($scope.topology.operations.length === ($scope.topology.lastOperationIndex + 1)) {
          return;
        }
        var at = $scope.topology.lastOperationIndex + 1;
        undoRedo(at);
      };

      // -- Begin GIT SECTIONS --
      var gitRemoteResource = $alresource('rest/latest/editor/:topologyId/git/remote');

      // Define if the push/pull buttons are enabled or not
      //
      gitRemoteResource.get({topologyId: $scope.topologyId}).$promise.then(function(response){
        if(_.defined(response.data.remoteUrl)) {
          $scope.isGitValid = true;
        } else {
          $scope.isGitValid = false;
        }
      });

      // GIT SET REMOTE
      //
      $scope.gitRemote = function() {
        var modalInstance = $uibModal.open({
          templateUrl: 'views/topology/editor_git_remote_modal.html',
          controller: 'EditorGitRemoteModalController',
          scope: $scope,
          resolve: {
            remoteGit: ['$alresource', function($alresource) {
              return $alresource('rest/latest/editor/:topologyId/git/remote').get({topologyId: $scope.topologyId}).$promise.then(function(response){
                return response.data;
              });
            }]
          }
        });

        modalInstance.result.then(function(remoteUrl) {
          gitRemoteResource.update({
              topologyId: $scope.topologyId,
              remoteUrl: remoteUrl
            }, null, function() {
              $scope.isGitValid = true;
            });
        });
      };

      // GIT PUSH FUNCTION
      //
      $scope.gitPush = function() {
        var modalInstance = $uibModal.open({
          templateUrl: 'views/topology/editor_git_push_pull_modal.html',
          controller: 'EditorGitPushPullModalController',
          scope: $scope,
          resolve: {
            action: function() {
              return 'PUSH';
            }
          }
        });
        modalInstance.result.then(function(gitPushPullForm) {
          var gitPushResource= $alresource('rest/latest/editor/:topologyId/git/push');
          gitPushResource.update({topologyId: $scope.topologyId, remoteBranch: gitPushPullForm.remoteBranch}, angular.toJson(gitPushPullForm.credentials), function(response) {
            if(_.undefined(response.error)) {
              toaster.pop('success', $translate.instant('EDITOR.GIT.OPERATIONS.PUSH.TITLE'), $translate.instant('EDITOR.GIT.OPERATIONS.PUSH.SUCCESS_MSGE'), 4000, 'trustedHtml', null);
            }
            console.debug('pushed');
          });
        });
      };

      $scope.showParsingErrors = function (response) {
        $uibModal.open({
          templateUrl: 'views/topology/topology_parsing_error.html',
          controller: ['$scope', '$uibModalInstance', 'uploadInfo',
            function ($scope, $uibModalInstance, uploadInfo) {
              $scope.uploadInfo = uploadInfo;
              $scope.close = function () {
                $uibModalInstance.dismiss('close');
              };
            }],
          resolve: {
            uploadInfo: function() {
              return {
                errors: response.data.errors,
                infoType: 'danger'
              };
            }
          },
          size: 'lg'
        });
      };

      // GIT PULL FUNCTION
      //
      $scope.gitPull = function() {
        var modalInstance = $uibModal.open({
          templateUrl: 'views/topology/editor_git_push_pull_modal.html',
          controller: 'EditorGitPushPullModalController',
          scope: $scope,
          resolve: {
            action: function() {
              return 'PULL';
            }
          }
        });
        modalInstance.result.then(function(gitPushPullForm) {
          var gitPullResource= $alresource('rest/latest/editor/:topologyId/git/pull');
          gitPullResource.update({topologyId: $scope.topologyId, remoteBranch: gitPushPullForm.remoteBranch}, angular.toJson(gitPushPullForm.credentials), function(response) {
            if(_.undefined(response.error)){
              $scope.refreshTopology(response.data);
              toaster.pop('success', $translate.instant('EDITOR.GIT.OPERATIONS.PULL.TITLE'), $translate.instant('EDITOR.GIT.OPERATIONS.PULL.SUCCESS_MSGE'), 4000, 'trustedHtml', null);
            } else {
              $scope.showParsingErrors(response);
            }
          });
        });
      };
      // -- End of GIT SECTIONS --

      // key binding
      hotkeys.bindTo($scope)
        .add({
          combo: 'mod+s',
          description: 'save and commit the operations.',
          callback: function(e) {
            $scope.save();
            if(e.preventDefault) {
              e.preventDefault();
            } else {
              e.returnValue = false;
            }
          }
        })
        .add({
          combo: 'mod+z',
          description: 'undo the last operation.',
          callback: function(e) {
            $scope.undo();
            if(e.preventDefault) {
              e.preventDefault();
            } else {
              e.returnValue = false;
            }
          }
        })
        .add ({
          combo: 'mod+y',
          description: 'redo the last operation.',
          callback: function(e) {
            $scope.redo();
            if(e.preventDefault) {
              e.preventDefault();
            } else {
              e.returnValue = false;
            }
          }
        });

      var AskSaveTopologyController = ['$scope', '$uibModalInstance',
        function($scope, $uibModalInstance) {
          $scope.save = function () {
            $uibModalInstance.close();
          };

          $scope.doNotSave = function () {
            $uibModalInstance.dismiss();
          };
        }];

      $scope.$on('$stateChangeStart',
        function(event, toState, toParams, fromState) {
          if(_.undefined($scope.topology) || _.undefined($scope.topology.operations) || $scope.topology.operations.length === 0 || $scope.topology.lastOperationIndex === -1) {
            // nothing to save
            return;
          }
          var getStateBasePath = function (state) {
            var lastIndexOfPoint = state.lastIndexOf('.');
            if(lastIndexOfPoint >= 0) {
              return state.substring(0, lastIndexOfPoint);
            } else {
              return state;
            }
          };
          if(getStateBasePath(toState.name) === getStateBasePath(fromState.name)) {
            // We are always inside editor
            return;
          }
          if($scope.skipStateChangeStart) {
            // Just skip once
            $scope.skipStateChangeStart = false;
            return;
          }
          event.preventDefault();
          var modalInstance = $uibModal.open({
            templateUrl: 'views/topology/editor_ask_save.html',
            controller: AskSaveTopologyController
          });

          var proceedToStateChange = function () {
            // Don't intercept the next one
            $scope.skipStateChangeStart = true;
            // Save
            $state.go(toState, toParams);
          };

          modalInstance.result.then(function() {
            $scope.save();
            proceedToStateChange();
          }, function() {
            proceedToStateChange();
          });
        });
      }
    ]);
  }
); // define
<commit_msg>fixed save modal pupping up twice when leaving topology editor with pending operations<commit_after>/**
* Main controller for topology edition.
* It loads topology and manage common server communication and topology refresh through common parent scope.
*/
define(function (require) {
  'use strict';

  var modules = require('modules');
  var _ = require('lodash');
  var angular = require('angular');

  require('scripts/topology/controllers/topology');
  require('scripts/topology/controllers/editor_browser');
  require('scripts/topology/controllers/editor_workflow');
  require('scripts/topology/controllers/editor_history');
  require('scripts/topology/controllers/editor_git_modal');
  require('scripts/topology/controllers/editor_validation');
  require('scripts/topology/controllers/editor_inputs_variables');

  require('scripts/tosca/services/tosca_cardinalities_service');
  require('scripts/topology/services/topology_json_processor');
  require('scripts/topology/services/topology_recovery_service');
  require('scripts/topology/services/topology_services');

  // manage websockets for topology editor
  // require('scripts/topology/services/topology_editor_events_services');
  require('scripts/common/directives/parsing_errors');

  modules.get('a4c-topology-editor', ['a4c-common', 'ui.bootstrap', 'a4c-tosca', 'a4c-styles', 'cfp.hotkeys']).controller('TopologyEditorCtrl',
    ['$scope', '$state', '$stateParams', '$alresource', '$uibModal', '$translate', 'toaster', 'hotkeys', 'menu', 'topologyServices', 'topologyJsonProcessor', 'toscaService', 'toscaCardinalitiesService', 'topologyRecoveryServices',
    function($scope, $state, $stateParams, $alresource, $uibModal, $translate, toaster, hotkeys, menu,  topologyServices, topologyJsonProcessor, toscaService, toscaCardinalitiesService, topologyRecoveryServices) {
      // This controller acts as a specific layout for the topology edition.
      $scope.menu = menu;
      $scope.topologyId = $stateParams.archiveId;

      $scope.getShortName = toscaService.simpleName;
      // this allow to avoid file edition in the ui-ace.
      $scope.released = false;

      // Initial load of the topology
      topologyServices.dao.get({ topologyId: $scope.topologyId },
        function(result) {
          if (_.undefined(result.error)) {
            $scope.refreshTopology(result.data, null, true);
            return;
          }
          // case there actually is an error
          var lastOperationId = 'null';
          if (_.undefined($scope.topology) && _.defined(result.data.lastOperationId)) {
            lastOperationId = result.data.lastOperationId;
          } else {
            lastOperationId = $scope.getLastOperationId(true);
          }
          topologyRecoveryServices.handleTopologyRecovery(result.data, $scope.topologyId, lastOperationId).then(function(recoveryResult){
            if (_.definedPath(recoveryResult, 'data')) {
              $scope.refreshTopology(recoveryResult.data, null, true);
              return;
            }
          });
        });

      /**
      * Add bounds information to the requirements and capabilities in the topology based on relationships.
      */
      function fillBounds(topology) {
        _.each(topology.nodeTemplates, function(nodeTemplate) {
          toscaCardinalitiesService.fillRequirementBounds($scope.topology.nodeTypes, nodeTemplate);
          toscaCardinalitiesService.fillCapabilityBounds($scope.topology.nodeTypes, $scope.topology.topology.nodeTemplates, nodeTemplate);
        });
      }

      // Version selection management is below, find here topology update handling
      /**
      * refreshTopology has to be triggered when the topology is updated.
      * Added to the scope as right now every operation returns the full and update topology.
      */
      $scope.refreshTopology = function(topologyDTO, selectedNodeTemplateName, initial) {
        $scope.topology = topologyDTO;
        if(topologyDTO.topology.workspace === 'ALIEN_GLOBAL_WORKSPACE') {
          $scope.workspaces = ['ALIEN_GLOBAL_WORKSPACE'];
        } else {
          $scope.workspaces = [topologyDTO.topology.workspace, 'ALIEN_GLOBAL_WORKSPACE'];
        }
        // Process the topology to enrich it with some additional data
        _.each(topologyDTO.topology.nodeTemplates, function(value, key){
          value.name = key;
        });
        // enrich objects to add maps for the fields that are currently mapped as array of map entries.
        topologyJsonProcessor.process($scope.topology);
        fillBounds($scope.topology.topology);
        _.each($scope.topology.topology.inputs, function(value, key){
          value.inputId = key;
        });

        // trigger refresh event so child scope can update what they need. Initial flag allows to know if this is the initial loading of the topology.
        $scope.$broadcast('topologyRefreshedEvent', {
          topology: topologyDTO,
          initial: initial,
          selectedNodeTemplateName: selectedNodeTemplateName
        });
      };

      $scope.getLastOperationId = function(nullAsString) {
        if(_.get($scope.topology, 'lastOperationIndex')>= 0) {
          return $scope.topology.operations[$scope.topology.lastOperationIndex].id;
        }
        return _.defined(nullAsString) && nullAsString ? 'null' : null;
      };

      var editorResource = $alresource('rest/latest/editor/:topologyId/execute');
      $scope.execute = function(operation, successCallback, errorCallback, selectedNodeTemplateName, isPropertyEdit) {
        operation.previousOperationId = $scope.getLastOperationId();
        // execute operations, create is a post
        return editorResource.create({
          topologyId: $scope.topologyId
        }, angular.toJson(operation), function(result) {
          if(_.defined(result.error) && result.error.code === 860) {
            // Topology recovery
            topologyRecoveryServices.handleTopologyRecovery(result.data, $scope.topologyId, $scope.getLastOperationId(true)).then(function(recoveryResult) {
              if(_.definedPath(recoveryResult, 'data')) {
                $scope.refreshTopology(recoveryResult.data, selectedNodeTemplateName);
                if(_.defined(successCallback)) {
                  successCallback(recoveryResult);
                }
                return;
              }
            });
            return result;
          }

          if(_.undefined(result.error)) {
            if(_.defined(isPropertyEdit)) {
              // If the call is related to a property value edition this may be a complex one and we should not perform full topology override.
              $scope.topology.operations = result.data.operations;
              $scope.topology.lastOperationIndex = result.data.lastOperationIndex;
            } else {
              $scope.refreshTopology(result.data, selectedNodeTemplateName);
            }
          }
          if(_.defined(successCallback)) {
            successCallback(result);
          }
          return result;
        }, function(error) {
          if(_.defined(errorCallback)) {
            errorCallback(error);
          }
          return error;
        }).$promise;
      };

      var editorSaveResource = $alresource('rest/latest/editor/:topologyId');
      $scope.save = function() {
        if($scope.topology.operations.length === 0 || $scope.topology.lastOperationIndex===-1) {
          // nothing to save
          return;
        }
        editorSaveResource.create({
          topologyId: $scope.topologyId,
          lastOperationId: $scope.getLastOperationId(true)
        }, null, function(result) {
          if(_.undefined(result.error)) {
            $scope.refreshTopology(result.data);
          }
        });
      };

      var editorUndoResource = $alresource('rest/latest/editor/:topologyId/undo');
      function undoRedo(at) {
        editorUndoResource.create({
          topologyId: $scope.topologyId,
          at: at,
          lastOperationId: $scope.getLastOperationId(true)
        }, null, function(result) {
          if(_.undefined(result.error)) {
            $scope.refreshTopology(result.data);
            return;
          }

          //case there actually is an error
          topologyRecoveryServices.handleTopologyRecovery(result.data, $scope.topologyId, $scope.getLastOperationId(true)).then(function(recoveryResult){
            if(_.definedPath(recoveryResult, 'data')){
              $scope.refreshTopology(recoveryResult.data);
              return;
            }
          });

        });
      }
      $scope.undo = function() {
        if(0 === ($scope.topology.lastOperationIndex + 1)) {
          return;
        }
        var at = $scope.topology.lastOperationIndex - 1;
        undoRedo(at);
      };
      $scope.redo = function() {
        if($scope.topology.operations.length === ($scope.topology.lastOperationIndex + 1)) {
          return;
        }
        var at = $scope.topology.lastOperationIndex + 1;
        undoRedo(at);
      };

      // -- Begin GIT SECTIONS --
      var gitRemoteResource = $alresource('rest/latest/editor/:topologyId/git/remote');

      // Define if the push/pull buttons are enabled or not
      //
      gitRemoteResource.get({topologyId: $scope.topologyId}).$promise.then(function(response){
        if(_.defined(response.data.remoteUrl)) {
          $scope.isGitValid = true;
        } else {
          $scope.isGitValid = false;
        }
      });

      // GIT SET REMOTE
      //
      $scope.gitRemote = function() {
        var modalInstance = $uibModal.open({
          templateUrl: 'views/topology/editor_git_remote_modal.html',
          controller: 'EditorGitRemoteModalController',
          scope: $scope,
          resolve: {
            remoteGit: ['$alresource', function($alresource) {
              return $alresource('rest/latest/editor/:topologyId/git/remote').get({topologyId: $scope.topologyId}).$promise.then(function(response){
                return response.data;
              });
            }]
          }
        });

        modalInstance.result.then(function(remoteUrl) {
          gitRemoteResource.update({
              topologyId: $scope.topologyId,
              remoteUrl: remoteUrl
            }, null, function() {
              $scope.isGitValid = true;
            });
        });
      };

      // GIT PUSH FUNCTION
      //
      $scope.gitPush = function() {
        var modalInstance = $uibModal.open({
          templateUrl: 'views/topology/editor_git_push_pull_modal.html',
          controller: 'EditorGitPushPullModalController',
          scope: $scope,
          resolve: {
            action: function() {
              return 'PUSH';
            }
          }
        });
        modalInstance.result.then(function(gitPushPullForm) {
          var gitPushResource= $alresource('rest/latest/editor/:topologyId/git/push');
          gitPushResource.update({topologyId: $scope.topologyId, remoteBranch: gitPushPullForm.remoteBranch}, angular.toJson(gitPushPullForm.credentials), function(response) {
            if(_.undefined(response.error)) {
              toaster.pop('success', $translate.instant('EDITOR.GIT.OPERATIONS.PUSH.TITLE'), $translate.instant('EDITOR.GIT.OPERATIONS.PUSH.SUCCESS_MSGE'), 4000, 'trustedHtml', null);
            }
            console.debug('pushed');
          });
        });
      };

      $scope.showParsingErrors = function (response) {
        $uibModal.open({
          templateUrl: 'views/topology/topology_parsing_error.html',
          controller: ['$scope', '$uibModalInstance', 'uploadInfo',
            function ($scope, $uibModalInstance, uploadInfo) {
              $scope.uploadInfo = uploadInfo;
              $scope.close = function () {
                $uibModalInstance.dismiss('close');
              };
            }],
          resolve: {
            uploadInfo: function() {
              return {
                errors: response.data.errors,
                infoType: 'danger'
              };
            }
          },
          size: 'lg'
        });
      };

      // GIT PULL FUNCTION
      //
      $scope.gitPull = function() {
        var modalInstance = $uibModal.open({
          templateUrl: 'views/topology/editor_git_push_pull_modal.html',
          controller: 'EditorGitPushPullModalController',
          scope: $scope,
          resolve: {
            action: function() {
              return 'PULL';
            }
          }
        });
        modalInstance.result.then(function(gitPushPullForm) {
          var gitPullResource= $alresource('rest/latest/editor/:topologyId/git/pull');
          gitPullResource.update({topologyId: $scope.topologyId, remoteBranch: gitPushPullForm.remoteBranch}, angular.toJson(gitPushPullForm.credentials), function(response) {
            if(_.undefined(response.error)){
              $scope.refreshTopology(response.data);
              toaster.pop('success', $translate.instant('EDITOR.GIT.OPERATIONS.PULL.TITLE'), $translate.instant('EDITOR.GIT.OPERATIONS.PULL.SUCCESS_MSGE'), 4000, 'trustedHtml', null);
            } else {
              $scope.showParsingErrors(response);
            }
          });
        });
      };
      // -- End of GIT SECTIONS --

      // key binding
      hotkeys.bindTo($scope)
        .add({
          combo: 'mod+s',
          description: 'save and commit the operations.',
          callback: function(e) {
            $scope.save();
            if(e.preventDefault) {
              e.preventDefault();
            } else {
              e.returnValue = false;
            }
          }
        })
        .add({
          combo: 'mod+z',
          description: 'undo the last operation.',
          callback: function(e) {
            $scope.undo();
            if(e.preventDefault) {
              e.preventDefault();
            } else {
              e.returnValue = false;
            }
          }
        })
        .add ({
          combo: 'mod+y',
          description: 'redo the last operation.',
          callback: function(e) {
            $scope.redo();
            if(e.preventDefault) {
              e.preventDefault();
            } else {
              e.returnValue = false;
            }
          }
        });

      var AskSaveTopologyController = ['$scope', '$uibModalInstance',
        function($scope, $uibModalInstance) {
          $scope.save = function () {
            $uibModalInstance.close();
          };

          $scope.doNotSave = function () {
            $uibModalInstance.dismiss();
          };
        }];

      $scope.$on('$stateChangeStart',
        function(event, toState, toParams, fromState) {
          if(_.undefined($scope.topology) || _.undefined($scope.topology.operations) || $scope.topology.operations.length === 0 || $scope.topology.lastOperationIndex === -1) {
            // nothing to save
            return;
          }
          var getStateBasePath = function (state) {
            var lastIndexOfPoint = state.lastIndexOf('.');
            if(lastIndexOfPoint >= 0) {
              return state.substring(0, lastIndexOfPoint);
            } else {
              return state;
            }
          };
          if(getStateBasePath(toState.name) === getStateBasePath(fromState.name)) {
            // We are always inside editor
            return;
          }
          if($scope.skipStateChangeStart) {
            // Just skip once
            return;
          }
          event.preventDefault();
          var modalInstance = $uibModal.open({
            templateUrl: 'views/topology/editor_ask_save.html',
            controller: AskSaveTopologyController
          });

          var proceedToStateChange = function () {
            // Don't intercept the next one
            $scope.skipStateChangeStart = true;
            // Save
            $state.go(toState, toParams);
          };

          modalInstance.result.then(function() {
            $scope.save();
            proceedToStateChange();
          }, function() {
            proceedToStateChange();
          });
        });
      }
    ]);
  }
); // define
<|endoftext|>


<commit_before><commit_msg>fix p5.soundFile.save() (#406)<commit_after><|endoftext|>


<commit_before>define(['donation_announcer/fundrazr/parse'], function(parse) {
  var donations = "https://fundrazr.com/api/campaigns/811ws3/highlights?v=1&max-results=25&order=newest-first"
  //var donations = "https://fundrazr.com/api/campaigns/4xZAc/highlights?v=1&max-results=25&order=newest-first"

  var update = function(url) {
    return $.getJSON(url || donations, {_: new Date().getTime()}).then(parse.process)
  }

  return {
    donations: donations,
    update: update,
    process: parse.process,
  }
})
<commit_msg>more approprite fundrazr url<commit_after>define(['donation_announcer/fundrazr/parse'], function(parse) {
  var donations = "https://fundrazr.com/api/campaigns/811ws3/activities?v=1&max-results=25&order=newest-first"
  //var donations = "https://fundrazr.com/api/campaigns/4xZAc/activities?v=1&max-results=25&order=newest-first"

  var update = function(url) {
    return $.getJSON(url || donations, {_: new Date().getTime()}).then(parse.process)
  }

  return {
    donations: donations,
    update: update,
    process: parse.process,
  }
})
<|endoftext|>


<commit_before>// loads all the recipes when the recipe loads
async function loadRecipes() {
  loadUserRecipes();
}

async function loadUserRecipes() {
  const response = await fetch('/list-private-recipes');
  const recipes = await response.json();

  let recipesDiv = document.getElementById("user-recipes");
  recipesDiv.innerHTML = "";

  if (Object.keys(recipes)) {
    if (Object.keys(recipes).length == 0) {
      noRecipes("user-recipes", "You have not uploaded any recipes yet.");
    }
    else {
      for (let key of Object.keys(recipes)) {
        let value = recipes[key];
        createRecipeCard("user-recipes", value);

        // since these cards are the user recipe cards, they need edit/delete buttons
        let elementsToAddToImageDiv = [
          createElement("button", "", {"class": "card-button icon top left far fa-trash-alt"}),
          createElement("button", "", {"class": "card-button icon top right fa fa-edit"}),
        ];
        let imageDivs = document.getElementsByClassName("image-div");
        for (let i = 0; i < imageDivs.length; i++) {
          elementsToAddToImageDiv.forEach(elem => imageDivs[i].appendChild(elem));
        }
      }
      addDeleteFunctionality(recipes);
      // TODO: add edit functionality
    }
  }
}

function addDeleteFunctionality(recipes){
  // functionality of delete button
  const deleteButtons = document.getElementsByClassName('fa-trash-alt');
  const recipeCards = document.getElementsByClassName('recipe-card');
  // there are as many delete buttons as there are recipe cards
  for (let i = 0; i < deleteButtons.length; i++) {
    let recipe = recipes[i];
    let recipeCard = recipeCards[i];
    deleteButtons[i].addEventListener('click', () => {
      const deleteConfirmed = confirm("Are you sure you want to delete the "
        + recipe.name + " recipe?\nThis action cannot be undone!");
      if (deleteConfirmed) {
        deleteRecipe(recipe);
        // Remove the recipe from the DOM.
        recipeCard.remove();
      }
      if (recipeCards.length == 0) {
        noRecipes("user-recipes", "You have not uploaded any recipes yet.");
      }
    });
  }
}

// takes in div id and message
noRecipes = (divID, message) => {
  let recipesDiv = document.getElementById(divID);
  recipesDiv.innerText = message;
  recipesDiv.style.height = "100px";
}

// opens the recipe form modal
function openModal() {
  document.getElementById("recipe-modal").style.display = "block";
  fetchBlobstoreUrl();
}

// not quite sure what this does; something with blobs
function fetchBlobstoreUrl() {
  fetch('/blobstore-upload-url')
      .then((response) => {
        return response.text();
      })
      .then((imageUploadUrl) => {
        const messageForm = document.getElementById('recipe-form');
        messageForm.action = imageUploadUrl;
      });
}

// closes the recipe form modal
function closeModal() {
  document.getElementById("recipe-modal").style.display = "none";
}

// tells the server to delete the recipe.
// input is a recipe js object
function deleteRecipe(recipe) {
  const params = new URLSearchParams();
  params.append("id", recipe.id);
  fetch("/delete-recipe", {method: "POST", body: params});
}<commit_msg>add comments<commit_after>// constants
const NO_PLANNER_RECIPES = "You have not added any recipes to your planner yet.";
const NO_COOKBOOK_RECIPES = "You have not added any recipes to your cookbook yet.";
const NO_USER_RECIPES = "You have not uploaded any recipes yet.";

// loads all the recipes when the recipe loads
async function loadRecipes() {
  loadUserRecipes();
}

async function loadUserRecipes() {
  const response = await fetch('/list-private-recipes');
  const recipes = await response.json();

  let recipesDiv = document.getElementById("user-recipes");
  recipesDiv.innerHTML = "";

  if (Object.keys(recipes)) {
    if (Object.keys(recipes).length == 0) {
      noRecipes("user-recipes", NO_USER_RECIPES);
    }
    else {
      for (let key of Object.keys(recipes)) {
        let value = recipes[key];
        createRecipeCard("user-recipes", value);

        // since these cards are the user recipe cards, they need edit/delete buttons
        let elementsToAddToImageDiv = [
          createElement("button", "", {"class": "card-button icon top left far fa-trash-alt"}),
          createElement("button", "", {"class": "card-button icon top right fa fa-edit"}),
        ];
        let imageDivs = document.getElementsByClassName("image-div");
        for (let i = 0; i < imageDivs.length; i++) {
          elementsToAddToImageDiv.forEach(elem => imageDivs[i].appendChild(elem));
        }
      }
      addDeleteFunctionality(recipes);
      // TODO: add edit functionality
    }
  }
}

// adds delete functionality to the delete button in the recipe cards
function addDeleteFunctionality(recipes){
  const deleteButtons = document.getElementsByClassName('fa-trash-alt');
  const recipeCards = document.getElementsByClassName('recipe-card');
  // there are as many delete buttons as there are recipe cards
  for (let i = 0; i < deleteButtons.length; i++) {
    let recipe = recipes[i];
    let recipeCard = recipeCards[i];
    deleteButtons[i].addEventListener('click', () => {
      const deleteConfirmed = confirm("Are you sure you want to delete the "
        + recipe.name + " recipe?\nThis action cannot be undone!");
      if (deleteConfirmed) {
        deleteRecipe(recipe);
        // Remove the recipe from the DOM.
        recipeCard.remove();
      }
      if (recipeCards.length == 0) {
        noRecipes("user-recipes", NO_USER_RECIPES);
      }
    });
  }
}

// takes in div id and message
// makes the recipes container bigger and gives it the message
// to use when there are no recipes in planner/cookbook/user-recipes
noRecipes = (divID, message) => {
  let recipesDiv = document.getElementById(divID);
  recipesDiv.innerText = message;
  recipesDiv.style.height = "100px";
}

// opens the recipe form modal
function openModal() {
  document.getElementById("recipe-modal").style.display = "block";
  fetchBlobstoreUrl();
}

// not quite sure what this does; something with blobs
function fetchBlobstoreUrl() {
  fetch('/blobstore-upload-url')
      .then((response) => {
        return response.text();
      })
      .then((imageUploadUrl) => {
        const messageForm = document.getElementById('recipe-form');
        messageForm.action = imageUploadUrl;
      });
}

// closes the recipe form modal
function closeModal() {
  document.getElementById("recipe-modal").style.display = "none";
}

// tells the server to delete the recipe.
// input is a recipe js object
function deleteRecipe(recipe) {
  const params = new URLSearchParams();
  params.append("id", recipe.id);
  fetch("/delete-recipe", {method: "POST", body: params});
}<|endoftext|>


<commit_before>define([
    'jquery',
    'underscore',
    'backbone',
    'joint',
    'models/ProjectModel'

], function ($, _, Backbone, joint, ProjectModel) {

    /**
     * @classdesc `ProjectView` represents the drawing area.
     * It can be the main class diagram or a specific method diagram.
     *
     * @module client.view
     * @name ProjectView
     * @class ProjectView
     * @extends {Backbone.View}
     */
    var ProjectView = Backbone.View.extend({

        /**
         * details (?)
         * @name ProjectView#det
         * @type {Object}
         */
        det: {},

        /**
         * The main drawing area, contained in the HTML `#paper` div.
         * @name ProjectView#paper
         * @type {joint.dia.Paper}
         */
        paper: {},

        /**
         * Initializes `model` with a new `ProjectModel`;
         * initializes `paper` with a new `joint.dia.Paper` object;
         * links mouse events to the right actions
         * [...]
         * @name ProjectView#initialize
         * @function
         */
        initialize: function () {
            this.model = ProjectModel;//new ProjectModel();
            this.paper = new joint.dia.Paper({
                el: $('#paper'),
                model: this.model.graph,
                width: 1500,
                height: 1000,
                gridSize: 6,
                drawGrid: true,
                elementView: joint.shapes.uml.ClassDiagramElementView,

                highlighting: {
                    'default': {
                        name: 'stroke',
                        options: {
                            padding: 3
                        }
                    },

                },

                //clickThreshold: 1,
                linkView: joint.dia.LinkView.extend({
                    pointerdblclick: function (evt, x, y) {
                        if (V(evt.target).hasClass('connection') || V(evt.target).hasClass('connection-wrap')) {
                            this.addVertex({x: x, y: y});
                        }
                    },
                    /*pointerclick: function (evt, x, y) {
                        console.log("you clicked a link");
                        // codice per dire a detailsview che è cambiato qualcosa
                    }*/
                }),

                selectedCell: null,

                interactive: function (cellView) {
                    if (cellView.model instanceof joint.dia.Link) {
                        // Disable the default vertex add functionality on pointerdown.
                        return {vertexAdd: false};
                    }

                    return true;
                },

            });

            //_.bindAll();

            // per qualche satanica ragione cell:pointerclick non funziona
            /// e pointerup funziona. FML.
/*
            this.paper.on('cell:pointerclick', function (cellView, evt, x, y)
            {

            });*/

            this.paper.on('cell:pointerdown', function(cellView, evt, x, y) {

                var cell = cellView.model;

                if (!cell.get('embeds') || cell.get('embeds').length === 0) {
                    // Show the dragged element above all the other cells (except when the
                    // element is a parent).
                    //cell.toFront();
                }

                if (cell.get('parent')) {
                    this.model.getCell(cell.get('parent')).unembed(cell);
                }

            });

            var pointerUpFunction = function (cellView, evt, x, y) {

                var changed = false;
                // EMBED e selectedcell
                if(cellView)
                {
                    var cell = cellView.model;
                    var cellViewsBelow = this.findViewsFromPoint(cell.getBBox().center());

                    if (cellViewsBelow.length) {
                        // Note that the findViewsFromPoint() returns the view for the `cell` itself.

                        // ho modificato il comportamento di default descritto nelle API perché sennò prendeva sempre il
                        /// primo trovato (_.find).
                        // prendendo l'ultimo sono sicuro che sia quello più interessante per l'user.

                        var index = _.findLastIndex(cellViewsBelow, function(c) { return c.model.id !== cell.id });
                        cellViewBelow = cellViewsBelow[index];

                        // Prevent recursive embedding.
                        if (cellViewBelow && cellViewBelow.model.get('parent') !== cell.id) {
                            cellViewBelow.model.embed(cell);
                        }
                    }
                    if (this.selectedCell != cellView.model) {
                        changed = true;
                        this.selectedCell = cellView.model;
                        if (true) {//cellView.model instanceof joint.shapes.uml.ClassDiagramElement) {
                            this.trigger("changed-cell");
                            console.log("ho cambiato la selectedcell");
                            console.log(this.selectedCell);

                        }

                    }
                }

                //var g = this.model.options.graphs[this.model.options.currentindex] ;

                //console.log(this);
                //var g = this.model.options.graphs;

                var g = this.model.attributes.cells.models;
                //var g = ;//this.getCurrentGraph();

                var curr = this.selectedCell;

                //console.log(g);
                /*
                 var xyz = Array.from(g);
                 console.log(xyz);
                 console.log(typeof xyz);

                 var index = xyz.findIndex(curr);
                 */

                //console.log("questa è la cella selezionata: ");
                //console.log(curr);

if(changed) {

    var move = function (a, old_index, new_index) {
        if (new_index >= a.length) {
            var k = new_index - a.length;
            while ((k--) + 1) {
                a.push(undefined);
            }
        }
        a.splice(new_index, 0, a.splice(old_index, 1)[0]);
        return a; // for testing purposes
    };

    var currentIndex = g.indexOf(curr);//curr.get("index"); // è necessario cercare a che indice vorrebbe mettere la cosa

    //console.log(g);

    // controllo se  la cella è andata in basso, oltre altri blocchi
    // nextindex è la prossima non embeddata

    var nextIndex = currentIndex + 1;

    while (nextIndex <= g.length - 1 && g[nextIndex].getAncestors().indexOf(g[currentIndex]) != -1) {
        //console.log(g[currentIndex]);
        //console.log(g[nextIndex].getAncestors());
        nextIndex++;
        //console.log("yup");
    }

    if (nextIndex <= g.length - 1 && curr.get("position").y > g[nextIndex].get("position").y) {
        while (nextIndex <= g.length - 1 && curr.get("position").y > g[nextIndex].get("position").y) {
            nextIndex++;
        }
        if (nextIndex != currentIndex + 1) {
            var figli = g[currentIndex].getEmbeddedCells();
            move(g, currentIndex, nextIndex - 1);
            //console.log("sposto ");
            //console.log(currentIndex);
            //console.log(nextIndex-1);
            for (i = 0; i < figli.length; i++) {
                move(g, currentIndex, nextIndex - 1 + i);
                //console.log("sposto ");
                //console.log(currentIndex);
                //console.log(nextIndex-1+i);

            }
        }
        //console.log("finito di spostare");
        //console.log(currentIndex);
        //console.log(nextIndex);
    }

    // controllo se la cella è andata in altro, oltre altri blocchi
    var prevIndex = currentIndex - 1;

    //while(g[currentIndex] in g[nextIndex].getAncestors()) {nextIndex++;}

    if (prevIndex >= 0 && curr.get("position").y < g[prevIndex].get("position").y) {
        while (prevIndex >= 0 && curr.get("position").y < g[prevIndex].get("position").y) {
            //console.log(curr.get("position").y );
            // console.log(g[prevIndex].get("position").y);
            prevIndex--;
            //console.log(prevIndex);

        }
        if (prevIndex != currentIndex - 1) {
            var figli = g[currentIndex].getEmbeddedCells();
            move(g, currentIndex, prevIndex + 1);
            //console.log("sposto ");
            //console.log(currentIndex);
            //console.log(prevIndex+1);

            for (i = 1; i <= figli.length; i++) {
                move(g, currentIndex + i, prevIndex + i + 1);
                //console.log("sposto ");
                //console.log(currentIndex+i);
                //console.log(prevIndex+1+i);

            }
        }

    }

    /*
     if(index+1<=g.length-1 && curr.get("position").y > g[index+1].get("position").y)
     {
     //curr.set("index", index+1);
     // g[index+1].set("index", index); // -1+1 mi raccomando
     //console.log(g);

     move(g,index,index+1);
     // console.log(g);

     console.log("sposto>");
     console.log(index) ;
     }*/

    // sarebbe >=1 ma c'è ancora il link in mezzo (senza sarebbe 0)
    /*
     if(index-1>=1 && curr.get("position").y < g[index-1].get("position").y )
     {
     ///curr.set("index", index-1);
     // g[index-1].set("index", index); // -1+1 mi raccomando
     //console.log(g);

     move(g, index,index-1);
     // console.log(g);
     console.log("sposto<");
     console.log(index) ;

     }*/

}

    var offsetY = 50;

    for(i=0;i<g.length;i++)
    {
        var hidden = false;/*
     _.each(g[i].getAncestors(), function (el) {
     if(!el.get("expanded"))
     {
     hidden = true; return;
     }
     }*/

        g[i].getAncestors().every(function (el) {
            if(!el.get("expanded"))
            {
                hidden = true;
                g[i].set("hidden", true);
            } return !hidden; // se every ritorna false, esce dal loop (per efficienza)
        });

        if(!hidden) {
            g[i].set("hidden", false);
            g[i].attributes.offsetY = offsetY;

            if(!g[i].get("expanded"))
            {
                offsetY += 50;// se è ridotto ho bisogno di meno spazio
            }

            else
            {
                offsetY += 100;
            }

        }
        else
        {
            g[i].set("hidden", true);
        }

    }

    var l = g.length;
    //console.log(g);
    //console.log(l);

    for(ii = 0; ii<l; ii++)
    {

        if(g[ii].get("type") == "uml.ActivityDiagramElement")
        {
            if(false)//g[i].get("hidden"))
            {
                this.removeView(g[ii]);
            }
            else
            {
                g[ii].updateRectangles();
                this.removeView(g[ii]);
                this.renderView(g[ii]); // per qualche ragione è necessario..
            }
        }

        else {
            console.log(g[ii]);
            console.log("questo no :(");
        }

    }
    ///this.trigger("uml-update");

    //this.removeView(g[1]);
    //this.renderView(g[1]); // per qualche ragione è necessario..

    //this.trigger("uml-update");
    /*
     _.each(g, function(el){
     console.log(el);
     el.updateRectangles();
     });
     */

    //console.log(cellView.model.getClassName());

            };

            this.paper.on('cell:pointermove', function(cellView) {
                var cell = cellView.model;

                var cellViewsBelow = this.findViewsFromPoint(cell.getBBox().center());

                if (cellViewsBelow.length> 0) {
                    // Note that the findViewsFromPoint() returns the view for the `cell` itself.

                    // ho modificato il comportamento di default descritto nelle API perché sennò prendeva sempre il
                    /// primo trovato (_.find).
                    // prendendo l'ultimo sono sicuro che sia quello più interessante per l'user.

                    var index = _.findLastIndex(cellViewsBelow, function (c) {
                        return c.model.id !== cell.id
                    });

                    if(index!=-1)
                    {
                        cellViewBelow = cellViewsBelow[index];
                        cellViewBelow.highlight();

                    }

                }

            });
            this.paper.on('cell:pointerup', pointerUpFunction);/*
            this.paper.on('blank:pointerdown', function (evt, x, y) {
                //console.log(evt);
               //console.log(x,y);
            });*/

            this.model.addInitialsCells();
            //this.trigger("cell:pointerup"); // come faccio a triggerare la cosa che ho definito sopra?

            console.log(this);
            // pointerUpFunction();

        },

        /**
         * ...
         * @name ProjectView#switch
         * @function
         * @param {number} index which graph to swith to
         * @param {?} selectedCell ?
         */
        switch: function (index, selectedCell) {
            this.model.switchToGraph(index, selectedCell);
            this.trigger("Switchgraph");
        }
    });
    return new ProjectView;
});
<commit_msg>highlight corretto<commit_after>define([
    'jquery',
    'underscore',
    'backbone',
    'joint',
    'models/ProjectModel'

], function ($, _, Backbone, joint, ProjectModel) {

    /**
     * @classdesc `ProjectView` represents the drawing area.
     * It can be the main class diagram or a specific method diagram.
     *
     * @module client.view
     * @name ProjectView
     * @class ProjectView
     * @extends {Backbone.View}
     */
    var ProjectView = Backbone.View.extend({

        /**
         * details (?)
         * @name ProjectView#det
         * @type {Object}
         */
        det: {},

        /**
         * The main drawing area, contained in the HTML `#paper` div.
         * @name ProjectView#paper
         * @type {joint.dia.Paper}
         */
        paper: {},

        /**
         * Initializes `model` with a new `ProjectModel`;
         * initializes `paper` with a new `joint.dia.Paper` object;
         * links mouse events to the right actions
         * [...]
         * @name ProjectView#initialize
         * @function
         */
        initialize: function () {
            this.model = ProjectModel;//new ProjectModel();
            this.paper = new joint.dia.Paper({
                el: $('#paper'),
                model: this.model.graph,
                width: 1500,
                height: 1000,
                gridSize: 6,
                drawGrid: true,
                elementView: joint.shapes.uml.ClassDiagramElementView,

                highlighting: {
                    'default': {
                        name: 'stroke',
                        options: {
                            padding: 3
                        }
                    },

                },

                //clickThreshold: 1,
                linkView: joint.dia.LinkView.extend({
                    pointerdblclick: function (evt, x, y) {
                        if (V(evt.target).hasClass('connection') || V(evt.target).hasClass('connection-wrap')) {
                            this.addVertex({x: x, y: y});
                        }
                    },
                    /*pointerclick: function (evt, x, y) {
                        console.log("you clicked a link");
                        // codice per dire a detailsview che è cambiato qualcosa
                    }*/
                }),

                selectedCell: null,

                interactive: function (cellView) {
                    if (cellView.model instanceof joint.dia.Link) {
                        // Disable the default vertex add functionality on pointerdown.
                        return {vertexAdd: false};
                    }

                    return true;
                },

            });

            //_.bindAll();

            // per qualche satanica ragione cell:pointerclick non funziona
            /// e pointerup funziona. FML.
/*
            this.paper.on('cell:pointerclick', function (cellView, evt, x, y)
            {

            });*/

            this.paper.on('cell:pointerdown', function(cellView, evt, x, y) {

                var cell = cellView.model;

                if (!cell.get('embeds') || cell.get('embeds').length === 0) {
                    // Show the dragged element above all the other cells (except when the
                    // element is a parent).
                    //cell.toFront();
                }

                if (cell.get('parent')) {
                    this.model.getCell(cell.get('parent')).unembed(cell);
                }

            });

            var pointerUpFunction = function (cellView, evt, x, y) {

                var changed = false;
                // EMBED e selectedcell
                if(cellView)
                {
                    var cell = cellView.model;
                    var cellViewsBelow = this.findViewsFromPoint(cell.getBBox().center());

                    if (cellViewsBelow.length) {
                        // Note that the findViewsFromPoint() returns the view for the `cell` itself.

                        // ho modificato il comportamento di default descritto nelle API perché sennò prendeva sempre il
                        /// primo trovato (_.find).
                        // prendendo l'ultimo sono sicuro che sia quello più interessante per l'user.

                        var index = _.findLastIndex(cellViewsBelow, function(c) { return c.model.id !== cell.id });
                        cellViewBelow = cellViewsBelow[index];

                        // Prevent recursive embedding.
                        if (cellViewBelow && cellViewBelow.model.get('parent') !== cell.id) {
                            cellViewBelow.model.embed(cell);
                        }
                    }
                    if (this.selectedCell != cellView.model) {
                        changed = true;
                        this.selectedCell = cellView.model;
                        if (true) {//cellView.model instanceof joint.shapes.uml.ClassDiagramElement) {
                            this.trigger("changed-cell");
                            console.log("ho cambiato la selectedcell");
                            console.log(this.selectedCell);

                        }

                    }
                }

                //var g = this.model.options.graphs[this.model.options.currentindex] ;

                //console.log(this);
                //var g = this.model.options.graphs;

                var g = this.model.attributes.cells.models;
                console.log(this.model.attributes.cells);
                //var g = ;//this.getCurrentGraph();

                var curr = this.selectedCell;

                //console.log(g);
                /*
                 var xyz = Array.from(g);
                 console.log(xyz);
                 console.log(typeof xyz);

                 var index = xyz.findIndex(curr);
                 */

                //console.log("questa è la cella selezionata: ");
                //console.log(curr);

    if(changed) {

    var move = function (a, old_index, new_index) {
        if (new_index >= a.length) {
            var k = new_index - a.length;
            while ((k--) + 1) {
                a.push(undefined);
            }
        }
        a.splice(new_index, 0, a.splice(old_index, 1)[0]);
        return a; // for testing purposes
    };

    var currentIndex = g.indexOf(curr);//curr.get("index"); // è necessario cercare a che indice vorrebbe mettere la cosa

    //console.log(g);

    // controllo se  la cella è andata in basso, oltre altri blocchi
    // nextindex è la prossima non embeddata

    var nextIndex = currentIndex + 1;

    while (nextIndex <= g.length - 1 && g[nextIndex].getAncestors().indexOf(g[currentIndex]) != -1) {
        //console.log(g[currentIndex]);
        //console.log(g[nextIndex].getAncestors());
        nextIndex++;
        //console.log("yup");
    }

    if (nextIndex <= g.length - 1 && curr.get("position").y > g[nextIndex].get("position").y) {
        while (nextIndex <= g.length - 1 && curr.get("position").y > g[nextIndex].get("position").y) {
            nextIndex++;
        }
        if (nextIndex != currentIndex + 1) {
            var figli = g[currentIndex].getEmbeddedCells();
            move(g, currentIndex, nextIndex - 1);
            //console.log("sposto ");
            //console.log(currentIndex);
            //console.log(nextIndex-1);
            for (i = 0; i < figli.length; i++) {
                move(g, currentIndex, nextIndex - 1 + i);
                //console.log("sposto ");
                //console.log(currentIndex);
                //console.log(nextIndex-1+i);

            }
        }
        //console.log("finito di spostare");
        //console.log(currentIndex);
        //console.log(nextIndex);
    }

    // controllo se la cella è andata in altro, oltre altri blocchi
    var prevIndex = currentIndex - 1;

    //while(g[currentIndex] in g[nextIndex].getAncestors()) {nextIndex++;}

    if (prevIndex >= 0 && curr.get("position").y < g[prevIndex].get("position").y) {
        while (prevIndex >= 0 && curr.get("position").y < g[prevIndex].get("position").y) {
            //console.log(curr.get("position").y );
            // console.log(g[prevIndex].get("position").y);
            prevIndex--;
            //console.log(prevIndex);

        }
        if (prevIndex != currentIndex - 1) {
            var figli = g[currentIndex].getEmbeddedCells();
            move(g, currentIndex, prevIndex + 1);
            //console.log("sposto ");
            //console.log(currentIndex);
            //console.log(prevIndex+1);

            for (i = 1; i <= figli.length; i++) {
                move(g, currentIndex + i, prevIndex + i + 1);
                //console.log("sposto ");
                //console.log(currentIndex+i);
                //console.log(prevIndex+1+i);

            }
        }

    }

    /*
     if(index+1<=g.length-1 && curr.get("position").y > g[index+1].get("position").y)
     {
     //curr.set("index", index+1);
     // g[index+1].set("index", index); // -1+1 mi raccomando
     //console.log(g);

     move(g,index,index+1);
     // console.log(g);

     console.log("sposto>");
     console.log(index) ;
     }*/

    // sarebbe >=1 ma c'è ancora il link in mezzo (senza sarebbe 0)
    /*
     if(index-1>=1 && curr.get("position").y < g[index-1].get("position").y )
     {
     ///curr.set("index", index-1);
     // g[index-1].set("index", index); // -1+1 mi raccomando
     //console.log(g);

     move(g, index,index-1);
     // console.log(g);
     console.log("sposto<");
     console.log(index) ;

     }*/

}

    var offsetY = 50;

    for(i=0;i<g.length;i++)
    {
        var hidden = false;/*
     _.each(g[i].getAncestors(), function (el) {
     if(!el.get("expanded"))
     {
     hidden = true; return;
     }
     }*/

        g[i].getAncestors().every(function (el) {
            if(!el.get("expanded"))
            {
                hidden = true;
                g[i].set("hidden", true);
            } return !hidden; // se every ritorna false, esce dal loop (per efficienza)
        });

        if(!hidden) {
            g[i].set("hidden", false);
            g[i].attributes.offsetY = offsetY;

            if(!g[i].get("expanded"))
            {
                offsetY += 50;// se è ridotto ho bisogno di meno spazio
            }

            else
            {
                offsetY += 100;
            }

        }
        else
        {
            g[i].set("hidden", true);
        }

    }

    var l = g.length;
    //console.log(g);
    //console.log(l);

    for(ii = 0; ii<l; ii++)
    {

        if(g[ii].get("type") == "uml.ActivityDiagramElement")
        {
            if(false)//g[i].get("hidden"))
            {
                this.removeView(g[ii]);
            }
            else
            {
                g[ii].updateRectangles();
                this.removeView(g[ii]);
                this.renderView(g[ii]); // per qualche ragione è necessario..
            }
        }

        else {
            console.log(g[ii]);
            console.log("questo no :(");
        }

    }

            };

            this.paper.on('cell:pointermove', function(cellView) {
                var cell = cellView.model;

                var cellViewsBelow = this.findViewsFromPoint(cell.getBBox().center());

                if (cellViewsBelow.length> 0) {
                    // Note that the findViewsFromPoint() returns the view for the `cell` itself.

                    // ho modificato il comportamento di default descritto nelle API perché sennò prendeva sempre il
                    /// primo trovato (_.find).
                    // prendendo l'ultimo sono sicuro che sia quello più interessante per l'user.

                    var index = _.findLastIndex(cellViewsBelow, function (c) {
                        return c.model.id !== cell.id
                    });

                    v = this.model.getElements();

                    for(i=0;i<v.length;i++)
                    {

                        this.findViewByModel(v[i]).unhighlight();

                    }

                    if(index!=-1)
                    {
                        cellViewBelow = cellViewsBelow[index];
                        cellViewBelow.highlight();

                        console.log("view highlight");
                        console.log(cellViewBelow);

                    }

                }

            });
            this.paper.on('cell:pointerup', pointerUpFunction);/*
            this.paper.on('blank:pointerdown', function (evt, x, y) {
                //console.log(evt);
               //console.log(x,y);
            });*/

            this.model.addInitialsCells();
            //this.trigger("cell:pointerup"); // come faccio a triggerare la cosa che ho definito sopra?

            console.log(this);
            // pointerUpFunction();

        },

        /**
         * ...
         * @name ProjectView#switch
         * @function
         * @param {number} index which graph to swith to
         * @param {?} selectedCell ?
         */
        switch: function (index, selectedCell) {
            this.model.switchToGraph(index, selectedCell);
            this.trigger("Switchgraph");
        }
    });
    return new ProjectView;
});
<|endoftext|>


<commit_before>/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */

/*************************************************************
 *
 *  MathJax/jax/output/SVG/autoload/maction.js
 *
 *  Implements the SVG output for <maction> elements.
 *
 *  ---------------------------------------------------------------------
 *
 *  Copyright (c) 2011-2015 The MathJax Consortium
 *
 *  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.
 */

MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
  var VERSION = "2.6.0-beta";
  var MML = MathJax.ElementJax.mml,
      SVG = MathJax.OutputJax["SVG"];

  var currentTip, hover, clear;

  //
  //  Add configuration for tooltips
  //
  var CONFIG = SVG.config.tooltip = MathJax.Hub.Insert({
    delayPost: 600, delayClear: 600,
    offsetX: 10, offsetY: 5
  },SVG.config.tooltip||{});

  MML.maction.Augment({
    SVGtooltip: MathJax.HTML.addElement(document.body,"div",{id:"MathJax_SVG_Tooltip"}),

    toSVG: function (HW,D) {
      this.SVGgetStyles();
      var svg = this.SVG();
      var selected = this.selected();
      if (selected.type == "null") {this.SVGsaveData(svg);return svg;}
      svg.Add(this.SVGdataStretched(this.Get("selection")-1,HW,D));
      this.SVGhandleHitBox(svg);
      this.SVGhandleSpace(svg);
      this.SVGhandleColor(svg);
      this.SVGsaveData(svg);
      return svg;
    },
    SVGhandleHitBox: function (svg) {
      var frame = SVG.addElement(svg.element,"rect",
        {width:svg.w, height:svg.h+svg.d, y:-svg.d, fill:"none", "pointer-events":"all"});
      var type = this.Get("actiontype");
      if (this.SVGaction[type]) {this.SVGaction[type].call(this,svg,frame,this.Get("selection"))}
    },
    SVGstretchH: MML.mbase.prototype.SVGstretchH,
    SVGstretchV: MML.mbase.prototype.SVGstretchV,

    //
    //  Implementations for the various actions
    //
    SVGaction: {
      toggle: function (svg,frame,selection) {
        this.selection = selection;
        SVG.Element(frame,{cursor:"pointer"});
        frame.onclick = MathJax.Callback(["SVGclick",this]);
      },

      statusline: function (svg,frame,selection) {
        frame.onmouseover = MathJax.Callback(["SVGsetStatus",this]),
        frame.onmouseout  = MathJax.Callback(["SVGclearStatus",this]);
        frame.onmouseover.autoReset = frame.onmouseout.autoReset = true;
      },

      tooltip: function(svg,frame,selection) {
        frame.onmouseover = MathJax.Callback(["SVGtooltipOver",this]),
        frame.onmouseout  = MathJax.Callback(["SVGtooltipOut",this]);
        frame.onmouseover.autoReset = frame.onmouseout.autoReset = true;
      }
    },

    //
    //  Handle a click on the maction element
    //    (remove the original rendering and rerender)
    //
    SVGclick: function (event) {
      this.selection++;
      if (this.selection > this.data.length) {this.selection = 1}
      var math = this; while (math.type !== "math") {math = math.inherit}
      var jax = MathJax.Hub.getJaxFor(math.inputID); //, hover = !!jax.hover;
      jax.Update();
      /*
       * if (hover) {
       *   var span = document.getElementById(jax.inputID+"-Span");
       *   MathJax.Extension.MathEvents.Hover.Hover(jax,span);
       * }
       */
      return MathJax.Extension.MathEvents.Event.False(event);
    },

    //
    //  Set/Clear the window status message
    //
    SVGsetStatus: function (event) {
      // FIXME:  Do something better with non-token elements
      this.messageID = MathJax.Message.Set
        ((this.data[1] && this.data[1].isToken) ?
             this.data[1].data.join("") : this.data[1].toString());
    },
    SVGclearStatus: function (event) {
      if (this.messageID) {MathJax.Message.Clear(this.messageID,0)}
      delete this.messageID;
    },

    //
    //  Handle tooltips
    //
    SVGtooltipOver: function (event) {
      if (!event) {event = window.event}
      if (clear) {clearTimeout(clear); clear = null}
      if (hover) {clearTimeout(hover)}
      var x = event.pageX; var y = event.pageY;
      if (x == null) {
        x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
        y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
      }
      var callback = MathJax.Callback(["SVGtooltipPost",this,x+CONFIG.offsetX,y+CONFIG.offsetY])
      hover = setTimeout(callback,CONFIG.delayPost);
    },
    SVGtooltipOut: function (event) {
      if (hover) {clearTimeout(hover); hover = null}
      if (clear) {clearTimeout(clear)}
      var callback = MathJax.Callback(["SVGtooltipClear",this,80]);
      clear = setTimeout(callback,CONFIG.delayClear);
    },
    SVGtooltipPost: function (x,y) {
      hover = null; if (clear) {clearTimeout(clear); clear = null}

      //
      //  Get the tip div and show it at the right location, then clear its contents
      //
      var tip = this.SVGtooltip;
      tip.style.display = "block"; tip.style.opacity = "";
      if (this === currentTip) return;
      tip.style.left = x+"px"; tip.style.top = y+"px";
      tip.innerHTML = ''; var span = MathJax.HTML.addElement(tip,"span");

      //
      //  Get the sizes from the jax (FIXME: should calculate again?)
      //
      var math = this; while (math.type !== "math") {math = math.inherit}
      var jax = MathJax.Hub.getJaxFor(math.inputID);
      this.em = MML.mbase.prototype.em = jax.SVG.em; this.ex = jax.SVG.ex;
      this.linebreakWidth = jax.SVG.lineWidth; this.cwidth = jax.SVG.cwidth;

      //
      //  Make a new math element and temporarily move the tooltip to it
      //  Display the math containing the tip, but check for errors
      //  Then put the tip back into the maction element
      //
      var mml = this.data[1];
      math = MML.math(mml);
      try {math.toSVG(span,tip)} catch(err) {
        this.SetData(1,mml); tip.style.display = "none";
        if (!err.restart) {throw err}
        MathJax.Callback.After(["SVGtooltipPost",this,x,y],err.restart);
        return;
      }
      this.SetData(1,mml);

      currentTip = this;
    },
    SVGtooltipClear: function (n) {
      var tip = this.SVGtooltip;
      if (n <= 0) {
        tip.style.display = "none";
        tip.style.opacity = "";
        clear = null;
      } else {
        tip.style.opacity = n/100;
        clear = setTimeout(MathJax.Callback(["SVGtooltipClear",this,n-20]),50);
      }
    }
  });

  MathJax.Hub.Startup.signal.Post("SVG maction Ready");
  MathJax.Ajax.loadComplete(SVG.autoloadDir+"/maction.js");

});

<commit_msg>Make mactions nest properly in SVG output<commit_after>/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */

/*************************************************************
 *
 *  MathJax/jax/output/SVG/autoload/maction.js
 *
 *  Implements the SVG output for <maction> elements.
 *
 *  ---------------------------------------------------------------------
 *
 *  Copyright (c) 2011-2015 The MathJax Consortium
 *
 *  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.
 */

MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
  var VERSION = "2.6.0-beta";
  var MML = MathJax.ElementJax.mml,
      SVG = MathJax.OutputJax["SVG"];

  var currentTip, hover, clear;

  //
  //  Add configuration for tooltips
  //
  var CONFIG = SVG.config.tooltip = MathJax.Hub.Insert({
    delayPost: 600, delayClear: 600,
    offsetX: 10, offsetY: 5
  },SVG.config.tooltip||{});

  MML.maction.Augment({
    SVGtooltip: MathJax.HTML.addElement(document.body,"div",{id:"MathJax_SVG_Tooltip"}),

    toSVG: function (HW,D) {
      this.SVGgetStyles();
      var svg = this.SVG();
      var selected = this.selected();
      if (selected.type == "null") {this.SVGsaveData(svg);return svg;}
      svg.Add(this.SVGdataStretched(this.Get("selection")-1,HW,D));
      svg.removeable = false;
      this.SVGhandleHitBox(svg);
      this.SVGhandleSpace(svg);
      this.SVGhandleColor(svg);
      this.SVGsaveData(svg);
      return svg;
    },
    SVGhandleHitBox: function (svg) {
      var frame = SVG.Element("rect",
        {width:svg.w, height:svg.h+svg.d, y:-svg.d, fill:"none", "pointer-events":"all"});
      svg.element.insertBefore(frame,svg.element.firstChild);
      var type = this.Get("actiontype");
      if (this.SVGaction[type])
        {this.SVGaction[type].call(this,svg,svg.element,this.Get("selection"))}
    },
    SVGstretchH: MML.mbase.prototype.SVGstretchH,
    SVGstretchV: MML.mbase.prototype.SVGstretchV,

    //
    //  Implementations for the various actions
    //
    SVGaction: {
      toggle: function (svg,frame,selection) {
        this.selection = selection;
        SVG.Element(frame,{cursor:"pointer"});
        frame.onclick = MathJax.Callback(["SVGclick",this]);
      },

      statusline: function (svg,frame,selection) {
        frame.onmouseover = MathJax.Callback(["SVGsetStatus",this]),
        frame.onmouseout  = MathJax.Callback(["SVGclearStatus",this]);
        frame.onmouseover.autoReset = frame.onmouseout.autoReset = true;
      },

      tooltip: function(svg,frame,selection) {
        frame.onmouseover = MathJax.Callback(["SVGtooltipOver",this]),
        frame.onmouseout  = MathJax.Callback(["SVGtooltipOut",this]);
        frame.onmouseover.autoReset = frame.onmouseout.autoReset = true;
      }
    },

    //
    //  Handle a click on the maction element
    //    (remove the original rendering and rerender)
    //
    SVGclick: function (event) {
      this.selection++;
      if (this.selection > this.data.length) {this.selection = 1}
      var math = this; while (math.type !== "math") {math = math.inherit}
      var jax = MathJax.Hub.getJaxFor(math.inputID); //, hover = !!jax.hover;
      jax.Update();
      /*
       * if (hover) {
       *   var span = document.getElementById(jax.inputID+"-Span");
       *   MathJax.Extension.MathEvents.Hover.Hover(jax,span);
       * }
       */
      return MathJax.Extension.MathEvents.Event.False(event);
    },

    //
    //  Set/Clear the window status message
    //
    SVGsetStatus: function (event) {
      // FIXME:  Do something better with non-token elements
      this.messageID = MathJax.Message.Set
        ((this.data[1] && this.data[1].isToken) ?
             this.data[1].data.join("") : this.data[1].toString());
    },
    SVGclearStatus: function (event) {
      if (this.messageID) {MathJax.Message.Clear(this.messageID,0)}
      delete this.messageID;
    },

    //
    //  Handle tooltips
    //
    SVGtooltipOver: function (event) {
      if (!event) {event = window.event}
      if (clear) {clearTimeout(clear); clear = null}
      if (hover) {clearTimeout(hover)}
      var x = event.pageX; var y = event.pageY;
      if (x == null) {
        x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
        y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
      }
      var callback = MathJax.Callback(["SVGtooltipPost",this,x+CONFIG.offsetX,y+CONFIG.offsetY])
      hover = setTimeout(callback,CONFIG.delayPost);
    },
    SVGtooltipOut: function (event) {
      if (hover) {clearTimeout(hover); hover = null}
      if (clear) {clearTimeout(clear)}
      var callback = MathJax.Callback(["SVGtooltipClear",this,80]);
      clear = setTimeout(callback,CONFIG.delayClear);
    },
    SVGtooltipPost: function (x,y) {
      hover = null; if (clear) {clearTimeout(clear); clear = null}

      //
      //  Get the tip div and show it at the right location, then clear its contents
      //
      var tip = this.SVGtooltip;
      tip.style.display = "block"; tip.style.opacity = "";
      if (this === currentTip) return;
      tip.style.left = x+"px"; tip.style.top = y+"px";
      tip.innerHTML = ''; var span = MathJax.HTML.addElement(tip,"span");

      //
      //  Get the sizes from the jax (FIXME: should calculate again?)
      //
      var math = this; while (math.type !== "math") {math = math.inherit}
      var jax = MathJax.Hub.getJaxFor(math.inputID);
      this.em = MML.mbase.prototype.em = jax.SVG.em; this.ex = jax.SVG.ex;
      this.linebreakWidth = jax.SVG.lineWidth; this.cwidth = jax.SVG.cwidth;

      //
      //  Make a new math element and temporarily move the tooltip to it
      //  Display the math containing the tip, but check for errors
      //  Then put the tip back into the maction element
      //
      var mml = this.data[1];
      math = MML.math(mml);
      try {math.toSVG(span,tip)} catch(err) {
        this.SetData(1,mml); tip.style.display = "none";
        if (!err.restart) {throw err}
        MathJax.Callback.After(["SVGtooltipPost",this,x,y],err.restart);
        return;
      }
      this.SetData(1,mml);

      currentTip = this;
    },
    SVGtooltipClear: function (n) {
      var tip = this.SVGtooltip;
      if (n <= 0) {
        tip.style.display = "none";
        tip.style.opacity = "";
        clear = null;
      } else {
        tip.style.opacity = n/100;
        clear = setTimeout(MathJax.Callback(["SVGtooltipClear",this,n-20]),50);
      }
    }
  });

  MathJax.Hub.Startup.signal.Post("SVG maction Ready");
  MathJax.Ajax.loadComplete(SVG.autoloadDir+"/maction.js");

});

<|endoftext|>


<commit_before>const cloudinary = require('cloudinary')
const config = require('config')
const User = require('../../models/user.js')
const Street = require('../../models/street.js')
const logger = require('../../../lib/logger.js')()

const ALLOW_ANON_STREET_THUMBNAILS = false

exports.post = async function (req, res) {
  const image = req.body

  if (!image) {
    res.status(400).send('Image data not specified.')
    return
  }

  if (!req.params.street_id) {
    res.status(400).send('Please provide street ID.')
    return
  }

  // 1) Check if street exists.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding street.')
    return
  }

  if (!street) {
    res.status(400).send('Street not found.')
    return
  }

  // 2) Check if street thumbnail exists.
  let resource
  const publicId = `${config.env}/street_thumbnails/${street.id}`

  try {
    resource = cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
  }

  const handleUploadStreetThumbnail = async function (publicId) {
    if (!publicId) {
      res.status(400).send('Please provide the public ID to be used.')
      return
    }

    try {
      resource = await cloudinary.v2.uploader.upload(image, { public_id: publicId })
    } catch (error) {
      logger.error(error)
    }

    if (!resource) {
      res.status(500).send('Error uploading street thumbnail to Cloudinary.')
      return
    }

    res.status(200).json(resource)
  }

  const handleFindStreetWithCreator = async function (street) {
    if (!req.userId) {
      res.status(404).send('Please provide a user ID.')
      return
    }

    let user

    try {
      user = await User.findOne({ id: req.userId })
    } catch (error) {
      logger.error(error)
      res.status(500).send('Error finding user.')
      return
    }

    if (!user) {
      res.status(400).send('User not found.')
      return
    }

    if (street.creator_id !== user._id) {
      res.status(404).send('User does not have right permissions to upload street thumbnail.')
      return
    }

    const publicId = `${config.env}/street_thumbnails/${street.id}`
    return publicId
  }

  const handleError = function (error) {
    logger.error(error)
    res.status(400).end()
  }

  // 3a) If street thumbnail does not exist, upload to Cloudinary no matter the currently signed in user.
  // 3b) If street was created by anonymous user, upload to Cloudinary.
  if (!resource || (!street.creator_id && ALLOW_ANON_STREET_THUMBNAILS)) {
    handleUploadStreetThumbnail(publicId)
      .catch(handleError)
  } else if (street.creator_id) {
    // 3c) If street thumbnail already exists and street was created by a user, check if signed in user = creator.
    handleFindStreetWithCreator(street)
      .then(handleUploadStreetThumbnail)
      .catch(handleError)
  } else {
    res.status(400).send('User does not have the right permissions to upload street thumbnail to Cloudinary.')
  }
}

exports.delete = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).send('Please provide street ID.')
    return
  }

  // 1) Verify user is logged in.
  const userId = req.userId

  if (!userId) {
    res.status(400).send('Please provide user ID.')
    return
  }

  let user

  try {
    user = await User.findOne({ id: userId })
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding user.')
  }

  if (!user) {
    res.status(404).send('User not found.')
    return
  }

  // Is requesting user logged in?
  if (user.login_tokens.indexOf(req.loginToken) === -1) {
    res.status(401).end()
    return
  }

  // 2) Check that street exists.
  // 3) Verify that street is owned by logged in user.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding street.')
  }

  if (!street) {
    res.status(400).send('Street not found.')
    return
  } else if (street.creator_id.toString() !== user._id.toString()) {
    res.status(404).send('Signed in user cannot delete street thumbnail.')
    return
  }

  // 4) Delete street thumbnail from cloudinary.
  const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
  cloudinary.v2.uploader.destroy(publicId, function (error, result) {
    if (error) {
      logger.error(error)
      res.status(500).send('Error deleting street thumbnail from cloudinary.')
      return
    }

    res.status(204).end()
  })
}

exports.get = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).send('Please provide a street id.')
    return
  }

  let resource

  try {
    const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
    resource = await cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding street thumbnail from cloudinary.')
    return
  }

  if (!resource) {
    res.status(400).send('Could not find street thumbnail from cloudinary.')
    return
  }

  res.status(200).json(resource)
}
<commit_msg>send status code 500 if error caught<commit_after>const cloudinary = require('cloudinary')
const config = require('config')
const User = require('../../models/user.js')
const Street = require('../../models/street.js')
const logger = require('../../../lib/logger.js')()

const ALLOW_ANON_STREET_THUMBNAILS = false

exports.post = async function (req, res) {
  const image = req.body

  if (!image) {
    res.status(400).send('Image data not specified.')
    return
  }

  if (!req.params.street_id) {
    res.status(400).send('Please provide street ID.')
    return
  }

  // 1) Check if street exists.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding street.')
    return
  }

  if (!street) {
    res.status(400).send('Street not found.')
    return
  }

  // 2) Check if street thumbnail exists.
  let resource
  const publicId = `${config.env}/street_thumbnails/${street.id}`

  try {
    resource = cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
  }

  const handleUploadStreetThumbnail = async function (publicId) {
    if (!publicId) {
      res.status(400).send('Please provide the public ID to be used.')
      return
    }

    try {
      resource = await cloudinary.v2.uploader.upload(image, { public_id: publicId })
    } catch (error) {
      logger.error(error)
    }

    if (!resource) {
      res.status(500).send('Error uploading street thumbnail to Cloudinary.')
      return
    }

    res.status(200).json(resource)
  }

  const handleFindStreetWithCreator = async function (street) {
    if (!req.userId) {
      res.status(404).send('Please provide a user ID.')
      return
    }

    let user

    try {
      user = await User.findOne({ id: req.userId })
    } catch (error) {
      logger.error(error)
      res.status(500).send('Error finding user.')
      return
    }

    if (!user) {
      res.status(400).send('User not found.')
      return
    }

    if (street.creator_id !== user._id) {
      res.status(404).send('User does not have right permissions to upload street thumbnail.')
      return
    }

    const publicId = `${config.env}/street_thumbnails/${street.id}`
    return publicId
  }

  const handleError = function (error) {
    logger.error(error)
    res.status(500).end()
  }

  // 3a) If street thumbnail does not exist, upload to Cloudinary no matter the currently signed in user.
  // 3b) If street was created by anonymous user, upload to Cloudinary.
  if (!resource || (!street.creator_id && ALLOW_ANON_STREET_THUMBNAILS)) {
    handleUploadStreetThumbnail(publicId)
      .catch(handleError)
  } else if (street.creator_id) {
    // 3c) If street thumbnail already exists and street was created by a user, check if signed in user = creator.
    handleFindStreetWithCreator(street)
      .then(handleUploadStreetThumbnail)
      .catch(handleError)
  } else {
    res.status(400).send('User does not have the right permissions to upload street thumbnail to Cloudinary.')
  }
}

exports.delete = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).send('Please provide street ID.')
    return
  }

  // 1) Verify user is logged in.
  const userId = req.userId

  if (!userId) {
    res.status(400).send('Please provide user ID.')
    return
  }

  let user

  try {
    user = await User.findOne({ id: userId })
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding user.')
  }

  if (!user) {
    res.status(404).send('User not found.')
    return
  }

  // Is requesting user logged in?
  if (user.login_tokens.indexOf(req.loginToken) === -1) {
    res.status(401).end()
    return
  }

  // 2) Check that street exists.
  // 3) Verify that street is owned by logged in user.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding street.')
  }

  if (!street) {
    res.status(400).send('Street not found.')
    return
  } else if (street.creator_id.toString() !== user._id.toString()) {
    res.status(404).send('Signed in user cannot delete street thumbnail.')
    return
  }

  // 4) Delete street thumbnail from cloudinary.
  const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
  cloudinary.v2.uploader.destroy(publicId, function (error, result) {
    if (error) {
      logger.error(error)
      res.status(500).send('Error deleting street thumbnail from cloudinary.')
      return
    }

    res.status(204).end()
  })
}

exports.get = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).send('Please provide a street id.')
    return
  }

  let resource

  try {
    const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
    resource = await cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
    res.status(500).send('Error finding street thumbnail from cloudinary.')
    return
  }

  if (!resource) {
    res.status(400).send('Could not find street thumbnail from cloudinary.')
    return
  }

  res.status(200).json(resource)
}
<|endoftext|>