<commit_before>/**
 * Author: Milica Kadic
 * Date: 10/21/14
 * Time: 2:51 PM
 */
'use strict';

angular.module('registryApp.dyole')
    .controller('PipelineCtrl', ['$scope', '$rootScope', '$element', '$window', '$timeout', '$injector', 'pipeline', 'App', 'rawPipeline', '$uibModal', '$templateCache', 'PipelineService', 'lodash', 'Notification', 'HotkeyRegistry', 'rawRabixWorkflow', function ($scope, $rootScope, $element, $window, $timeout, $injector, pipeline, App, rawPipeline, $modal, $templateCache, PipelineService, _, Notification, HotkeyRegistry, rawRabixWorkflow) {

        var Pipeline;
        var selector = '.pipeline';
        var timeoutId;

        $scope.view = {};
        $scope.view.canFlush = true;

        /* show usage hints to user flag */
        $scope.view.explanation = false;

        /* disable button if zoom limit is reached */
        $scope.view.disableZoomIn = false;
        $scope.view.disableZoomOut = false;

        /**
         * Initialize pipeline
         */
        var initPipeline = function (obj) {

            $scope.view.explanation = !obj || (obj.steps && obj.steps.length === 0) || (obj.dataLinks && obj.dataLinks.length === 0);

            Pipeline = pipeline.getInstance({
                model: typeof obj.steps !== 'undefined' ? obj || rawPipeline : rawPipeline,
                $parent: angular.element($element[0].querySelector(selector)),
                editMode: $scope.editMode
            });

            // Will be used to check if any of the buttons needs disabling
            Pipeline.initZoom();

        };

        if ($scope.pipeline){
            initPipeline($scope.pipeline);
        } else {
            initPipeline({});
        }

        if ($scope.previewNode) {

            var e = {
                clientX: 300,
                clientY: 150
            };

            Pipeline.addNode($scope.previewNode, e.clientX, e.clientY);

        }

        $scope.$watch('pipeline', function(n, o) {
            if (n !== o) {

                $scope.pipeline = n;

                if (angular.isDefined(Pipeline)) {
                    Pipeline.destroy();
                    Pipeline = null;
                }

                initPipeline(n);

                PipelineService.refresh();
            }
        });

        var fork = function (repoId, name) {
            $scope.pipeline.json = Pipeline.getJSON();

            $scope.pipeline.repo = repoId;

            if (name) {
                $scope.pipeline.name = name;
            }

        };

        /**
         * Save pipeline locally
         */
        $scope.$on('save-local', function (e, value) {
            if (value) {
                //Workflow.saveLocal(Pipeline.getJSON());
            }
        });

        var format = function () {

            return Pipeline.getJSON();
        };

        var getUrl = function (url) {

            $modal.open({
                template: $templateCache.get('views/partials/job-url-response.html'),
                controller: ['$scope', '$uibModalInstance', 'data', function($scope, $modalInstance, data) {

                    $scope.view = {};
                    $scope.data = data;

                    /**
                     * Close the modal window
                     */
                    $scope.ok = function () {
                        $modalInstance.close();
                    };

                }],
                resolve: { data: function () { return {trace: {message: 'Workflow link:', url:  url.url}}; }}
            });

        };

        /**
         * Drop node on the canvas
         *
         * @param {MouseEvent} e
         * @param {String} app object
         */
         var dropNode = function(e, app) {

            $scope.view.loading = true;
            $scope.view.explanation = false;

            var project = app.project.split('/'),
                projectOwner = project[0],
                projectSlug = project[1];

            App.getApp(projectOwner, projectSlug, app.app_name).then(function(result) {

                $scope.view.loading = false;

                if (typeof result.message === 'object' && !_.isEmpty(result.message)) {
                    Pipeline.addNode(result.message, e.clientX, e.clientY);
                } else {
                    console.error('App does not exist: Message: %s, Status: %s', result.message, result.status);
                    Notification.error('App does not exist: Message: '+ result.message +', Status: ' + result.status);
                }

            });
        };

        var onNodeDroppedOff = $rootScope.$on('node:dropped', function (e, data) {
            dropNode(data.e, data.app);
        });

        /**
         * Cancel timeout
         */
        var cancelTimeout = function () {
            if (angular.isDefined(timeoutId)) {
                $timeout.cancel(timeoutId);
                timeoutId = undefined;
            }
        };

        /**
         * Adjust size of the canvas when window size changes
         */
        var changeWidth = function () {
            if (Pipeline) {
                Pipeline.adjustSize();
            }
        };

        var lazyChangeWidth = _.debounce(changeWidth, 150);

        angular.element($window).on('resize', lazyChangeWidth);

        /**
         * Track sidebar toggle in order to adjust canvas size
         */
        var adjustSize = function (showSidebar) {

            cancelTimeout();

            timeoutId = $timeout(function () {
                Pipeline.adjustSize(showSidebar);
            }, 300);

        };

        var getEventObj = function () {
            if (Pipeline) {
                return Pipeline.Event
            } else {
                return false
            }
        };

        var updateMetadata = function (metadata) {
            if (Pipeline) {
                Pipeline.updateMetadata(metadata);
            }
        };

        var getSvgString = function () {
            if (Pipeline) {
                return Pipeline.getSvgString();
            }
        };

        /**
         * Track pipeline change
         */
        var onPipelineChangeOff = $rootScope.$on('pipeline:change', function (isDisplay) {
            $scope.pipelineChangeFn({value: {value: true, isDisplay: isDisplay}});
        });

        /**
         * Set fresh canvas
         */
        $scope.flush = function() {
            if (!$scope.view.canFlush) { return false; }

            var modalInstance = $modal.open({
                controller: 'ModalCtrl',
                template: $templateCache.get('views/partials/confirm-delete.html'),
                resolve: { data: function () {
                    return {
                        message: 'Are you sure you want to delete this workflow?'
                    }; }}
            });

            modalInstance.result.then(function () {
                App.flush();

                var p = Pipeline.getJSON();

                if (angular.isDefined(Pipeline)) {

                    var raw = angular.copy(rawRabixWorkflow);
                    raw.id = p.id;
                    raw.label = p.label;

                    Pipeline.destroy();
                    Pipeline = null;
                    initPipeline(raw);

                    PipelineService.refresh();

                }
            }, function () {
                return false;
            });

        };

        /**
         * Open modal with info for selected node
         *
         * @param e
         * @param model
         * @param schema
         */
        var onNodeInfo = function(e, model, schema) {

            var _getConnections = function () {
                var connections = Pipeline.getConnections();

                return _.filter(connections, function(connection){
                    return connection.end_node === model.id || connection.start_node === model.id
                });
            };

            var $modal = $injector.get('$uibModal');
            var $templateCache = $injector.get('$templateCache');

            var modalInstance = $modal.open({
                template: $templateCache.get('views/dyole/'+ ( schema ? 'io-' : '') +'node-info.html'),
                controller: 'ModalTabsCtrl',
                windowClass: 'modal-node',
                resolve: {data: function () { return {model: model, connections: _getConnections(), schema: schema};}}
            });

            modalInstance.result.then(function (data) {
                var scatter = data.scatter;

                if (!_.isEmpty(data.schema)) {
                    // get schema for i/o node ( copyes schema from *put )
                    var schema = model.inputs[0] || model.outputs[0];
                    schema.type = data.schema.type;

                    Pipeline.updateIOSchema(model.id, schema.type, data.schema.description);

                }

                if (scatter) {
                    model.scatter = scatter;
                } else {
                    model.scatter = false;
                    delete model.scatter;
                }

                $scope.pipelineChangeFn({value: {value: true, isDisplay: false}});

            });

        };

        var onNodeLabelEdit = function(e, opts, onEdit, onSave, scope) {

            var $modal = $injector.get('$uibModal');
            var $templateCache = $injector.get('$templateCache');
            var name = opts.name;
            var isSystem = opts.isSystem;

            var template = isSystem ? 'views/dyole/input-label-edit.html' : 'views/dyole/node-label-edit.html';
            $modal.open({
                template: $templateCache.get(template),
                controller: 'NodeEditCtrl',
                windowClass: 'modal-node',
                resolve: {data: function () { return {
                    name: name,
                    onEdit: onEdit,
                    onSave: onSave,
                    scope: scope
                }; }}
            });

        };

        var onIncludeInPorts = function (nodeId, inputId, value) {
            Pipeline.updateNodePorts(nodeId, inputId, value);
        };

        var getWorkflowHints = function () {
            return Pipeline.getHints();
        };

        var getRequireSBGMetadata = function () {
            return Pipeline.getRequireSBGMetadata();
        };

        var updateWorkflowSettings = function (hints, requireSBGMetadata) {
            return Pipeline.updateWorkflowSettings(hints,requireSBGMetadata);
        };

        var onNodeInfoOff = $rootScope.$on('node:info', onNodeInfo);
        var onNodeLabelEditOff = $rootScope.$on('node:label:edit', onNodeLabelEdit);

        $scope.$on('$destroy', function() {

            angular.element($window).off('resize', lazyChangeWidth);

            cancelTimeout();
            onPipelineChangeOff();
            onNodeDroppedOff();
            onNodeInfoOff();
            onNodeLabelEditOff();

            if (angular.isDefined(Pipeline)) {
                // not a single page app anymore
                //Pipeline.destroy();
                //Pipeline = null;
            }

        });

        var validate = function () {
            return App.validateJson(Pipeline.getJSON());
        };

        HotkeyRegistry.loadHotkeys([
            {name: 'delete', callback: Pipeline.deleteSelected, preventDefault: true, context: Pipeline},
            {name: 'backspace delete', callback: Pipeline.deleteSelected, preventDefault: true, context: Pipeline}
        ]);

        $scope.pipelineActions = {

            zoomIn: function () {

                if (Pipeline) {
                    Pipeline.zoomIn();
                }
            },
            zoomOut: function () {

                if (Pipeline) {
                    Pipeline.zoomOut();
                }

            }
        };

        /**
         * If scope controller is set, expose pipeline methods to service
         */
        if ($scope.controllerId) {

            var methods = {
                flush: $scope.flush,
                dropNode: dropNode,
                getUrl: getUrl,
                fork: fork,
                format: format,
                getJSON: format,
                validate: validate,
                adjustSize: adjustSize,
                getEventObj: getEventObj,
                updateMetadata: updateMetadata,
                getSvgString: getSvgString,
                onIncludeInPorts: onIncludeInPorts,
                getWorkflowHints: getWorkflowHints,
                getRequireSBGMetadata: getRequireSBGMetadata,
                updateWorkflowSettings: updateWorkflowSettings
            };

            PipelineService.setInstance($scope.controllerId, methods);

        }

    }]);
