You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
457 lines
18 KiB
457 lines
18 KiB
/* 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';
|
|
|
|
/**
|
|
* General bootstrap of the application.
|
|
*/
|
|
angular.module('flowableModeler')
|
|
.controller('EditorController', ['$rootScope', '$scope', '$http', '$q', '$routeParams', '$timeout', '$location', '$translate', '$modal', 'editorManager', 'FormBuilderService',
|
|
function ($rootScope, $scope, $http, $q, $routeParams, $timeout, $location, $translate, $modal, editorManager, FormBuilderService) {
|
|
|
|
$rootScope.editorFactory = $q.defer();
|
|
|
|
$rootScope.forceSelectionRefresh = false;
|
|
|
|
$rootScope.ignoreChanges = false; // by default never ignore changes
|
|
|
|
$rootScope.validationErrors = [];
|
|
|
|
$rootScope.staticIncludeVersion = Date.now();
|
|
|
|
/**
|
|
* Initialize the event bus: couple all Oryx events with a dispatch of the
|
|
* event of the event bus. This way, it gets much easier to attach custom logic
|
|
* to any event.
|
|
*/
|
|
|
|
|
|
/* Helper method to fetch model from server (always needed) */
|
|
function fetchModel() {
|
|
|
|
var modelUrl;
|
|
if ($routeParams.modelId) {
|
|
modelUrl = FLOWABLE.URL.getModel($routeParams.modelId);
|
|
} else {
|
|
modelUrl = FLOWABLE.URL.newModelInfo();
|
|
}
|
|
|
|
$http({method: 'GET', url: modelUrl}).
|
|
success(function (data, status, headers, config) {
|
|
$rootScope.editor = new ORYX.Editor(data);
|
|
$rootScope.modelData = angular.fromJson(data);
|
|
$rootScope.editorFactory.resolve();
|
|
}).
|
|
error(function (data, status, headers, config) {
|
|
$location.path("/processes/");
|
|
});
|
|
}
|
|
|
|
|
|
function initScrollHandling() {
|
|
var canvasSection = jQuery('#canvasSection');
|
|
canvasSection.scroll(function() {
|
|
|
|
// Hides the resizer and quick menu items during scrolling
|
|
|
|
var selectedElements = editorManager.getSelection();
|
|
var subSelectionElements = editorManager.getSubSelection();
|
|
|
|
$scope.selectedElements = selectedElements;
|
|
$scope.subSelectionElements = subSelectionElements;
|
|
if (selectedElements && selectedElements.length > 0)
|
|
{
|
|
$rootScope.selectedElementBeforeScrolling = selectedElements[0];
|
|
}
|
|
|
|
jQuery('.Oryx_button').each(function(i, obj) {
|
|
$scope.orginalOryxButtonStyle = obj.style.display;
|
|
obj.style.display = 'none';
|
|
});
|
|
jQuery('.resizer_southeast').each(function(i, obj) {
|
|
$scope.orginalResizerSEStyle = obj.style.display;
|
|
obj.style.display = 'none';
|
|
});
|
|
jQuery('.resizer_northwest').each(function(i, obj) {
|
|
$scope.orginalResizerNWStyle = obj.style.display;
|
|
obj.style.display = 'none';
|
|
});
|
|
editorManager.handleEvents({type:ORYX.CONFIG.EVENT_CANVAS_SCROLL});
|
|
});
|
|
|
|
canvasSection.scrollStopped(function(){
|
|
|
|
// Puts the quick menu items and resizer back when scroll is stopped.
|
|
|
|
editorManager.setSelection([]); // needed cause it checks for element changes and does nothing if the elements are the same
|
|
editorManager.setSelection($scope.selectedElements, $scope.subSelectionElements);
|
|
$scope.selectedElements = undefined;
|
|
$scope.subSelectionElements = undefined;
|
|
|
|
function handleDisplayProperty(obj) {
|
|
if (jQuery(obj).position().top > 0) {
|
|
obj.style.display = 'block';
|
|
} else {
|
|
obj.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
jQuery('.Oryx_button').each(function(i, obj) {
|
|
handleDisplayProperty(obj);
|
|
});
|
|
jQuery('.resizer_southeast').each(function(i, obj) {
|
|
handleDisplayProperty(obj);
|
|
});
|
|
jQuery('.resizer_northwest').each(function(i, obj) {
|
|
handleDisplayProperty(obj);
|
|
});
|
|
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize the Oryx Editor when the content has been loaded
|
|
*/
|
|
if (!$rootScope.editorInitialized) {
|
|
|
|
var paletteHelpWrapper = jQuery('#paletteHelpWrapper');
|
|
var paletteSectionFooter = jQuery('#paletteSectionFooter');
|
|
var paletteSectionOpen = jQuery('#paletteSectionOpen');
|
|
var contentCanvasWrapper = jQuery('#contentCanvasWrapper');
|
|
|
|
paletteSectionFooter.on('click', function() {
|
|
paletteHelpWrapper.addClass('close');
|
|
contentCanvasWrapper.addClass('collapsedCanvasWrapper');
|
|
paletteSectionOpen.removeClass('hidden');
|
|
});
|
|
|
|
paletteSectionOpen.on('click', function () {
|
|
paletteHelpWrapper.removeClass('close');
|
|
contentCanvasWrapper.removeClass('collapsedCanvasWrapper');
|
|
paletteSectionOpen.addClass('hidden');
|
|
});
|
|
|
|
/**
|
|
* A 'safer' apply that avoids concurrent updates (which $apply allows).
|
|
*/
|
|
$rootScope.safeApply = function(fn) {
|
|
if (this.$root) {
|
|
var phase = this.$root.$$phase;
|
|
if(phase == '$apply' || phase == '$digest') {
|
|
if(fn && (typeof(fn) === 'function')) {
|
|
fn();
|
|
}
|
|
} else {
|
|
this.$apply(fn);
|
|
}
|
|
|
|
} else {
|
|
this.$apply(fn);
|
|
}
|
|
};
|
|
|
|
$rootScope.addHistoryItem = function(resourceId) {
|
|
var modelMetaData = editorManager.getBaseModelData();
|
|
|
|
var historyItem = {
|
|
id: modelMetaData.modelId,
|
|
name: modelMetaData.name,
|
|
key: modelMetaData.key,
|
|
stepId: resourceId,
|
|
type: 'bpmnmodel'
|
|
};
|
|
|
|
if (editorManager.getCurrentModelId() != editorManager.getModelId()) {
|
|
historyItem.subProcessId = editorManager.getCurrentModelId();
|
|
}
|
|
|
|
$rootScope.editorHistory.push(historyItem);
|
|
};
|
|
|
|
$rootScope.getStencilSetName = function() {
|
|
var modelMetaData = editorManager.getBaseModelData();
|
|
if (modelMetaData.model.stencilset.namespace == 'http://b3mn.org/stencilset/cmmn1.1#') {
|
|
return 'cmmn1.1';
|
|
} else {
|
|
return 'bpmn2.0';
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialize the event bus: couple all Oryx events with a dispatch of the
|
|
* event of the event bus. This way, it gets much easier to attach custom logic
|
|
* to any event.
|
|
*/
|
|
|
|
$rootScope.editorFactory.promise.then(function() {
|
|
|
|
$rootScope.formItems = undefined;
|
|
|
|
FLOWABLE.eventBus.editor = $rootScope.editor;
|
|
|
|
var eventMappings = [
|
|
{ oryxType : ORYX.CONFIG.EVENT_SELECTION_CHANGED, flowableType : FLOWABLE.eventBus.EVENT_TYPE_SELECTION_CHANGE },
|
|
{ oryxType : ORYX.CONFIG.EVENT_DBLCLICK, flowableType : FLOWABLE.eventBus.EVENT_TYPE_DOUBLE_CLICK },
|
|
{ oryxType : ORYX.CONFIG.EVENT_MOUSEOUT, flowableType : FLOWABLE.eventBus.EVENT_TYPE_MOUSE_OUT },
|
|
{ oryxType : ORYX.CONFIG.EVENT_MOUSEOVER, flowableType : FLOWABLE.eventBus.EVENT_TYPE_MOUSE_OVER },
|
|
{ oryxType: ORYX.CONFIG.EVENT_EDITOR_INIT_COMPLETED, flowableType:FLOWABLE.eventBus.EVENT_TYPE_EDITOR_READY},
|
|
{ oryxType: ORYX.CONFIG.EVENT_PROPERTY_CHANGED, flowableType: FLOWABLE.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED}
|
|
|
|
];
|
|
|
|
eventMappings.forEach(function(eventMapping) {
|
|
editorManager.registerOnEvent(eventMapping.oryxType, function(event) {
|
|
FLOWABLE.eventBus.dispatch(eventMapping.flowableType, event);
|
|
});
|
|
});
|
|
|
|
// Show getting started if this is the first time (boolean true for use local storage)
|
|
// FLOWABLE_EDITOR_TOUR.gettingStarted($scope, $translate, $q, true);
|
|
});
|
|
|
|
// Hook in resizing of main panels when window resizes
|
|
// TODO: perhaps move to a separate JS-file?
|
|
jQuery(window).resize(function () {
|
|
|
|
// Calculate the offset based on the bottom of the module header
|
|
var offset = jQuery("#editor-header").offset();
|
|
var propSectionHeight = jQuery('#propertySection').height();
|
|
var canvas = jQuery('#canvasSection');
|
|
var mainHeader = jQuery('#main-header');
|
|
|
|
if (offset == undefined || offset === null
|
|
|| propSectionHeight === undefined || propSectionHeight === null
|
|
|| canvas === undefined || canvas === null || mainHeader === null) {
|
|
return;
|
|
}
|
|
|
|
if ($rootScope.editor) {
|
|
var selectedElements = editorManager.getSelection();
|
|
var subSelectionElements = editorManager.getSelection();
|
|
|
|
$scope.selectedElements = selectedElements;
|
|
$scope.subSelectionElements = subSelectionElements;
|
|
if (selectedElements && selectedElements.length > 0) {
|
|
$rootScope.selectedElementBeforeScrolling = selectedElements[0];
|
|
|
|
editorManager.setSelection([]); // needed cause it checks for element changes and does nothing if the elements are the same
|
|
editorManager.setSelection($scope.selectedElements, $scope.subSelectionElements);
|
|
$scope.selectedElements = undefined;
|
|
$scope.subSelectionElements = undefined;
|
|
}
|
|
}
|
|
|
|
var totalAvailable = jQuery(window).height() - offset.top - mainHeader.height() - 21;
|
|
canvas.height(totalAvailable - propSectionHeight);
|
|
var footerHeight = jQuery('#paletteSectionFooter').height();
|
|
var treeViewHeight = jQuery('#process-treeview-wrapper').height();
|
|
jQuery('#paletteSection').height(totalAvailable - treeViewHeight - footerHeight);
|
|
|
|
// Update positions of the resize-markers, according to the canvas
|
|
|
|
var actualCanvas = null;
|
|
if (canvas && canvas[0].children[1]) {
|
|
actualCanvas = canvas[0].children[1];
|
|
}
|
|
|
|
var canvasTop = canvas.position().top;
|
|
var canvasLeft = canvas.position().left;
|
|
var canvasHeight = canvas[0].clientHeight;
|
|
var canvasWidth = canvas[0].clientWidth;
|
|
var iconCenterOffset = 8;
|
|
var widthDiff = 0;
|
|
|
|
var actualWidth = 0;
|
|
if (actualCanvas) {
|
|
// In some browsers, the SVG-element clientwidth isn't available, so we revert to the parent
|
|
actualWidth = actualCanvas.clientWidth || actualCanvas.parentNode.clientWidth;
|
|
}
|
|
|
|
if (actualWidth < canvas[0].clientWidth) {
|
|
widthDiff = actualWidth - canvas[0].clientWidth;
|
|
// In case the canvas is smaller than the actual viewport, the resizers should be moved
|
|
canvasLeft -= widthDiff / 2;
|
|
canvasWidth += widthDiff;
|
|
}
|
|
|
|
var iconWidth = 17;
|
|
var iconOffset = 20;
|
|
|
|
var north = jQuery('#canvas-grow-N');
|
|
north.css('top', canvasTop + iconOffset + 'px');
|
|
north.css('left', canvasLeft - 10 + (canvasWidth - iconWidth) / 2 + 'px');
|
|
|
|
var south = jQuery('#canvas-grow-S');
|
|
south.css('top', (canvasTop + canvasHeight - iconOffset - iconCenterOffset) + 'px');
|
|
south.css('left', canvasLeft - 10 + (canvasWidth - iconWidth) / 2 + 'px');
|
|
|
|
var east = jQuery('#canvas-grow-E');
|
|
east.css('top', canvasTop - 10 + (canvasHeight - iconWidth) / 2 + 'px');
|
|
east.css('left', (canvasLeft + canvasWidth - iconOffset - iconCenterOffset) + 'px');
|
|
|
|
var west = jQuery('#canvas-grow-W');
|
|
west.css('top', canvasTop -10 + (canvasHeight - iconWidth) / 2 + 'px');
|
|
west.css('left', canvasLeft + iconOffset + 'px');
|
|
|
|
north = jQuery('#canvas-shrink-N');
|
|
north.css('top', canvasTop + iconOffset + 'px');
|
|
north.css('left', canvasLeft + 10 + (canvasWidth - iconWidth) / 2 + 'px');
|
|
|
|
south = jQuery('#canvas-shrink-S');
|
|
south.css('top', (canvasTop + canvasHeight - iconOffset - iconCenterOffset) + 'px');
|
|
south.css('left', canvasLeft +10 + (canvasWidth - iconWidth) / 2 + 'px');
|
|
|
|
east = jQuery('#canvas-shrink-E');
|
|
east.css('top', canvasTop + 10 + (canvasHeight - iconWidth) / 2 + 'px');
|
|
east.css('left', (canvasLeft + canvasWidth - iconOffset - iconCenterOffset) + 'px');
|
|
|
|
west = jQuery('#canvas-shrink-W');
|
|
west.css('top', canvasTop + 10 + (canvasHeight - iconWidth) / 2 + 'px');
|
|
west.css('left', canvasLeft + iconOffset + 'px');
|
|
});
|
|
|
|
jQuery(window).trigger('resize');
|
|
|
|
jQuery.fn.scrollStopped = function(callback) {
|
|
jQuery(this).scroll(function(){
|
|
var self = this, $this = jQuery(self);
|
|
if ($this.data('scrollTimeout')) {
|
|
clearTimeout($this.data('scrollTimeout'));
|
|
}
|
|
$this.data('scrollTimeout', setTimeout(callback,50,self));
|
|
});
|
|
};
|
|
|
|
FLOWABLE.eventBus.addListener('ORYX-EDITOR-LOADED',function(){
|
|
this.editorFactory.resolve();
|
|
this.editorInitialized = true;
|
|
this.modelData = editorManager.getBaseModelData();
|
|
|
|
}, $rootScope);
|
|
|
|
FLOWABLE.eventBus.addListener(FLOWABLE.eventBus.EVENT_TYPE_EDITOR_READY, function() {
|
|
var url = window.location.href;
|
|
var regex = new RegExp("[?&]subProcessId(=([^&#]*)|&|#|$)");
|
|
var results = regex.exec(url);
|
|
if (results && results[2]) {
|
|
editorManager.edit(decodeURIComponent(results[2].replace(/\+/g, " ")));
|
|
}
|
|
});
|
|
}
|
|
|
|
$scope.$on('$locationChangeStart', function(event, next, current) {
|
|
if ($rootScope.editor && !$rootScope.ignoreChanges) {
|
|
var plugins = $rootScope.editor.loadedPlugins;
|
|
|
|
var savePlugin;
|
|
for (var i=0; i<plugins.length; i++) {
|
|
if (plugins[i].type == 'ORYX.Plugins.Save') {
|
|
savePlugin = plugins[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (savePlugin && savePlugin.hasChanges()) {
|
|
// Always prevent location from changing. We'll use a popup to determine the action we want to take
|
|
event.preventDefault();
|
|
|
|
if (!$scope.unsavedChangesModalInstance) {
|
|
|
|
var handleResponseFunction = function (discard) {
|
|
$scope.unsavedChangesModalInstance = undefined;
|
|
if (discard) {
|
|
$rootScope.ignoreChanges = true;
|
|
$location.url(next.substring(next.indexOf('/#') + 2));
|
|
} else {
|
|
$rootScope.ignoreChanges = false;
|
|
$rootScope.setMainPageById('processes');
|
|
}
|
|
};
|
|
|
|
$scope.handleResponseFunction = handleResponseFunction;
|
|
|
|
_internalCreateModal({
|
|
template: 'editor-app/popups/unsaved-changes.html',
|
|
scope: $scope
|
|
}, $modal, $scope);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Always needed, cause the DOM element on wich the scroll event listeners are attached are changed for every new model
|
|
initScrollHandling();
|
|
|
|
var modelId = $routeParams.modelId;
|
|
editorManager.setModelId(modelId);
|
|
//we first initialize the stencilset used by the editor. The editorId is always the modelId.
|
|
$http.get(FLOWABLE.URL.getModel(modelId)).then(function (response) {
|
|
editorManager.setModelData(response);
|
|
return response;
|
|
}).then(function (modelData) {
|
|
if(modelData.data.model.stencilset.namespace == 'http://b3mn.org/stencilset/cmmn1.1#') {
|
|
return $http.get(FLOWABLE.URL.getCmmnStencilSet());
|
|
} else {
|
|
return $http.get(FLOWABLE.URL.getStencilSet());
|
|
}
|
|
}).then(function (response) {
|
|
var baseUrl = "http://b3mn.org/stencilset/";
|
|
editorManager.setStencilData(response.data);
|
|
//the stencilset alters the data ref!
|
|
var stencilSet = new ORYX.Core.StencilSet.StencilSet(baseUrl, response.data);
|
|
ORYX.Core.StencilSet.loadStencilSet(baseUrl, stencilSet, modelId);
|
|
//after the stencilset is loaded we make sure the plugins.xml is loaded.
|
|
return $http.get(ORYX.CONFIG.PLUGINS_CONFIG);
|
|
}).then(function (response) {
|
|
ORYX._loadPlugins(response.data);
|
|
return response;
|
|
}).then(function (response) {
|
|
editorManager.bootEditor();
|
|
}).catch(function (error) {
|
|
console.log(error);
|
|
});
|
|
|
|
//minihack to make sure mousebind events are processed if the modeler is used in an iframe.
|
|
//selecting an element and pressing "del" could sometimes not trigger an event.
|
|
jQuery(window).focus();
|
|
|
|
}]);
|
|
|
|
angular.module('flowableModeler')
|
|
.controller('EditorUnsavedChangesPopupCtrl', ['$rootScope', '$scope', '$http', '$location', '$window', function ($rootScope, $scope, $http, $location, $window) {
|
|
|
|
$scope.discard = function () {
|
|
if ($scope.handleResponseFunction) {
|
|
$scope.handleResponseFunction(true);
|
|
// Also clear any 'onbeforeunload', added by oryx
|
|
$window.onbeforeunload = undefined;
|
|
}
|
|
$scope.$hide();
|
|
};
|
|
|
|
$scope.save = function () {
|
|
if ($scope.handleResponseFunction) {
|
|
$scope.handleResponseFunction(false);
|
|
}
|
|
$scope.$hide();
|
|
};
|
|
|
|
$scope.cancel = function () {
|
|
if ($scope.handleResponseFunction) {
|
|
$scope.handleResponseFunction(null);
|
|
}
|
|
$scope.$hide();
|
|
};
|
|
}]);
|
|
|