// $Id: ganttprojectcontroller.js 32761 2015-11-02 11:50:21Z schaap $ var ganttProjectControllerMod = angular.module('GanttProjectControllerMod', [ 'gantt', 'gantt.sortable', 'gantt.movable', 'gantt.drawtask', 'gantt.tooltips', 'gantt.bounds', 'gantt.progress', 'gantt.table', 'gantt.tree', 'gantt.groups', 'gantt.dependencies', 'gantt.overlap', 'gantt.resizeSensor', 'gantt.contextmenu']).config(['$compileProvider', function($compileProvider) { $compileProvider.debugInfoEnabled(false); // Remove debug info (angularJS >= 1.3) }]); ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataService', function($scope, dataService) { var self = this; self.lastUpdateTimestamp = new Date(0); self.waitingForDelayedUpdate = false; self.doInitialCollapse = true; $scope.dataService = dataService; $scope.ganttData = []; $scope.enabled = true; self.taskStatusColors = dataService.taskStatusColors; $scope.options = { mode: 'custom', viewScale: '1 hours', currentDate: 'line', currentDateValue: $scope.dataService.lofarTime, columnMagnet: '1 minutes', timeFramesMagnet: false, sideMode: 'Table', autoExpand: 'both', taskOutOfRange: 'truncate', dependencies: false, api: function(api) { // API Object is used to control methods and events from angular-gantt. $scope.api = api; api.core.on.ready($scope, function () { api.tasks.on.moveEnd($scope, moveHandler); api.tasks.on.resizeEnd($scope, moveHandler); } ); api.directives.on.new($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' ) { directiveElement.bind('click', function(event) { if(directiveScope.row.model.project) { $scope.dataService.selected_project_id = directiveScope.row.model.project.id; } }); } else if (directiveName === 'ganttTask') { directiveElement.bind('click', function(event) { if(directiveScope.task.model.raTask) { if(event.ctrlKey) { $scope.dataService.toggleTaskSelection(directiveScope.task.model.raTask.id); } else { $scope.dataService.setSelectedTaskId(directiveScope.task.model.raTask.id); } } }); directiveElement.bind('dblclick', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.setSelectedTaskId(directiveScope.task.model.raTask.id); $scope.jumpToSelectedTasks(); } }); } }); api.directives.on.destroy($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' || directiveName === 'ganttTask') { directiveElement.unbind('click'); } if (directiveName === 'ganttTask') { directiveElement.unbind('dblclick'); } }); } }; function moveHandler(item) { var task_id = item.model.id; if(task_id) { var task = $scope.dataService.taskDict[task_id]; var updatedTask = { id: task.id, starttime: item.model.from._d, endtime: item.model.to._d }; $scope.dataService.putTask(updatedTask); } }; function updateGanttDataAsync() { var now = new Date(); var diff = now.getTime() - self.lastUpdateTimestamp.getTime(); if(diff > 500) { self.waitingForDelayedUpdate = false; $scope.$evalAsync(updateGanttData); } else { if (!self.waitingForDelayedUpdate) { self.waitingForDelayedUpdate = true; setTimeout(updateGanttDataAsync, diff); } } }; function updateGanttData() { if(!$scope.enabled) { return; } if(!dataService.initialLoadComplete) { return; } var projectsDict = $scope.dataService.momProjectsDict; var numProjecs = $scope.dataService.momProjects.length; if(numProjecs == 0) { $scope.ganttData = []; return; } var taskDict = $scope.dataService.taskDict; var tasks = $scope.dataService.filteredTasks; var numTasks = tasks.length; //only enable dependencies (arrows between tasks) in detailed view $scope.options.dependencies = false; var has_any_task_with_dependencies = false; var ganttRows = []; if(numProjecs > 0 && numTasks > 0) { var lowerViewBound = $scope.dataService.viewTimeSpan.from; var upperViewBound = $scope.dataService.viewTimeSpan.to; $scope.options.fromDate = lowerViewBound; $scope.options.toDate = upperViewBound; var fullTimespanInMinutes = (upperViewBound - lowerViewBound) / (60 * 1000); $scope.options.dependencies = (fullTimespanInMinutes <= 6*60 && numTasks <= 100) || numTasks < 20; if(fullTimespanInMinutes > 28*24*60) { $scope.options.viewScale = '7 days'; } else if(fullTimespanInMinutes > 14*24*60) { $scope.options.viewScale = '1 day'; } else if(fullTimespanInMinutes > 7*24*60) { $scope.options.viewScale = '6 hours'; } else if(fullTimespanInMinutes > 2*24*60) { $scope.options.viewScale = '3 hours'; } else if(fullTimespanInMinutes > 12*60) { $scope.options.viewScale = '1 hours'; } else if(fullTimespanInMinutes > 3*60) { $scope.options.viewScale = '30 minutes'; } else { $scope.options.viewScale = '15 minutes'; } //start with aggregating all tasks per type, //and plot these in the upper rows, //so we can see the observartion and pipeline scheduling usage/efficiency for(var type of ['observation', 'pipeline']) { var typeTasks = tasks.filter(function(t) { return t.type == type;}).sort(function(a, b) { return a.starttime.getTime() - b.starttime.getTime(); });; var numTypeTasks = typeTasks.length; if(numTypeTasks > 0) { var typeAggregateRow = { id: type + 's_aggregated', name: ('All ' + type + 's').toUpperCase(), tasks: [] }; ganttRows.push(typeAggregateRow); var task = typeTasks[0]; var rowTask = { id: typeAggregateRow.id + '_task_' + typeAggregateRow.tasks.length, from: task.starttime, color: '#cceecc', movable: false }; if(rowTask.from < lowerViewBound) { rowTask.from = lowerViewBound; } for(var i = 1; i < numTypeTasks; i++) { var prev_task = task; task = typeTasks[i]; if(task.starttime > prev_task.endtime) { rowTask.to = prev_task.endtime; if(rowTask.to > upperViewBound) { rowTask.to = upperViewBound; } typeAggregateRow.tasks.push(rowTask); rowTask = { id: typeAggregateRow.id + '_task_' + typeAggregateRow.tasks.length, from: task.starttime, color: '#cceecc', movable: false }; if(rowTask.from < lowerViewBound) { rowTask.from = lowerViewBound; } } } if(!rowTask.to) { rowTask.to = task.endtime; if(rowTask.to > upperViewBound) { rowTask.to = upperViewBound; } } typeAggregateRow.tasks.push(rowTask); var aggTaskTotalDuration = 0.0 + typeAggregateRow.tasks.map(function(t) { return t.to - t.from;}).reduce(function(a, b) { return a+b; }); var usage = aggTaskTotalDuration / (upperViewBound - lowerViewBound); var usagePerc = parseFloat(Math.round(100.0 * usage * 10.0) / 10.0).toFixed(1); typeAggregateRow.name += ' (' + usagePerc + '%)'; } } var editableTaskStatusIds = $scope.dataService.editableTaskStatusIds; var ganttRowsDict = {}; for(var i = 0; i < numTasks; i++) { var task = tasks[i]; var project = projectsDict[task.project_mom_id]; if(!project) { continue; } var projectTypeRowsId = 'project_' + task.project_mom_id + '_type_' + task.type_id; var ganttProjectTypeRows = ganttRowsDict[projectTypeRowsId]; if(!ganttProjectTypeRows) { ganttProjectTypeRows = []; ganttRowsDict[projectTypeRowsId] = ganttProjectTypeRows; } var availableRow = ganttProjectTypeRows.find(function(row) { var overlappingTasks = row.tasks.filter(function(t) { return (t.from >= task.starttime && t.from <= task.endtime) || (t.to >= task.starttime && t.to <= task.endtime) || (t.from <= task.starttime && t.to >= task.endtime); }); return overlappingTasks.length == 0; }); if(!availableRow) { availableRow = { id: projectTypeRowsId + '_' + (ganttProjectTypeRows.length+1), name: project.name + ' ' + task.type, project: project, tasks: [] }; ganttProjectTypeRows.push(availableRow); ganttRows.push(availableRow); } // Scheduled tasks that are blocked tasks are shown differently and use a tooltip var css_class = "task-status-"; if (task.blocked_by_ids.length > 0) { css_class += "blocked"; } else { css_class += task.status; } var rowTask = { id: task.id.toString(), name: task.name, from: task.starttime, to: task.endtime, raTask: task, // Leave color property undefined; it is now defined by CSS //color: self.taskStatusColors[task.status], classes: css_class, movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 }; if(dataService.isTaskIdSelected(task.id)) { rowTask.classes += ' task-selected-task'; } if($scope.options.dependencies && task.predecessor_ids && task.predecessor_ids.length > 0) { rowTask['dependencies'] = []; for(var predId of task.predecessor_ids) { rowTask['dependencies'].push({'from': predId}); has_any_task_with_dependencies = true; } } availableRow.tasks.push(rowTask); } } //only enable dependencies (arrows between tasks) if there are any tasks with dependencies $scope.options.dependencies &= has_any_task_with_dependencies; $scope.ganttData = ganttRows; self.lastUpdateTimestamp = new Date(); }; $scope.$watch('dataService.initialLoadComplete', updateGanttDataAsync); $scope.$watch('dataService.selected_task_ids', updateGanttDataAsync, true); $scope.$watch('dataService.viewTimeSpan', updateGanttDataAsync, true); $scope.$watch('dataService.filteredTaskChangeCntr', updateGanttDataAsync); $scope.$watch('enabled', function() { setTimeout(updateGanttDataAsync, 500); } ); $scope.$watch('dataService.lofarTime', function() { $scope.$evalAsync(function() { if($scope.dataService.lofarTime.getSeconds() % 10 == 0) { $scope.options.currentDateValue= $scope.dataService.lofarTime;} }); }); } ]);