<commit_msg>FIX: Initial text remains when node is loaded on the canvas but not connected<commit_after>/**
 * Author: Milica Kadic
 * Date: 10/21/14
 * Time: 2:51 PM
 */
'use strict';

angular.module('registryApp.dyole')
    .controller('PipelineCtrl', ['$scope', '$rootScope', '$element', '$window', '$timeout', '$injector', 'pipeline', 'App', 'rawPipeline', '$uibModal', '$templateCache', 'PipelineService', 'lodash', 'Notification', 'HotkeyRegistry', 'rawRabixWorkflow', function ($scope, $rootScope, $element, $window, $timeout, $injector, pipeline, App, rawPipeline, $modal, $templateCache, PipelineService, _, Notification, HotkeyRegistry, rawRabixWorkflow) {

        var Pipeline;
        var selector = '.pipeline';
        var timeoutId;

        $scope.view = {};
        $scope.view.canFlush = true;

        /* show usage hints to user flag */
        $scope.view.explanation = false;

        /* disable button if zoom limit is reached */
        $scope.view.disableZoomIn = false;
        $scope.view.disableZoomOut = false;

        /**
         * Initialize pipeline
         */
        var initPipeline = function (obj) {

            $scope.view.explanation = !obj || (obj.steps && obj.steps.length === 0); // || (obj.dataLinks && obj.dataLinks.length === 0);

            Pipeline = pipeline.getInstance({
                model: typeof obj.steps !== 'undefined' ? obj || rawPipeline : rawPipeline,
                $parent: angular.element($element[0].querySelector(selector)),
                editMode: $scope.editMode
            });

            // Will be used to check if any of the buttons needs disabling
            Pipeline.initZoom();

        };

        if ($scope.pipeline){
            initPipeline($scope.pipeline);
        } else {
            initPipeline({});
        }

        if ($scope.previewNode) {

            var e = {
                clientX: 300,
                clientY: 150
            };

            Pipeline.addNode($scope.previewNode, e.clientX, e.clientY);

        }

        $scope.$watch('pipeline', function(n, o) {
            if (n !== o) {

                $scope.pipeline = n;

                if (angular.isDefined(Pipeline)) {
                    Pipeline.destroy();
                    Pipeline = null;
                }

                initPipeline(n);

                PipelineService.refresh();
            }
        });

        var fork = function (repoId, name) {
            $scope.pipeline.json = Pipeline.getJSON();

            $scope.pipeline.repo = repoId;

            if (name) {
                $scope.pipeline.name = name;
            }

        };

        /**
         * Save pipeline locally
         */
        $scope.$on('save-local', function (e, value) {
            if (value) {
                //Workflow.saveLocal(Pipeline.getJSON());
            }
        });

        var format = function () {

            return Pipeline.getJSON();
        };

        var getUrl = function (url) {

            $modal.open({
                template: $templateCache.get('views/partials/job-url-response.html'),
                controller: ['$scope', '$uibModalInstance', 'data', function($scope, $modalInstance, data) {

                    $scope.view = {};
                    $scope.data = data;

                    /**
                     * Close the modal window
                     */
                    $scope.ok = function () {
                        $modalInstance.close();
                    };

                }],
                resolve: { data: function () { return {trace: {message: 'Workflow link:', url:  url.url}}; }}
            });

        };

        /**
         * Drop node on the canvas
         *
         * @param {MouseEvent} e
         * @param {String} app object
         */
         var dropNode = function(e, app) {

            $scope.view.loading = true;
            $scope.view.explanation = false;

            var project = app.project.split('/'),
                projectOwner = project[0],
                projectSlug = project[1];

            App.getApp(projectOwner, projectSlug, app.app_name).then(function(result) {

                $scope.view.loading = false;

                if (typeof result.message === 'object' && !_.isEmpty(result.message)) {
                    Pipeline.addNode(result.message, e.clientX, e.clientY);
                } else {
                    console.error('App does not exist: Message: %s, Status: %s', result.message, result.status);
                    Notification.error('App does not exist: Message: '+ result.message +', Status: ' + result.status);
                }

            });
        };

        var onNodeDroppedOff = $rootScope.$on('node:dropped', function (e, data) {
            dropNode(data.e, data.app);
        });

        /**
         * Cancel timeout
         */
        var cancelTimeout = function () {
            if (angular.isDefined(timeoutId)) {
                $timeout.cancel(timeoutId);
                timeoutId = undefined;
            }
        };

        /**
         * Adjust size of the canvas when window size changes
         */
        var changeWidth = function () {
            if (Pipeline) {
                Pipeline.adjustSize();
            }
        };

        var lazyChangeWidth = _.debounce(changeWidth, 150);

        angular.element($window).on('resize', lazyChangeWidth);

        /**
         * Track sidebar toggle in order to adjust canvas size
         */
        var adjustSize = function (showSidebar) {

            cancelTimeout();

            timeoutId = $timeout(function () {
                Pipeline.adjustSize(showSidebar);
            }, 300);

        };

        var getEventObj = function () {
            if (Pipeline) {
                return Pipeline.Event
            } else {
                return false
            }
        };

        var updateMetadata = function (metadata) {
            if (Pipeline) {
                Pipeline.updateMetadata(metadata);
            }
        };

        var getSvgString = function () {
            if (Pipeline) {
                return Pipeline.getSvgString();
            }
        };

        /**
         * Track pipeline change
         */
        var onPipelineChangeOff = $rootScope.$on('pipeline:change', function (isDisplay) {
            $scope.pipelineChangeFn({value: {value: true, isDisplay: isDisplay}});
        });

        /**
         * Set fresh canvas
         */
        $scope.flush = function() {
            if (!$scope.view.canFlush) { return false; }

            var modalInstance = $modal.open({
                controller: 'ModalCtrl',
                template: $templateCache.get('views/partials/confirm-delete.html'),
                resolve: { data: function () {
                    return {
                        message: 'Are you sure you want to delete this workflow?'
                    }; }}
            });

            modalInstance.result.then(function () {
                App.flush();

                var p = Pipeline.getJSON();

                if (angular.isDefined(Pipeline)) {

                    var raw = angular.copy(rawRabixWorkflow);
                    raw.id = p.id;
                    raw.label = p.label;

                    Pipeline.destroy();
                    Pipeline = null;
                    initPipeline(raw);

                    PipelineService.refresh();

                }
            }, function () {
                return false;
            });

        };

        /**
         * Open modal with info for selected node
         *
         * @param e
         * @param model
         * @param schema
         */
        var onNodeInfo = function(e, model, schema) {

            var _getConnections = function () {
                var connections = Pipeline.getConnections();

                return _.filter(connections, function(connection){
                    return connection.end_node === model.id || connection.start_node === model.id
                });
            };

            var $modal = $injector.get('$uibModal');
            var $templateCache = $injector.get('$templateCache');

            var modalInstance = $modal.open({
                template: $templateCache.get('views/dyole/'+ ( schema ? 'io-' : '') +'node-info.html'),
                controller: 'ModalTabsCtrl',
                windowClass: 'modal-node',
                resolve: {data: function () { return {model: model, connections: _getConnections(), schema: schema};}}
            });

            modalInstance.result.then(function (data) {
                var scatter = data.scatter;

                if (!_.isEmpty(data.schema)) {
                    // get schema for i/o node ( copyes schema from *put )
                    var schema = model.inputs[0] || model.outputs[0];
                    schema.type = data.schema.type;

                    Pipeline.updateIOSchema(model.id, schema.type, data.schema.description);

                }

                if (scatter) {
                    model.scatter = scatter;
                } else {
                    model.scatter = false;
                    delete model.scatter;
                }

                $scope.pipelineChangeFn({value: {value: true, isDisplay: false}});

            });

        };

        var onNodeLabelEdit = function(e, opts, onEdit, onSave, scope) {

            var $modal = $injector.get('$uibModal');
            var $templateCache = $injector.get('$templateCache');
            var name = opts.name;
            var isSystem = opts.isSystem;

            var template = isSystem ? 'views/dyole/input-label-edit.html' : 'views/dyole/node-label-edit.html';
            $modal.open({
                template: $templateCache.get(template),
                controller: 'NodeEditCtrl',
                windowClass: 'modal-node',
                resolve: {data: function () { return {
                    name: name,
                    onEdit: onEdit,
                    onSave: onSave,
                    scope: scope
                }; }}
            });

        };

        var onIncludeInPorts = function (nodeId, inputId, value) {
            Pipeline.updateNodePorts(nodeId, inputId, value);
        };

        var getWorkflowHints = function () {
            return Pipeline.getHints();
        };

        var getRequireSBGMetadata = function () {
            return Pipeline.getRequireSBGMetadata();
        };

        var updateWorkflowSettings = function (hints, requireSBGMetadata) {
            return Pipeline.updateWorkflowSettings(hints,requireSBGMetadata);
        };

        var onNodeInfoOff = $rootScope.$on('node:info', onNodeInfo);
        var onNodeLabelEditOff = $rootScope.$on('node:label:edit', onNodeLabelEdit);

        $scope.$on('$destroy', function() {

            angular.element($window).off('resize', lazyChangeWidth);

            cancelTimeout();
            onPipelineChangeOff();
            onNodeDroppedOff();
            onNodeInfoOff();
            onNodeLabelEditOff();

            if (angular.isDefined(Pipeline)) {
                // not a single page app anymore
                //Pipeline.destroy();
                //Pipeline = null;
            }

        });

        var validate = function () {
            return App.validateJson(Pipeline.getJSON());
        };

        HotkeyRegistry.loadHotkeys([
            {name: 'delete', callback: Pipeline.deleteSelected, preventDefault: true, context: Pipeline},
            {name: 'backspace delete', callback: Pipeline.deleteSelected, preventDefault: true, context: Pipeline}
        ]);

        $scope.pipelineActions = {

            zoomIn: function () {

                if (Pipeline) {
                    Pipeline.zoomIn();
                }
            },
            zoomOut: function () {

                if (Pipeline) {
                    Pipeline.zoomOut();
                }

            }
        };

        /**
         * If scope controller is set, expose pipeline methods to service
         */
        if ($scope.controllerId) {

            var methods = {
                flush: $scope.flush,
                dropNode: dropNode,
                getUrl: getUrl,
                fork: fork,
                format: format,
                getJSON: format,
                validate: validate,
                adjustSize: adjustSize,
                getEventObj: getEventObj,
                updateMetadata: updateMetadata,
                getSvgString: getSvgString,
                onIncludeInPorts: onIncludeInPorts,
                getWorkflowHints: getWorkflowHints,
                getRequireSBGMetadata: getRequireSBGMetadata,
                updateWorkflowSettings: updateWorkflowSettings
            };

            PipelineService.setInstance($scope.controllerId, methods);

        }

    }]);
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc service
 * @name emuwebApp.loadedMetaDataService
 * @description
 * # loadedMetaDataService
 * Service in the emuwebApp.
 */
angular.module('emuwebApp')
	.service('loadedMetaDataService', function loadedMetaDataService(Validationservice) {
		// shared service object
		var sServObj = {};

		//////////////////////
		// private vars
		var uniqSessionList = [];
		var bundleList = [];
		var curBndl = {};
		var demoDbName = '';
		var drandropBundles = [];
		var rendOptBndlList = {}; // render optimized bundle list

		//////////////////////
		// private functions
		function genUniqSessionList(bndlList) {
			var sList = [];
			var fistSes;
			bndlList.forEach(function (bndl, idx) {
				sList[bndl.session] = {
					'collapsed': true
				};
				if (idx === 0) {
					fistSes = bndl.session;
				}
			});
			// open fist session up
			sList[fistSes].collapsed = false;
			return sList;
		}

		function genRendOptBndlList(bndlList) {
			bndlList.forEach(function (bndl, idx) {
				if (rendOptBndlList[bndl.session] === undefined) {
					rendOptBndlList[bndl.session] = [];
				}
				rendOptBndlList[bndl.session].push(bndl);
			});
			return rendOptBndlList;
		}

		//////////////////////
		//////////////////////
		// public API

		///////////////
		// bundleList

		/**
		 * setter for bundleList
		 * @returns validation result for bundle list
		 */
		sServObj.setBundleList = function (bList) {
			// validate
			var validRes = Validationservice.validateJSO('bundleListSchema', bList);
			if (validRes === true) {
				// set
				bundleList = bList;
				// generate uniqSessionList
				uniqSessionList = genUniqSessionList(bundleList);
				// generate render optimized bundlList
				rendOptBndlList = genRendOptBndlList(bundleList);
			}
			return validRes;
		};

		/**
		 * getter for bundleList
		 */
		sServObj.getBundleList = function () {
			return bundleList;
		};

		/**
		 * getter for rendOptBndlList
		 */
		sServObj.getRendOptBndlList = function () {
			return rendOptBndlList;
		};

		///////////
		// curBndl

		/**
		 * getter curBndl
		 */
		sServObj.getCurBndl = function () {
			return curBndl;
		};

		/**
		 * setter curBndl
		 */
		sServObj.setCurBndl = function (bndl) {
			curBndl = bndl;
		};

		/**
		 * getter curBndl name
		 */
		sServObj.getCurBndlName = function () {
			return curBndl.name;
		};

		/**
		 * setter curBndl name
		 */
		sServObj.setCurBndlName = function (name) {
			curBndl.name = name;
		};

		//////////////
		// demoDbName

		/**
		 * setter demoDbName
		 */
		sServObj.setDemoDbName = function (name) {
			demoDbName = name;
		};

		/**
		 * getter demoDbName
		 */
		sServObj.getDemoDbName = function () {
			return demoDbName;
		};

		///////////////////
		// uniqSessionList

		/**
		 *
		 */
		sServObj.toggleCollapseSession = function (session) {
			uniqSessionList[session].collapsed = !uniqSessionList[session].collapsed;
			// close all other sessions
			Object.keys(uniqSessionList).forEach(function (key) {
				if (key !== session) {
					uniqSessionList[key].collapsed = true;
				}
			});
		};

		/**
		 *
		 */
		// sServObj.updateCollapseSessionState = function (text) {
		// 	angular.forEach(sServObj.getBundleList(), function (bundle) {
		// 		if (bundle.name.indexOf(text)) {
		// 			uniqSessionList[bundle.session].collapsed = false;
		// 		} else {
		// 			uniqSessionList[bundle.session].collapsed = true;
		// 		}
		// 	});
		// };

		/**
		 *
		 */
		sServObj.getSessionCollapseState = function (session) {
			return uniqSessionList[session].collapsed;
		};

		///////////////////
		// other functions

		/**
		 * reset all private vals to init state
		 */
		sServObj.resetToInitState = function () {
			uniqSessionList = [];
			bundleList = [];
			curBndl = {};
		};

		return (sServObj);
	});<commit_msg>fixed reset to init state<commit_after>'use strict';

/**
 * @ngdoc service
 * @name emuwebApp.loadedMetaDataService
 * @description
 * # loadedMetaDataService
 * Service in the emuwebApp.
 */
angular.module('emuwebApp')
	.service('loadedMetaDataService', function loadedMetaDataService(Validationservice) {
		// shared service object
		var sServObj = {};

		//////////////////////
		// private vars
		var uniqSessionList = [];
		var bundleList = [];
		var curBndl = {};
		var demoDbName = '';
		var drandropBundles = [];
		var rendOptBndlList = {}; // render optimized bundle list

		//////////////////////
		// private functions
		function genUniqSessionList(bndlList) {
			var sList = [];
			var fistSes;
			bndlList.forEach(function (bndl, idx) {
				sList[bndl.session] = {
					'collapsed': true
				};
				if (idx === 0) {
					fistSes = bndl.session;
				}
			});
			// open fist session up
			sList[fistSes].collapsed = false;
			return sList;
		}

		function genRendOptBndlList(bndlList) {
			bndlList.forEach(function (bndl, idx) {
				if (rendOptBndlList[bndl.session] === undefined) {
					rendOptBndlList[bndl.session] = [];
				}
				rendOptBndlList[bndl.session].push(bndl);
			});
			return rendOptBndlList;
		}

		//////////////////////
		//////////////////////
		// public API

		///////////////
		// bundleList

		/**
		 * setter for bundleList
		 * @returns validation result for bundle list
		 */
		sServObj.setBundleList = function (bList) {
			// validate
			var validRes = Validationservice.validateJSO('bundleListSchema', bList);
			if (validRes === true) {
				// set
				bundleList = bList;
				// generate uniqSessionList
				uniqSessionList = genUniqSessionList(bundleList);
				// generate render optimized bundlList
				rendOptBndlList = genRendOptBndlList(bundleList);
			}
			return validRes;
		};

		/**
		 * getter for bundleList
		 */
		sServObj.getBundleList = function () {
			return bundleList;
		};

		/**
		 * getter for rendOptBndlList
		 */
		sServObj.getRendOptBndlList = function () {
			return rendOptBndlList;
		};

		///////////
		// curBndl

		/**
		 * getter curBndl
		 */
		sServObj.getCurBndl = function () {
			return curBndl;
		};

		/**
		 * setter curBndl
		 */
		sServObj.setCurBndl = function (bndl) {
			curBndl = bndl;
		};

		/**
		 * getter curBndl name
		 */
		sServObj.getCurBndlName = function () {
			return curBndl.name;
		};

		/**
		 * setter curBndl name
		 */
		sServObj.setCurBndlName = function (name) {
			curBndl.name = name;
		};

		//////////////
		// demoDbName

		/**
		 * setter demoDbName
		 */
		sServObj.setDemoDbName = function (name) {
			demoDbName = name;
		};

		/**
		 * getter demoDbName
		 */
		sServObj.getDemoDbName = function () {
			return demoDbName;
		};

		///////////////////
		// uniqSessionList

		/**
		 *
		 */
		sServObj.toggleCollapseSession = function (session) {
			uniqSessionList[session].collapsed = !uniqSessionList[session].collapsed;
			// close all other sessions
			Object.keys(uniqSessionList).forEach(function (key) {
				if (key !== session) {
					uniqSessionList[key].collapsed = true;
				}
			});
		};

		/**
		 *
		 */
		// sServObj.updateCollapseSessionState = function (text) {
		// 	angular.forEach(sServObj.getBundleList(), function (bundle) {
		// 		if (bundle.name.indexOf(text)) {
		// 			uniqSessionList[bundle.session].collapsed = false;
		// 		} else {
		// 			uniqSessionList[bundle.session].collapsed = true;
		// 		}
		// 	});
		// };

		/**
		 *
		 */
		sServObj.getSessionCollapseState = function (session) {
			return uniqSessionList[session].collapsed;
		};

		///////////////////
		// other functions

		/**
		 * reset all private vals to init state
		 */
		sServObj.resetToInitState = function () {
			uniqSessionList = [];
			bundleList = [];
			curBndl = {};
			rendOptBndlList = {};
		};

		return (sServObj);
	});<|endoftext|>


<commit_before>var ObjectManager = function() {
    this.proxyFactory = new ProxyFactory(this);

    var self = this;

    var objectMap = {};

    var addToObjectMap = function(entityClass, entity) {
        var name = entityClass.$name;
        objectMap[name] = objectMap[name] || [];
        if (objectMap[name].indexOf(entity) < 0) {
            objectMap[name].push(entity);
        }
    }
    var getFromObjectMap = function(entityClass, id) {
        if (id === undefined) {
            return null;
        }
        if (!(entityClass.$name in objectMap)) {
            return null;
        }
        var map = objectMap[entityClass.$name];
        for (var i in map) {
            if (map[i].id == id) {
                return map[i];
            }
        }
        return null;
    }

    var defineProperty = function(entity, name, property, data) {
        if (!('transform' in property)) {
            throw Error('transform is mandatory');
        }

        self.loadPropertyValue(entity, name, property, data);

        var definition = {};
        if ('get' in property) {
            definition.get = function() {
                return property.get.call(entity);
            }
        }
        if ('set' in property) {
            definition.set = function(value) {
                entity.$dirty = true;
                return property.set.call(entity, value);
            }
        }

        definition.enumerable = true;

        Object.defineProperty(entity, name, definition);
    };

    this.loadPropertyValue = function(entity, name, property, data) {
        entity.$raw[name] = data[name];
        var value = property.transform(data);
        entity.$values[name] = value;
    }

    this.update = function(existing, data) {
        for (var i in existing.$schema) {
            self.loadPropertyValue(existing, i, existing.$schema[i], data);
        }
    }

    this.create = function(entityClass, data) {
        var entity = new entityClass;

        data = data || {};

        for (var i in entity.$schema) {
            defineProperty(entity, i, entity.$schema[i], data);
        }

        var existing = getFromObjectMap(entityClass, data.id);
        if (null !== existing) {
            if (!(existing instanceof this.proxyFactory.getProxyClass(entityClass))) {
                this.update(existing, data);
                return existing;
            }

            existing.object = entity;
            existing.initialize(this);
            return existing;
        }
        entity.initialize(this);

        addToObjectMap(entityClass, entity);
        return entity;
    };

    this.get = function(entityClass, id) {
        var entity = getFromObjectMap(entityClass, id);
        if (null === entity) {
            throw Error('not loaded');
        }

        return entity;
    }

    this.getAll = function(entityClass) {
        return objectMap[entityClass.$name] || {};
    }

    this.toArray = function(entity) {
        var data = {};
        for (var i in entity.$schema) {
            if ('reverseTransform' in entity.$schema[i]) {
                entity.$schema[i].reverseTransform.call(entity, data);
            }
        }
        return data;
    }

    this.save = function(entity) {
        var data = {};

        for (var i in entity.$schema) {
            var property = entity.$schema[i];
            if (!('reverseTransform' in property)) {
                continue;
            }

            property.reverseTransform.call(entity, data);
        }
        entity.$dirty = false;

        repo.update(entity, data);
    };

    this.getReference = function(entityClass, id) {
        var reference;

        reference = getFromObjectMap(entityClass, id);
        if (null === reference) {
            reference = this.createProxy(entityClass, id);
        }

        return reference;
    }
    this.createProxy = function(entityClass, id) {
        var proxy = this.proxyFactory.createProxy(entityClass, id);
        addToObjectMap(entityClass, proxy);
        return proxy;
    }

    this.loadSchema = function(className, schemaClassName) {

        className.prototype = Object.create(Entity.prototype);
        className.prototype.constructor = className;
        className.prototype.$schema = new schemaClassName;

        var schema = className.prototype.$schema;
        for (var i in schema) {
            if (schema[i] instanceof RRM.Relation.Base) {
                schema[i].om = self;
            }
        }
    }
}<commit_msg>added doc comments to the ObjectManager class<commit_after>var ObjectManager = function() {
    this.proxyFactory = new ProxyFactory(this);

    var self = this;

    var objectMap = {};

    /**
     * Store this entity into the object map for later reference
     *
     * @param entityClass
     * @param entity
     */
    var addToObjectMap = function(entityClass, entity) {
        var name = entityClass.$name;
        objectMap[name] = objectMap[name] || [];
        if (objectMap[name].indexOf(entity) < 0) {
            objectMap[name].push(entity);
        }
    }

    /**
     * Fetch the references entity from the object map or null if it is not stored.
     *
     * @param entityClass
     * @param id
     * @returns entity|null
     */
    var getFromObjectMap = function(entityClass, id) {
        if (id === undefined) {
            return null;
        }
        if (!(entityClass.$name in objectMap)) {
            return null;
        }
        var map = objectMap[entityClass.$name];
        for (var i in map) {
            if (map[i].id == id) {
                return map[i];
            }
        }
        return null;
    }

    /**
     * When constructing the entity class, this method will add the property as defined in the schema.
     *
     * @param entity
     * @param name
     * @param property
     * @param data the value
     */
    var defineProperty = function(entity, name, property, data) {
        if (!('transform' in property)) {
            throw Error('transform is mandatory');
        }

        self.loadPropertyValue(entity, name, property, data);

        var definition = {};
        if ('get' in property) {
            definition.get = function() {
                return property.get.call(entity);
            }
        }
        if ('set' in property) {
            definition.set = function(value) {
                entity.$dirty = true;
                return property.set.call(entity, value);
            }
        }

        definition.enumerable = true;

        Object.defineProperty(entity, name, definition);
    };

    /**
     * With the original data, first update the $raw object with it, then transform the value and
     * store the result in the $values object
     *
     * @param entity
     * @param name
     * @param property
     * @param data
     */
    this.loadPropertyValue = function(entity, name, property, data) {
        entity.$raw[name] = data[name];
        var value = property.transform(data);
        entity.$values[name] = value;
    }

    /**
     * Update the data of an existing entity
     *
     * @param existing
     * @param data
     */
    this.update = function(existing, data) {
        for (var i in existing.$schema) {
            self.loadPropertyValue(existing, i, existing.$schema[i], data);
        }
    }

    /**
     * Create an entity with the specified data. If the entity already exists, the data is updated in that entity
     * instead. If the entity already exists as an unloaded Proxy, the Proxy will be loaded with the specified data.
     *
     * @param entityClass
     * @param data
     * @returns entity object (new or existing)
     */
    this.create = function(entityClass, data) {
        var entity = new entityClass;

        data = data || {};

        for (var i in entity.$schema) {
            defineProperty(entity, i, entity.$schema[i], data);
        }

        var existing = getFromObjectMap(entityClass, data.id);
        if (null !== existing) {
            if (!(existing instanceof this.proxyFactory.getProxyClass(entityClass))) {
                this.update(existing, data);
                return existing;
            }

            existing.object = entity;
            existing.initialize(this);
            return existing;
        }
        entity.initialize(this);

        addToObjectMap(entityClass, entity);
        return entity;
    };

    /**
     * Fetch an entity by ID
     *
     * @param entityClass
     * @param id
     * @throws Error if the entity is not loaded or proxied
     * @returns {entity|null}
     */
    this.get = function(entityClass, id) {
        var entity = getFromObjectMap(entityClass, id);
        if (null === entity) {
            throw Error('not loaded');
        }

        return entity;
    }

    /**
     * Fetch all entities of the specified class
     *
     * @param entityClass
     * @returns Object of entities mapped by ID
     */
    this.getAll = function(entityClass) {
        return objectMap[entityClass.$name] || {};
    }

    /**
     * Convert the entity to a simple object
     * @param entity
     * @returns object
     */
    this.toArray = function(entity) {
        var data = {};
        for (var i in entity.$schema) {
            if ('reverseTransform' in entity.$schema[i]) {
                entity.$schema[i].reverseTransform.call(entity, data);
            }
        }
        return data;
    }

    /**
     * Does the same as this.get(), but instead of throwing an Error, it will create a Proxy object if the entity is
     * not loaded.
     *
     * @param entityClass
     * @param id
     * @returns entity
     */
    this.getReference = function(entityClass, id) {
        var reference;

        reference = getFromObjectMap(entityClass, id);
        if (null === reference) {
            reference = this.createProxy(entityClass, id);
        }

        return reference;
    }

    /**
     * Create a new Proxy for the specified entity class and ID
     *
     * @param entityClass
     * @param id
     * @returns Proxy
     */
    this.createProxy = function(entityClass, id) {
        var proxy = this.proxyFactory.createProxy(entityClass, id);
        addToObjectMap(entityClass, proxy);
        return proxy;
    }

    /**
     * Load the specified schema into the manager
     *
     * @param className
     * @param schemaClassName
     */
    this.loadSchema = function(className, schemaClassName) {

        className.prototype = Object.create(Entity.prototype);
        className.prototype.constructor = className;
        className.prototype.$schema = new schemaClassName;

        var schema = className.prototype.$schema;
        for (var i in schema) {
            if (schema[i] instanceof RRM.Relation.Base) {
                schema[i].om = self;
            }
        }
    }
}<|endoftext|>


<commit_before>/**
 * Created by daniel.irwin on 8/26/16.
 */
module.exports = function(opts){

    var userCounter = 0;

    opts.metrics({
        name    : 'Realtime user',
        value   : function() {
            return userCounter;
        },
        alert : {
            mode     : 'threshold',
            value    : 500
        }
    });

    function addUser(){
        userCounter++;
    }

    function removeUser(){
        userCounter--;
    }

    var myUsers = {};

    function getUserIp(req){
        return req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    }

    function toBase64(input) {
        return new Buffer(input, 'utf8').toString('base64');
    }

    function toUtf8(input) {
        return new Buffer(input, 'base64').toString('utf8');
    }

    function middleware(req, res, next){
        var username = req.cookies.username || req.query.username;
        //if(!username){
        //    res.status(400);
        //    return res.send('must include username');
        //}

        addUser();

        res.on('end', function(){
           removeUser();
        });

        var guid;
        if(!req.cookies['cat-cam']){
            var epochNow = new Date().getTime().toString();
            guid = getUserIp(req) + '.' + username + '.' + toBase64(epochNow);
            res.cookie('cat-cam', guid);
        }
        else {
            guid = req.cookies['cat-cam'];
        }

        if(!myUsers[guid]){
            myUsers[guid] = {
                ip : getUserIp(req),
                guid : guid,
                login : new Date().toISOString(),
                username : username,
                hits : 0
            };
        }
        else {
            myUsers[guid].hits++
        }
        next();
    }

    function getUsers(){
        return Object.keys(myUsers).map((key) =>{
            if(myUsers && myUsers[key]) {
                return myUsers[key].guid;
            }
            return 'unknown';
        });
    }

    function userHits(){
        return Object.keys(myUsers).map((key) => {
           if(myUsers && myUsers[key]){
               var result = {};
               result[myUsers[key].username] = myUsers[key].hits;
               return result;
           }
            return '';
        });
    }

    return {
        middleware : middleware,
        getUsers : getUsers,
        userHits : userHits
    }

};<commit_msg>req instead of res<commit_after>/**
 * Created by daniel.irwin on 8/26/16.
 */
module.exports = function(opts){

    var userCounter = 0;

    opts.metrics({
        name    : 'Realtime user',
        value   : function() {
            return userCounter;
        },
        alert : {
            mode     : 'threshold',
            value    : 500
        }
    });

    function addUser(){
        userCounter++;
    }

    function removeUser(){
        userCounter--;
    }

    var myUsers = {};

    function getUserIp(req){
        return req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    }

    function toBase64(input) {
        return new Buffer(input, 'utf8').toString('base64');
    }

    function toUtf8(input) {
        return new Buffer(input, 'base64').toString('utf8');
    }

    function middleware(req, res, next){
        var username = req.cookies.username || req.query.username;
        //if(!username){
        //    res.status(400);
        //    return res.send('must include username');
        //}

        addUser();

        req.on('end', function(){
           removeUser();
        });

        var guid;
        if(!req.cookies['cat-cam']){
            var epochNow = new Date().getTime().toString();
            guid = getUserIp(req) + '.' + username + '.' + toBase64(epochNow);
            res.cookie('cat-cam', guid);
        }
        else {
            guid = req.cookies['cat-cam'];
        }

        if(!myUsers[guid]){
            myUsers[guid] = {
                ip : getUserIp(req),
                guid : guid,
                login : new Date().toISOString(),
                username : username,
                hits : 0
            };
        }
        else {
            myUsers[guid].hits++
        }
        next();
    }

    function getUsers(){
        return Object.keys(myUsers).map((key) =>{
            if(myUsers && myUsers[key]) {
                return myUsers[key].guid;
            }
            return 'unknown';
        });
    }

    function userHits(){
        return Object.keys(myUsers).map((key) => {
           if(myUsers && myUsers[key]){
               var result = {};
               result[myUsers[key].username] = myUsers[key].hits;
               return result;
           }
            return '';
        });
    }

    return {
        middleware : middleware,
        getUsers : getUsers,
        userHits : userHits
    }

};<|endoftext|>


<commit_before>var LoadingBar = require('./LoadingBar.js');
var Percentage = require('./Percentage.js');

function Overlay(parentElement) {
    'use strict';
    this.parentElement = parentElement;
    this.idName = "qLoverlay";
    this.className = "queryloader__overlay";
    this.element = null;
    this.loadingBar = null;
    this.percentage = null;
    this.barColor = "#ff0000";
    this.backgroundColor = "#000";
    this.barHeight = 1;
    this.fadeOutTime = 300;
    this.showPercentage = false;
}

Overlay.prototype.init = function () {
    "use strict";
    this.create();

    this.loadingBar = new LoadingBar();
    this.loadingBar.barHeight = this.barHeight;
    this.loadingBar.barColor = this.barColor;
    this.loadingBar.create();
    this.element.appendChild(this.loadingBar.element);

    if (this.showPercentage) {
        this.percentage = new Percentage();
        this.percentage.barColor = this.barColor;
        this.percentage.create();
        this.element.appendChild(this.percentage.element);
    }

    this.parentElement.appendChild(this.element);
};

Overlay.prototype.create = function () {
    'use strict';
    this.element = (document.querySelector("#" + this.idName) || document.createElement("div"));
    this.element.setAttribute("class", this.className);
    this.element.setAttribute("id", this.idName);
    this.applyStyling();
};

Overlay.prototype.applyStyling = function () {
    'use strict';
    //determine postion of overlay and set parent position
    this.element.style.position = this.calculatePosition();
    this.element.style.width = "100%";
    this.element.style.height = "100%";
    this.element.style.backgroundColor = this.backgroundColor;
    this.element.style.backgroundPosition = "fixed";
    this.element.style.zIndex = 666999; //very HIGH
    this.element.style.top = "0";
    this.element.style.left = "0";

    this.element.style.WebkitTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.MozTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.OTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.MsTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.Transition = "opacity " + this.fadeOutTime + "ms";
};

Overlay.prototype.calculatePosition = function () {
    'use strict';
    var overlayPosition = "absolute";

    if (this.parentElement.tagName.toLowerCase() === "body") {
        overlayPosition = "fixed";
    } else {
        if (this.parentElement.style.position !== "fixed" || this.parentElement.style.position !== "absolute" ) {
            this.parentElement.style.position = "relative";
        }
    }

    return overlayPosition;
};

Overlay.prototype.updateProgress = function (percentage, time) {
    "use strict";
    if (this.loadingBar !== null) {
        this.loadingBar.updateProgress(percentage, time);
    }

    if (this.percentage !== null) {
        this.percentage.updateProgress(percentage, time);
    }
};

Overlay.prototype.remove = function () {
    "use strict";
    this.element.parentNode.removeChild(this.element);
};

module.exports = Overlay;<commit_msg>Pass id to percentage object<commit_after>var LoadingBar = require('./LoadingBar.js');
var Percentage = require('./Percentage.js');

function Overlay(parentElement) {
    'use strict';
    this.parentElement = parentElement;
    this.idName = "qLoverlay";
    this.percentageId = "qlPercentage";
    this.className = "queryloader__overlay";
    this.element = null;
    this.loadingBar = null;
    this.percentage = null;
    this.barColor = "#ff0000";
    this.backgroundColor = "#000";
    this.barHeight = 1;
    this.fadeOutTime = 300;
    this.showPercentage = false;
}

Overlay.prototype.init = function () {
    "use strict";
    this.create();

    this.loadingBar = new LoadingBar();
    this.loadingBar.barHeight = this.barHeight;
    this.loadingBar.barColor = this.barColor;
    this.loadingBar.create();
    this.element.appendChild(this.loadingBar.element);

    if (this.showPercentage) {
        this.percentage = new Percentage();
        this.percentage.barColor = this.barColor;
        this.percentage.idName = this.percentageId;
        this.percentage.create();
        this.element.appendChild(this.percentage.element);
    }

    this.parentElement.appendChild(this.element);
};

Overlay.prototype.create = function () {
    'use strict';
    this.element = (document.querySelector("#" + this.idName) || document.createElement("div"));
    this.element.setAttribute("class", this.className);
    this.element.setAttribute("id", this.idName);
    this.applyStyling();
};

Overlay.prototype.applyStyling = function () {
    'use strict';
    //determine postion of overlay and set parent position
    this.element.style.position = this.calculatePosition();
    this.element.style.width = "100%";
    this.element.style.height = "100%";
    this.element.style.backgroundColor = this.backgroundColor;
    this.element.style.backgroundPosition = "fixed";
    this.element.style.zIndex = 666999; //very HIGH
    this.element.style.top = "0";
    this.element.style.left = "0";

    this.element.style.WebkitTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.MozTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.OTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.MsTransition = "opacity " + this.fadeOutTime + "ms";
    this.element.style.Transition = "opacity " + this.fadeOutTime + "ms";
};

Overlay.prototype.calculatePosition = function () {
    'use strict';
    var overlayPosition = "absolute";

    if (this.parentElement.tagName.toLowerCase() === "body") {
        overlayPosition = "fixed";
    } else {
        if (this.parentElement.style.position !== "fixed" || this.parentElement.style.position !== "absolute" ) {
            this.parentElement.style.position = "relative";
        }
    }

    return overlayPosition;
};

Overlay.prototype.updateProgress = function (percentage, time) {
    "use strict";
    if (this.loadingBar !== null) {
        this.loadingBar.updateProgress(percentage, time);
    }

    if (this.percentage !== null) {
        this.percentage.updateProgress(percentage, time);
    }
};

Overlay.prototype.remove = function () {
    "use strict";
    this.element.parentNode.removeChild(this.element);
};

module.exports = Overlay;<|endoftext|>


<commit_before>/*
 * Copyright (C) 2019 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import {bool, func} from 'prop-types'
import React from 'react'
import RichContentEditor from '@canvas/rce/RichContentEditor'
import {Submission} from '@canvas/assignments/graphql/student/Submission'
import LoadingIndicator from '@canvas/loading-indicator'

// This is the interval (in ms) at which we check for changes to the text
// content
const checkForChangesIntervalMS = 250

// This is how long we wait to see that changes have stopped before actually
// saving the draft
const saveDraftDelayMS = 1000

export default class TextEntry extends React.Component {
  static propTypes = {
    createSubmissionDraft: func,
    onContentsChanged: func,
    submission: Submission.shape,
    readOnly: bool
  }

  state = {
    editorLoaded: false,
    renderingEditor: false
  }

  _isMounted = false

  _saveDraftTimer = null

  _checkForChangesTimer = null

  _lastSavedContent = null

  getDraftBody = () => {
    const {submission} = this.props
    if (['graded', 'submitted'].includes(submission.state)) {
      // If this attempt has been submitted/graded, use it
      return submission.body
    } else if (submission.submissionDraft != null) {
      // If a draft object exists, get the submission contents from it
      return submission.submissionDraft.body
    }

    // If the submission is marked as unsubmitted and there's no draft object,
    // the user has started a new attempt but not entered any text, so return
    // an empty string. The body attribute may contain the contents of a
    // previous attempt, which we don't want.
    return ''
  }

  componentDidMount() {
    this._isMounted = true
    window.addEventListener('beforeunload', this.beforeunload.bind(this))

    if (this.getDraftBody() != null && !this.state.editorLoaded) {
      this.loadRCE()
    }
  }

  componentDidUpdate(prevProps) {
    if (this._tinyeditor == null) {
      return
    }

    if (this.props.readOnly !== prevProps.readOnly) {
      this._tinyeditor.mode.set(this.props.readOnly ? 'readonly' : 'design')
    }

    if (this.props.submission.attempt !== prevProps.submission.attempt) {
      const body = this.getDraftBody()
      this._tinyeditor.setContent(body)
      this._lastSavedContent = body

      if (!this.props.readOnly) {
        this.handleEditorIframeFocus()
        this.handleEditorFocus()
      }
    }
  }

  componentWillUnmount() {
    this._isMounted = false
    window.removeEventListener('beforeunload', this.beforeunload.bind(this))

    if (this.state.editorLoaded) {
      this.unloadRCE()
    }
  }

  beforeunload(e) {
    if (this.state.editorLoaded && this.getDraftBody() !== this.getRCEText()) {
      e.preventDefault()
      e.returnValue = true
    }
  }

  // Note: I believe there's a bug in tinymce, that
  // if you set focus:true to give the editor focus on init,
  // then the internal bookkeeping doesn't know it has focus
  // and it does not handle the focusout event correctly.
  // Start w/o focus, then give it focus after initialization
  // in this.handleRCEInit
  loadRCE() {
    this.setState({editorLoaded: true, renderingEditor: true}, () => {
      RichContentEditor.loadNewEditor(this._textareaRef, {
        focus: false,
        manageParent: false,
        tinyOptions: {
          init_instance_callback: this.handleRCEInit,
          height: 300
        },
        onFocus: this.handleEditorFocus,
        onBlur: () => {}
      })
    })
  }

  unloadRCE() {
    const documentContent = document.getElementById('content')
    if (documentContent) {
      const editorIframe = documentContent.querySelector('[id^="random_editor"]')
      if (editorIframe) {
        editorIframe.removeEventListener('focus', this.handleEditorIframeFocus)
      }
    }
    if (this._textareaRef) {
      RichContentEditor.destroyRCE(this._textareaRef)
    }
    this._textareaRef = null
    this.setState({editorLoaded: false, renderingEditor: false})

    clearInterval(this._checkForChangesTimer)
    clearTimeout(this._saveDraftTimer)
  }

  checkForChanges = () => {
    // The idea here:
    // - Every time this function is called (currently several times a second),
    //   check whether the contents of the editor have changed, assuming we're
    //   in a state where we care about changes.
    // - If we see changes, call the onContentsChanged prop, and schedule a
    //   timer to actually save the draft. Further changes to the content will
    //   cancel/re-schedule this timer, so that we only actually save the draft
    //   after the user has stopped making changes for some time.
    const {submission} = this.props

    const isNewAttempt = submission.submissionDraft == null && submission.state === 'unsubmitted'
    // If read-only *or* this is a brand new attempt with no content,
    // we don't want to save a draft, so don't bother comparing
    if (this.props.readOnly || (this._tinyeditor.getContent() === '' && isNewAttempt)) {
      return
    }

    const editorContents = this._tinyeditor.getContent()
    if (this._lastSavedContent !== editorContents) {
      this._lastSavedContent = editorContents

      this.props.onContentsChanged()

      clearTimeout(this._saveDraftTimer)
      this._saveDraftTimer = setTimeout(() => {
        this.saveSubmissionDraft({attempt: submission.attempt, rceText: editorContents})
      }, saveDraftDelayMS)
    }
  }

  handleRCEInit = tinyeditor => {
    this._tinyeditor = tinyeditor
    tinyeditor.mode.set(this.props.readOnly ? 'readonly' : 'design')

    const draftBody = this.getDraftBody()
    tinyeditor.setContent(draftBody)
    this._lastSavedContent = draftBody
    this._checkForChangesTimer = setInterval(this.checkForChanges, checkForChangesIntervalMS)

    const documentContent = document.getElementById('content')
    if (documentContent) {
      const editorIframe = documentContent.querySelector('[id^="random_editor"]')
      if (editorIframe) {
        editorIframe.addEventListener('focus', this.handleEditorIframeFocus)
        this._tinyeditor.focus()
      }
    }
    this.setState({renderingEditor: false})
  }

  handleEditorIframeFocus = _event => {
    this._tinyeditor.focus()
  }

  handleEditorFocus = _event => {
    // these two lines put the caret at the end of the text when focused
    this._tinyeditor.selection.select(this._tinyeditor.getBody(), true)
    this._tinyeditor.selection.collapse(false)
  }

  setTextareaRef = el => {
    this._textareaRef = el
  }

  getRCEText = () => {
    return RichContentEditor.callOnRCE(this._textareaRef, 'get_code')
  }

  saveSubmissionDraft = async ({attempt, rceText}) => {
    await this.props.createSubmissionDraft({
      variables: {
        id: this.props.submission.id,
        activeSubmissionType: 'online_text_entry',
        attempt: attempt || 1,
        body: rceText
      }
    })
  }

  render() {
    return (

        {this.state.renderingEditor && <LoadingIndicator />}

          <textarea
            defaultValue={this.getDraftBody()}
            readOnly={this.props.readOnly}
            ref={this.setTextareaRef}
          />

    )
  }
}
<commit_msg>Remove RCE confirm-unsaved-changes behavior<commit_after>/*
 * Copyright (C) 2019 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import {bool, func} from 'prop-types'
import React from 'react'
import RichContentEditor from '@canvas/rce/RichContentEditor'
import {Submission} from '@canvas/assignments/graphql/student/Submission'
import LoadingIndicator from '@canvas/loading-indicator'

// This is the interval (in ms) at which we check for changes to the text
// content
const checkForChangesIntervalMS = 250

// This is how long we wait to see that changes have stopped before actually
// saving the draft
const saveDraftDelayMS = 1000

export default class TextEntry extends React.Component {
  static propTypes = {
    createSubmissionDraft: func,
    onContentsChanged: func,
    submission: Submission.shape,
    readOnly: bool
  }

  state = {
    editorLoaded: false,
    renderingEditor: false
  }

  _isMounted = false

  _saveDraftTimer = null

  _checkForChangesTimer = null

  _lastSavedContent = null

  getDraftBody = () => {
    const {submission} = this.props
    if (['graded', 'submitted'].includes(submission.state)) {
      // If this attempt has been submitted/graded, use it
      return submission.body
    } else if (submission.submissionDraft != null) {
      // If a draft object exists, get the submission contents from it
      return submission.submissionDraft.body
    }

    // If the submission is marked as unsubmitted and there's no draft object,
    // the user has started a new attempt but not entered any text, so return
    // an empty string. The body attribute may contain the contents of a
    // previous attempt, which we don't want.
    return ''
  }

  componentDidMount() {
    this._isMounted = true

    if (this.getDraftBody() != null && !this.state.editorLoaded) {
      this.loadRCE()
    }
  }

  componentDidUpdate(prevProps) {
    if (this._tinyeditor == null) {
      return
    }

    if (this.props.readOnly !== prevProps.readOnly) {
      this._tinyeditor.mode.set(this.props.readOnly ? 'readonly' : 'design')
    }

    if (this.props.submission.attempt !== prevProps.submission.attempt) {
      const body = this.getDraftBody()
      this._tinyeditor.setContent(body)
      this._lastSavedContent = body

      if (!this.props.readOnly) {
        this.handleEditorIframeFocus()
        this.handleEditorFocus()
      }
    }
  }

  componentWillUnmount() {
    this._isMounted = false

    if (this.state.editorLoaded) {
      this.unloadRCE()
    }
  }

  // Note: I believe there's a bug in tinymce, that
  // if you set focus:true to give the editor focus on init,
  // then the internal bookkeeping doesn't know it has focus
  // and it does not handle the focusout event correctly.
  // Start w/o focus, then give it focus after initialization
  // in this.handleRCEInit
  loadRCE() {
    this.setState({editorLoaded: true, renderingEditor: true}, () => {
      RichContentEditor.loadNewEditor(this._textareaRef, {
        focus: false,
        manageParent: false,
        tinyOptions: {
          init_instance_callback: this.handleRCEInit,
          height: 300
        },
        onFocus: this.handleEditorFocus,
        onBlur: () => {}
      })
    })
  }

  unloadRCE() {
    const documentContent = document.getElementById('content')
    if (documentContent) {
      const editorIframe = documentContent.querySelector('[id^="random_editor"]')
      if (editorIframe) {
        editorIframe.removeEventListener('focus', this.handleEditorIframeFocus)
      }
    }
    if (this._textareaRef) {
      RichContentEditor.destroyRCE(this._textareaRef)
    }
    this._textareaRef = null
    this.setState({editorLoaded: false, renderingEditor: false})

    clearInterval(this._checkForChangesTimer)
    clearTimeout(this._saveDraftTimer)
  }

  checkForChanges = () => {
    // The idea here:
    // - Every time this function is called (currently several times a second),
    //   check whether the contents of the editor have changed, assuming we're
    //   in a state where we care about changes.
    // - If we see changes, call the onContentsChanged prop, and schedule a
    //   timer to actually save the draft. Further changes to the content will
    //   cancel/re-schedule this timer, so that we only actually save the draft
    //   after the user has stopped making changes for some time.
    const {submission} = this.props

    const isNewAttempt = submission.submissionDraft == null && submission.state === 'unsubmitted'
    // If read-only *or* this is a brand new attempt with no content,
    // we don't want to save a draft, so don't bother comparing
    if (this.props.readOnly || (this._tinyeditor.getContent() === '' && isNewAttempt)) {
      return
    }

    const editorContents = this._tinyeditor.getContent()
    if (this._lastSavedContent !== editorContents) {
      this._lastSavedContent = editorContents

      this.props.onContentsChanged()

      clearTimeout(this._saveDraftTimer)
      this._saveDraftTimer = setTimeout(() => {
        this.saveSubmissionDraft({attempt: submission.attempt, rceText: editorContents})
      }, saveDraftDelayMS)
    }
  }

  handleRCEInit = tinyeditor => {
    this._tinyeditor = tinyeditor
    tinyeditor.mode.set(this.props.readOnly ? 'readonly' : 'design')

    const draftBody = this.getDraftBody()
    tinyeditor.setContent(draftBody)
    this._lastSavedContent = draftBody
    this._checkForChangesTimer = setInterval(this.checkForChanges, checkForChangesIntervalMS)

    const documentContent = document.getElementById('content')
    if (documentContent) {
      const editorIframe = documentContent.querySelector('[id^="random_editor"]')
      if (editorIframe) {
        editorIframe.addEventListener('focus', this.handleEditorIframeFocus)
        this._tinyeditor.focus()
      }
    }
    this.setState({renderingEditor: false})
  }

  handleEditorIframeFocus = _event => {
    this._tinyeditor.focus()
  }

  handleEditorFocus = _event => {
    // these two lines put the caret at the end of the text when focused
    this._tinyeditor.selection.select(this._tinyeditor.getBody(), true)
    this._tinyeditor.selection.collapse(false)
  }

  setTextareaRef = el => {
    this._textareaRef = el
  }

  getRCEText = () => {
    return RichContentEditor.callOnRCE(this._textareaRef, 'get_code')
  }

  saveSubmissionDraft = async ({attempt, rceText}) => {
    await this.props.createSubmissionDraft({
      variables: {
        id: this.props.submission.id,
        activeSubmissionType: 'online_text_entry',
        attempt: attempt || 1,
        body: rceText
      }
    })
  }

  render() {
    return (

        {this.state.renderingEditor && <LoadingIndicator />}

          <textarea
            defaultValue={this.getDraftBody()}
            readOnly={this.props.readOnly}
            ref={this.setTextareaRef}
          />

    )
  }
}
<|endoftext|>


<commit_before>(function () {
    'use strict';

    var fs = require('fs');
    var util = require('util');
    var moment = require('moment');
    var winston = require('winston');
    var WinstonDailyRotateFile = require('winston-daily-rotate-file');
    var morgan = require('morgan');
    var cluster = require('cluster');

    // Названия транспортов
    var LOGGER_APP_CONSOLE = 'app-console';
    var LOGGER_APP_FILE = 'app-daily-file';
    var LOGGER_REQUEST_CONSOLE = 'request-console';
    var LOGGER_REQUEST_FILE = 'request-daily-file';

    // Настройки файлового лога
    var _logPath = '',
        _logFilePrefix = 'app';

    // winston логгер
    var winstonLogger = new (winston.Logger)({
        exitOnError: true,

        transports: [
            new (winston.transports.Console)({
                name: LOGGER_APP_CONSOLE,
                level: 'info',
                formatter: appFormatter,
                handleExceptions: true,
                humanReadableUnhandledException: true
            })
        ]
    });

    // Экспортируемые методы логгера
    module.exports = {
        /**
         * Инициализация файлового лога
         *
         * @param {String} logPath          Путь к папке логов
         * @param {String} [filePrefix=app] Префикс файлов лога
         */
        initFileLog: initFileLog,
        init: initFileLog,

        /**
         * Настройка лога запросов
         * @returns {morgan}
         */
        createRequestLog: createRequestLog,

        /**
         * Создать функтор из метода logAndRethrow
         *
         * @param {String} caption Заголовок сообщения об ошибке
         * @returns {function} Функтор
         */
        createLogAndRethrow: createLogAndRethrow,

        /**
         * Записать в лог сообщение об ошибке и перебросить исключение
         *
         * @param {String} caption Заголовок сообщения об ошибке
         * @param error Ошибка
         */
        logAndRethrow: logAndRethrow,

        /**
         * Записать в лог сообщение об ошибке, только если передана ошибка
         *
         * @param [error] Ошибка
         */
        logIfError: logIfError
    };

    // Проксируем вызовы функций лога на winston логгер
    ['silly', 'debug', 'verbose', 'info', 'warn', 'error'].forEach(function (method) {
        module.exports[method] = function () {
            return winstonLogger[method].apply(winstonLogger, arguments);
        };
    });

    /**
     * Инициализация файлового лога
     *
     * @param {String} logPath          Путь к папке логов
     * @param {String} [filePrefix=app] Префикс файлов лога
     */
    function initFileLog(logPath, filePrefix) {
        _logPath = (logPath) ? logPath + '/' : './';
        _logFilePrefix = filePrefix || 'app';

        preparePath(_logPath);

        // Удаляем текущий транспорт
        if (winstonLogger.transports[LOGGER_APP_FILE]) {
            winstonLogger.remove(LOGGER_APP_FILE);
        }

        // Лог в файл
        winstonLogger.add(WinstonDailyRotateFile, {
            name: LOGGER_APP_FILE,
            level: 'info',
            filename: _logPath + _logFilePrefix,
            datePattern: '_yyyy-MM-dd.log',
            localTime: true,
            json: false,
            formatter: appFormatter,
            handleExceptions: true,
            humanReadableUnhandledException: true
        });
    }

    /**
     * Настройка лога запросов
     * @returns {morgan}
     */
    function createRequestLog() {
        var requestLogger = new winston.Logger({
            level: 'info',

            transports: [
                // Лог в консоль
                new (winston.transports.Console)({
                    name: LOGGER_REQUEST_CONSOLE,
                    formatter: requestFormatter
                }),
                // Лог в файл
                new WinstonDailyRotateFile({
                    name: LOGGER_REQUEST_FILE,
                    filename: _logPath + 'request',
                    datePattern: '_yyyy-MM-dd.log',
                    json: false,
                    formatter: requestFormatter
                })
            ]
        });

        // Настраиваем morgan для использования winston лога
        var format = ':remote-addr ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :response-time';
        return morgan(format, {
            stream: {
                write: function (message, encoding) {
                    requestLogger.info((message || '').trim());
                }
            }
        });
    }

    /**
     * Создать функтор из метода logAndRethrow
     *
     * @param {String} caption Заголовок сообщения об ошибке
     * @returns {function} Функтор
     */
    function createLogAndRethrow(caption) {
        return function (e) {
            logAndRethrow(caption, e);
        };
    }

    /**
     * Записать в лог сообщение об ошибке и перебросить исключение
     *
     * @param {String} caption Заголовок сообщения об ошибке
     * @param error Ошибка
     */
    function logAndRethrow(caption, error) {
        var message = prepareErrorMessage(error);
        winstonLogger.error('%s: %s', caption, message);
        throw error;
    }

    /**
     * Записать в лог сообщение об ошибке, только если передана ошибка
     *
     * @param [error] Ошибка
     */
    function logIfError(error) {
        if (error) {
            winstonLogger.error(prepareErrorMessage(error));
        }
    }

    function prepareErrorMessage(error) {
        var message = '';
        if (error) {
            message = message + error;
            var stack = error.stack;
            if (stack) {
                message = message + '\n' + stack;
            }
        }

        return message;
    }

    function preparePath(path) {
        try {
            fs.mkdirSync(path);
        } catch (e) {
            if (e.code !== 'EEXIST') {
                throw e;
            }
        }
    }

    function appFormatter(options) {
        var output = options.level.toUpperCase() + ' [' + formattedTimestamp() + '] ' + workerMark() + (options.message || '');

        var meta = options.meta;
        if (meta && Object.keys(meta).length > 0) {
            if (Array.isArray(meta.stack)) {
                output += '\n' + meta.stack.join('\n');
            } else {
                output += '\n' + util.inspect(meta);
            }
        }

        return output;
    }

    function requestFormatter(options) {
        return '[' + formattedTimestamp() + '] ' + (options.message || '');
    }

    function formattedTimestamp() {
        return moment().format('YYYY-MM-DD HH:mm:ss');
    }

    function workerMark() {
        if (!cluster.isWorker) {
            return '';
        }

    }
})();<commit_msg>follow up<commit_after>(function () {
    'use strict';

    var fs = require('fs');
    var util = require('util');
    var moment = require('moment');
    var winston = require('winston');
    var WinstonDailyRotateFile = require('winston-daily-rotate-file');
    var morgan = require('morgan');
    var cluster = require('cluster');

    // Названия транспортов
    var LOGGER_APP_CONSOLE = 'app-console';
    var LOGGER_APP_FILE = 'app-daily-file';
    var LOGGER_REQUEST_CONSOLE = 'request-console';
    var LOGGER_REQUEST_FILE = 'request-daily-file';

    // Настройки файлового лога
    var _logPath = '',
        _logFilePrefix = 'app';

    // winston логгер
    var winstonLogger = new (winston.Logger)({
        exitOnError: true,

        transports: [
            new (winston.transports.Console)({
                name: LOGGER_APP_CONSOLE,
                level: 'info',
                formatter: appFormatter,
                handleExceptions: true,
                humanReadableUnhandledException: true
            })
        ]
    });

    // Экспортируемые методы логгера
    module.exports = {
        /**
         * Инициализация файлового лога
         *
         * @param {String} logPath          Путь к папке логов
         * @param {String} [filePrefix=app] Префикс файлов лога
         */
        initFileLog: initFileLog,
        init: initFileLog,

        /**
         * Настройка лога запросов
         * @returns {morgan}
         */
        createRequestLog: createRequestLog,

        /**
         * Создать функтор из метода logAndRethrow
         *
         * @param {String} caption Заголовок сообщения об ошибке
         * @returns {function} Функтор
         */
        createLogAndRethrow: createLogAndRethrow,

        /**
         * Записать в лог сообщение об ошибке и перебросить исключение
         *
         * @param {String} caption Заголовок сообщения об ошибке
         * @param error Ошибка
         */
        logAndRethrow: logAndRethrow,

        /**
         * Записать в лог сообщение об ошибке, только если передана ошибка
         *
         * @param [error] Ошибка
         */
        logIfError: logIfError
    };

    // Проксируем вызовы функций лога на winston логгер
    ['silly', 'debug', 'verbose', 'info', 'warn', 'error'].forEach(function (method) {
        module.exports[method] = function () {
            return winstonLogger[method].apply(winstonLogger, arguments);
        };
    });

    /**
     * Инициализация файлового лога
     *
     * @param {String} logPath          Путь к папке логов
     * @param {String} [filePrefix=app] Префикс файлов лога
     */
    function initFileLog(logPath, filePrefix) {
        _logPath = (logPath) ? logPath + '/' : './';
        _logFilePrefix = filePrefix || 'app';

        preparePath(_logPath);

        // Удаляем текущий транспорт
        if (winstonLogger.transports[LOGGER_APP_FILE]) {
            winstonLogger.remove(LOGGER_APP_FILE);
        }

        // Лог в файл
        winstonLogger.add(WinstonDailyRotateFile, {
            name: LOGGER_APP_FILE,
            level: 'info',
            filename: _logPath + _logFilePrefix,
            datePattern: '_yyyy-MM-dd.log',
            localTime: true,
            json: false,
            formatter: appFormatter,
            handleExceptions: true,
            humanReadableUnhandledException: true
        });
    }

    /**
     * Настройка лога запросов
     * @returns {morgan}
     */
    function createRequestLog() {
        var requestLogger = new winston.Logger({
            level: 'info',

            transports: [
                // Лог в консоль
                new (winston.transports.Console)({
                    name: LOGGER_REQUEST_CONSOLE,
                    formatter: requestFormatter
                }),
                // Лог в файл
                new WinstonDailyRotateFile({
                    name: LOGGER_REQUEST_FILE,
                    filename: _logPath + 'request',
                    datePattern: '_yyyy-MM-dd.log',
                    json: false,
                    formatter: requestFormatter
                })
            ]
        });

        // Настраиваем morgan для использования winston лога
        var format = ':remote-addr ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :response-time';
        return morgan(format, {
            stream: {
                write: function (message, encoding) {
                    requestLogger.info((message || '').trim());
                }
            }
        });
    }

    /**
     * Создать функтор из метода logAndRethrow
     *
     * @param {String} caption Заголовок сообщения об ошибке
     * @returns {function} Функтор
     */
    function createLogAndRethrow(caption) {
        return function (e) {
            logAndRethrow(caption, e);
        };
    }

    /**
     * Записать в лог сообщение об ошибке и перебросить исключение
     *
     * @param {String} caption Заголовок сообщения об ошибке
     * @param error Ошибка
     */
    function logAndRethrow(caption, error) {
        var message = prepareErrorMessage(error);
        winstonLogger.error('%s: %s', caption, message);
        throw error;
    }

    /**
     * Записать в лог сообщение об ошибке, только если передана ошибка
     *
     * @param [error] Ошибка
     */
    function logIfError(error) {
        if (error) {
            winstonLogger.error(prepareErrorMessage(error));
        }
    }

    function prepareErrorMessage(error) {
        var message = '';
        if (error) {
            message = message + error;
            var stack = error.stack;
            if (stack) {
                message = message + '\n' + stack;
            }
        }

        return message;
    }

    function preparePath(path) {
        try {
            fs.mkdirSync(path);
        } catch (e) {
            if (e.code !== 'EEXIST') {
                throw e;
            }
        }
    }

    function appFormatter(options) {
        var output = options.level.toUpperCase() + ' [' + formattedTimestamp() + '] ' + workerMark() + (options.message || '');

        var meta = options.meta;
        if (meta && Object.keys(meta).length > 0) {
            if (Array.isArray(meta.stack)) {
                output += '\n' + meta.stack.join('\n');
            } else {
                output += '\n' + util.inspect(meta);
            }
        }

        return output;
    }

    function requestFormatter(options) {
        return '[' + formattedTimestamp() + '] ' + (options.message || '');
    }

    function formattedTimestamp() {
        return moment().format('YYYY-MM-DD HH:mm:ss');
    }

    function workerMark() {
        if (!cluster.isWorker) {
            return '';
        }

        return '[pid: ' + cluster.worker.process.pid + ', id: ' + cluster.worker.id + '] ';
    }
})();<|endoftext|>


<commit_before>//= require jsoneditor/jsoneditor.js
//= require jsoneditor/asset/jsonlint/jsonlint.js

;(function(window, $) {
  $(function() {
    /*  multipart/form-data*/
    $('form').attr('enctype', 'application/json');

    $('div.jsoneditor-wrap').each(function(i,wrap){
      var container = $(wrap)[0];
      var textarea = $($(wrap).find('textarea'));
      var editor;
      var options = {
        mode: 'tree',
        change: function(ev){
          textarea.text(JSON.stringify(editor.get()));
        }
      };
      editor = new JSONEditor(container, options,JSON.parse(textarea.val()));
    });
  })
})(window, jQuery)
<commit_msg>Change enctype to multipart/form-data<commit_after>//= require jsoneditor/jsoneditor.js
//= require jsoneditor/asset/jsonlint/jsonlint.js

;(function(window, $) {
  $(function() {
    /*  multipart/form-data*/
    $('form').attr('enctype', 'multipart/form-data');

    $('div.jsoneditor-wrap').each(function(i,wrap){
      var container = $(wrap)[0];
      var textarea = $($(wrap).find('textarea'));
      var editor;
      var options = {
        mode: 'tree',
        change: function(ev){
          textarea.text(JSON.stringify(editor.get()));
        }
      };
      editor = new JSONEditor(container, options,JSON.parse(textarea.val()));
    });
  });
})(window, jQuery);
<|endoftext|>