Projekt

Obecné

Profil

Stáhnout (18.7 KB) Statistiky
| Větev: | Tag: | Revize:
1
/**
2
 * Class representing a change. In a single change, lists of components to be changed and proposals are stored. 
3
 * Changes can be postponed and back activated.
4
 * @constructor
5
 */
6
function Change() {
7
	var rootElement;
8
	var buttonGroup;
9
	var includeNotFoundCheckbox;
10
	var replaceComponentsCheckbox;
11
	var triggerChangeButton;
12
	var oldVertexListComponent;
13
	var newVertexListComponent;
14

    
15
	var oldVertexList = [];
16
	var newVertexList = [];
17

    
18
	var postponed = false;
19
	var triggered = false;
20
	var detailsLoaded = false;
21

    
22
	/* TODO: display change in modal window
23
	app.changeModalWindow.setChange(this);
24
	app.changeModalWindow.open();
25
	*/
26

    
27
	/**
28
	 * Adds a new vertex to the list of components to be changed. The vertex is set as excluded and its DOM element 
29
	 * is removed from document. Its edges are moved so that they end at the group.
30
	 * @param {Vertex} vertex Vertex to be added to the change.
31
	 */
32
	this.addVertex = function(vertex) {
33
		if (!(vertex instanceof Vertex)) {
34
			throw new TypeError(vertex.toString() + 'is not instance of Vertex');
35
		}
36

    
37
		// set remove hook
38
		vertex.removeFromSidebarList = this.removeVertex.bind(this, vertex);
39

    
40
		oldVertexList.push(vertex);
41
		oldVertexListComponent.appendChild(vertex);
42

    
43
		app.redrawEdges();
44

    
45
		// vertex list changed so different results could be retrieved when triggered again
46
		this.setTriggered(false);
47
		this.setDetailsLoaded(false);
48

    
49
		toggleButtonGroup.call(this);
50
	};
51

    
52
	/**
53
	 * Removes a vertex from the list of component to be changed. The vertex is returned back to the viewport and 
54
	 * its edges are moved to it.
55
	 * @param {Vertex} vertex Vertex to be removed from the change.
56
	 */
57
	this.removeVertex = function(vertex) {
58
		if (!(vertex instanceof Vertex)) {
59
			throw new TypeError(vertex.toString() + 'is not instance of Vertex');
60
		}
61

    
62
		// unset remove hook
63
		vertex.removeFromSidebarList = app.utils.noop;
64

    
65
		oldVertexList.splice(oldVertexList.indexOf(vertex), 1);
66
		oldVertexListComponent.removeChild(vertex);
67

    
68
		app.redrawEdges();
69

    
70
		// vertex list changed so different results could be retrieved when triggered again
71
		this.setTriggered(false);
72
		this.setDetailsLoaded(false);
73

    
74
		toggleButtonGroup.call(this);
75
	};
76

    
77
	/**
78
	 * @returns {array<Vertex>} List of components to be changed.
79
	 */
80
	this.getOldVertexList = function() {
81
		return oldVertexList;
82
	};
83

    
84
	/**
85
	 * @returns {boolean} True if the change was already triggered, otherwise false.
86
	 */
87
	this.isTriggered = function() {
88
		return triggered;
89
	};
90

    
91
	/**
92
	 * Sets the change as triggered.
93
	 * @param {boolean} newValue True to set the change as trigger, otherwise false.
94
	 */
95
	this.setTriggered = function(newValue) {
96
		triggered = newValue;
97

    
98
		if (newValue) {
99
			rootElement.classList.add('change--triggered');
100
		} else {
101
			rootElement.classList.remove('change--triggered');
102
		}
103
	};
104

    
105
	/**
106
	 * @returns {boolean} True if the details of proposed component were loaded, otherwise false.
107
	 */
108
	this.isDetailsLoaded = function() {
109
		return detailsLoaded;
110
	};
111

    
112
	/**
113
	 * Sets the change details as loaded.
114
	 * @param {boolean} newValue True to set the change as it has details loaded, otherwise false.
115
	 */
116
	this.setDetailsLoaded = function(newValue) {
117
		detailsLoaded = newValue;
118

    
119
		if (newValue) {
120
			rootElement.classList.add('change--details-loaded');
121
		} else {
122
			rootElement.classList.remove('change--details-loaded');
123
		}
124
	};
125

    
126
	/**
127
	 * @returns {boolean} True if the change is currently postponed, otherwise false.
128
	 */
129
	this.isPostponed = function() {
130
		return postponed;
131
	};
132

    
133
	/**
134
	 * Sets the change postponed.
135
	 * @param {boolean} newValue True to set the change as postponed, otherwise false.
136
	 */
137
	this.setPostponed = function(newValue) {
138
		postponed = newValue;
139

    
140
		if (newValue) {
141
			rootElement.classList.add('change--postponed');
142
		} else {
143
			rootElement.classList.remove('change--postponed');
144
		}
145
	};
146

    
147
	/**
148
	 * Postpones the change. Removes its current DOM element from document.
149
	 */
150
	this.postpone = function() {
151
		this.setPostponed(true);
152
		this.remove();
153
	};
154

    
155
	/**
156
	 * Activates the change. Removes its current DOM element from document.
157
	 */
158
	this.activate = function() {
159
		this.setPostponed(false);
160
		this.remove();
161
	};
162

    
163
	/**
164
	 * Creates a new DOM element representing the change in memory. The element being created depends on whether the change
165
	 * is postponed at the moment. Binds user interactions to local handler functions.
166
	 * @returns {Element} HTML or SVG DOM element depending on whether the change is postponed.
167
	 */
168
	this.render = function() {
169
		rootElement = postponed ? renderPostponed.call(this) : renderActive.call(this);
170

    
171
		this.setPostponed(postponed);
172
		this.setDetailsLoaded(detailsLoaded);
173
		this.setTriggered(triggered);
174
		toggleButtonGroup.call(this);
175

    
176
		return rootElement;
177
	};
178

    
179
	/**
180
	 * Removes the DOM element representing the change from document.
181
	 */
182
	this.remove = function() {
183
		rootElement.remove();
184
	};
185

    
186
	function renderActive() {
187
		rootElement = app.utils.createHtmlElement('div', {
188
			'class': 'change',
189
		});
190

    
191
		// buttons
192
		buttonGroup = app.utils.createHtmlElement('div', {
193
			'class': 'button-group hidden',
194
		});
195
		rootElement.appendChild(buttonGroup);
196

    
197
		// include not found classes checkbox
198
		includeNotFoundCheckbox = app.utils.createHtmlElement('input', {
199
			'type': 'checkbox',
200
			'name': 'includeNotFoundClasses',
201
			'class': 'include-not-found-checkbox',
202
			'title': 'Include not found classes in the change',
203
			'data-tooltip': 'left',
204
		});
205
		buttonGroup.appendChild(includeNotFoundCheckbox);
206

    
207
		// trigger change button
208
		triggerChangeButton = app.utils.createHtmlElement('button', {
209
			'class': 'trigger-change-button button',
210
			'title': 'Change components',
211
		});
212
		triggerChangeButton.appendChild(app.utils.createHtmlElement('img', {
213
			'src': 'images/tochange/crce-call-trans.gif',
214
			'alt': 'Icon of "change components" action',
215
		}));
216
		triggerChangeButton.addEventListener('click', triggerChange.bind(this));
217
		buttonGroup.appendChild(triggerChangeButton);
218

    
219
		// postpone button
220
		var postponeButton = app.utils.createHtmlElement('button', {
221
			'class': 'postpone-button button',
222
			'title': 'Postpone change',
223
		});
224
		postponeButton.appendChild(app.utils.createHtmlElement('img', {
225
			'src': 'images/tochange/postpone-trans.gif',
226
			'alt': 'Icon of "postpone current change" action',
227
		}));
228
		postponeButton.addEventListener('click', moveToPostponed.bind(this));
229
		buttonGroup.appendChild(postponeButton);
230

    
231

    
232
		// old vertex list
233
		oldVertexListComponent = new ChangeVertexList(this);
234
		rootElement.appendChild(oldVertexListComponent.render());
235

    
236
		oldVertexList.forEach(function(vertex) {
237
			oldVertexListComponent.appendChild(vertex);
238
		}, this);
239

    
240

    
241
		// controls
242
		var changeControls = app.utils.createHtmlElement('div', {
243
			'class': 'change-controls',
244
		});
245
		rootElement.appendChild(changeControls);
246

    
247
		// transition arrow
248
		var arrow = app.utils.createHtmlElement('span', {
249
			'class': 'transition-arrow',
250
		});
251
		arrow.appendChild(app.utils.createTextElement('🡻'));
252
		changeControls.appendChild(arrow);
253

    
254
		// control buttons
255
		var controlButtonGroup = app.utils.createHtmlElement('div', {
256
			'class': 'button-group',
257
		});
258
		changeControls.appendChild(controlButtonGroup);
259

    
260
		// load change details button
261
		var loadDetailsButton = app.utils.createHtmlElement('button', {
262
			'class': 'load-details-button button',
263
			'title': 'Load change details',
264
		});
265
		loadDetailsButton.appendChild(app.utils.createTextElement(''));
266
		loadDetailsButton.addEventListener('click', loadChangeDetails.bind(this));
267
		controlButtonGroup.appendChild(loadDetailsButton);
268

    
269
		// replace old components by new ones checkbox
270
		replaceComponentsCheckbox = app.utils.createHtmlElement('input', {
271
			'type': 'checkbox',
272
			'name': 'replaceComponents',
273
			'class': 'replace-components-checkbox',
274
			'title': 'Replace old components by new ones',
275
			'checked': 'checked',
276
			'data-tooltip': 'left',
277
		});
278
		controlButtonGroup.appendChild(replaceComponentsCheckbox);
279

    
280
		// accept change button
281
		var acceptButton = app.utils.createHtmlElement('button', {
282
			'class': 'accept-button button',
283
			'title': 'Accept proposed change',
284
		});
285
		acceptButton.appendChild(app.utils.createHtmlElement('img', {
286
			'src': 'images/tochange/accept-trans.gif',
287
			'alt': 'Icon of "accept proposed change" action',
288
		}));
289
		acceptButton.addEventListener('click', acceptChange.bind(this));
290
		controlButtonGroup.appendChild(acceptButton);
291

    
292
		// revoke change button
293
		var revokeButton = app.utils.createHtmlElement('button', {
294
			'class': 'revoke-button button',
295
			'title': 'Revoke proposed change',
296
		});
297
		revokeButton.appendChild(app.utils.createHtmlElement('img', {
298
			'src': 'images/button_cancel.png',
299
			'alt': 'Icon of "revoke proposed change" action',
300
		}));
301
		revokeButton.addEventListener('click', revokeChange.bind(this));
302
		controlButtonGroup.appendChild(revokeButton);
303

    
304

    
305
		// new vertex list
306
		newVertexListComponent = new ChangeVertexList(this);
307
		rootElement.appendChild(newVertexListComponent.render());
308

    
309
		newVertexList.forEach(function(vertex) {
310
			newVertexListComponent.appendChild(vertex);
311
		}, this);
312

    
313
		return rootElement;
314
	}
315

    
316
	function renderPostponed() {
317
		rootElement = app.utils.createHtmlElement('li', {
318
			'class': 'change',
319
		});
320

    
321

    
322
		// buttons
323
		var buttonGroup = app.utils.createHtmlElement('div', {
324
			'class': 'button-group',
325
		});
326
		rootElement.appendChild(buttonGroup);
327

    
328
		// activate change button
329
		var activateButton = app.utils.createHtmlElement('button', {
330
			'class': 'activate-button button',
331
			'title': 'Set change active',
332
		});
333
		activateButton.appendChild(app.utils.createHtmlElement('img', {
334
			'src': 'images/tochange/tochange-trans.gif',
335
			'alt': 'Icon of "set change active" action',
336
		}));
337
		activateButton.addEventListener('click', moveToActive.bind(this));
338
		buttonGroup.appendChild(activateButton);
339

    
340

    
341
		// original vertex list
342
		oldVertexListComponent = new ChangeVertexList(this);
343
		rootElement.appendChild(oldVertexListComponent.render());
344

    
345
		oldVertexList.forEach(function(vertex) {
346
			oldVertexListComponent.appendChild(vertex);
347
		}, this);
348

    
349

    
350
		// new vertex list
351
		newVertexListComponent = new ChangeVertexList(this);
352
		rootElement.appendChild(newVertexListComponent.render());
353

    
354
		newVertexList.forEach(function(vertex) {
355
			newVertexListComponent.appendChild(vertex);
356
		}, this);
357

    
358

    
359
		return rootElement;
360
	}
361

    
362
	function triggerChange() {
363
		var self = this;
364
		var includeNotFound = includeNotFoundCheckbox.checked;
365

    
366
		app.loader.enable();
367

    
368
		app.componentChanger.run(oldVertexList, includeNotFound).then(function(data) {
369
			self.setTriggered(true);
370

    
371
			data.forEach(function(component) {
372
				var vertex = new VertexLight(component);
373

    
374
				newVertexList.push(vertex);
375
				newVertexListComponent.appendChild(vertex);
376
			});
377

    
378
		}, function(reason) {
379
			if (typeof reason === 'string') {
380
				alert(reason);
381
			} else {
382
				alert('Error occurred while querying CRCE. See JS console for more details.');
383
				console.error(reason);
384
			}
385

    
386
		}).always(function() {
387
			app.loader.disable();
388
		});
389
	}
390

    
391
	function loadChangeDetails() {
392
		var self = this;
393
		var graphVersion = parseInt(app.cookies.get('graphVersion'));
394
		var uuidToNameMap = {};
395

    
396
		app.loader.enable();
397

    
398
		downloadComponents(graphVersion + 1, newVertexList).then(function(responses) {
399
			// get map with component's UUID as key and its filename as value
400
			uuidToNameMap = responses.map(function(response) {
401
				return response[0];
402
			}).reduce(function(map, component) {
403
				map[component.uuid] = component.name;
404
				return map;
405
			}, {});
406

    
407
			return copyComponents(app.vertexList);
408

    
409
		}).then(function() {
410
			return loadGraphData(graphVersion + 1);
411

    
412
		}).then(function(data) {
413
			// vertices
414
			data.vertices.forEach(function(component) {
415
				var vertex = app.findVertexByName(component.name);
416

    
417
				if (app.utils.isDefined(vertex)) {
418
					// vertex already exists in graph
419
					return;
420

    
421
				} else {
422
					// create a new vertex
423
					var vertex = new Vertex(component);
424
					vertex.setPosition(new Coordinates(0, 0));
425

    
426
					app.nodeList.push(vertex);
427
					app.vertexList.push(vertex);
428

    
429
					app.viewportComponent.addVertex(vertex);
430
				}
431

    
432
				// replace light vertex placeholder by vertex component
433
				var placeholdingVertex = newVertexList.find(function(newVertex) {
434
					return uuidToNameMap[newVertex.id] == this.name;
435
				}, vertex);
436

    
437
				if (app.utils.isDefined(placeholdingVertex)) {
438
					newVertexList.splice(newVertexList.indexOf(placeholdingVertex), 1);
439
					newVertexListComponent.removeChild(placeholdingVertex);
440
				}
441

    
442
				newVertexList.push(vertex);
443
			});
444

    
445
			var newVertexNameList = newVertexList.map(function(vertex) {
446
				return vertex.name;
447
			});
448

    
449
			// edges
450
			data.edges.forEach(function(component) {
451
				// vertex names are prefixed by "vertex_" string, cut it off
452
				var fromNodeName = component.from.substring(7);
453
				var toNodeName = component.to.substring(7);
454

    
455
				// neither end of the edge is a newly added vertex
456
				if (newVertexNameList.indexOf(fromNodeName) < 0 && newVertexNameList.indexOf(toNodeName) < 0) return;
457

    
458
				var edge = new Edge(component);
459

    
460
				var fromNode = app.findVertexByName(fromNodeName);
461
				if (fromNode) {
462
					fromNode.addOutEdge(edge);
463
				}
464

    
465
				var toNode = app.findVertexByName(toNodeName);
466
				if (toNode) {
467
					toNode.addInEdge(edge);
468
				}
469

    
470
				app.edgeList.push(edge);
471

    
472
				app.viewportComponent.addEdge(edge);
473
			});
474

    
475

    
476
			newVertexList.forEach(function(vertex) {
477
				vertex.exclude();
478

    
479
				newVertexListComponent.appendChild(vertex);
480
			});
481

    
482
			app.redrawEdges();
483

    
484
			self.setDetailsLoaded(true);
485

    
486
		}).always(function() {
487
			app.loader.disable();
488
		});
489
	}
490

    
491
	function downloadComponents(graphVersion, vertexList) {
492
		var downloadPromises = [];
493
		vertexList.forEach(function(vertex) {
494
			var downloadPromise = $.ajax({
495
				type: 'POST',
496
				url: 'api/download-component?graphVersion=' + graphVersion + '&uuid=' + encodeURIComponent(vertex.id),
497
				contentType: 'application/json',
498
				timeout: 180 * 1000,	// in milliseconds
499
			});
500

    
501
			downloadPromises.push(downloadPromise);
502
		});
503

    
504
		return app.utils.promiseAll(downloadPromises);
505
	}
506

    
507
	function deleteComponents(graphVersion, vertexList) {
508
		var deletePromises = [];
509
		vertexList.forEach(function(vertex) {
510
			var deletePromise = $.ajax({
511
				type: 'GET',
512
				url: 'delete-component?graphVersion=' + graphVersion + '&name=' + encodeURIComponent(vertex.name),
513
				timeout: 180 * 1000,	// in milliseconds
514
			});
515

    
516
			deletePromises.push(deletePromise);
517
		});
518

    
519
		return app.utils.promiseAll(deletePromises);
520
	}
521

    
522
	function copyComponents(vertexList) {
523
		var involvedComponents = vertexList.filter(function(vertex) {
524
			return vertex.name !== app.constants.notFoundVertexName;
525
		}).map(function(vertex) {
526
			return {
527
				'id': vertex.id,
528
				'name': vertex.name,
529
			};
530
		});
531

    
532
		return $.ajax({
533
			type: 'POST',
534
			url: 'api/copy-components',
535
			data: JSON.stringify({
536
				'components': involvedComponents,
537
			}),
538
			contentType: 'application/json',
539
			timeout: 180 * 1000,	// in milliseconds
540
		});
541
	}
542

    
543
	function loadGraphData(graphVersion) {
544
		return $.getJSON(app.API.loadGraph + '?graphVersion=' + graphVersion);
545
	}
546

    
547
	function acceptChange() {
548
		var self = this;
549
		var graphVersion = parseInt(app.cookies.get('graphVersion'));
550
		var replaceComponents = replaceComponentsCheckbox.checked;
551

    
552
		app.loader.enable();
553

    
554
		var promise;
555
		if (this.isDetailsLoaded()) {
556
			var deleteComponentsPromise;
557
			if (replaceComponents) {
558
				deleteComponentsPromise = deleteComponents(graphVersion + 1, oldVertexList);
559
			} else {
560
				deleteComponentsPromise = $.when();
561
			}
562

    
563
			promise = deleteComponentsPromise.then(function() {
564
				return loadGraphData(graphVersion + 1);
565
			});
566

    
567
		} else {
568
			// download and copy components on the server side and then load the graph
569
			promise = downloadComponents(graphVersion + 1, newVertexList).then(function(responses) {
570
				var involvedVertexList = app.vertexList;
571

    
572
				if (replaceComponents) {
573
					involvedVertexList = involvedVertexList.filter(function(vertex) {
574
						return oldVertexList.some(function(oldVertex) {
575
							return oldVertex.name !== vertex.name;
576
						});
577
					});
578
				}
579

    
580
				return copyComponents(involvedVertexList);
581

    
582
			}).then(function() {
583
				return loadGraphData(graphVersion + 1);
584
			});
585
		}
586

    
587
		promise.then(function(data) {
588
			// increment graph version number
589
			app.cookies.set('graphVersion', graphVersion + 1);
590

    
591
			var graphExportData = app.graphExporter.run();
592

    
593
			app.reset();
594
			app.graphLoader.run(data, graphExportData);
595

    
596
		}).always(function() {
597
			app.loader.disable();
598
		});
599
	}
600

    
601
	function revokeChange() {
602
		var self = this;
603
		var graphVersion = parseInt(app.cookies.get('graphVersion'));
604

    
605
		app.loader.enable();
606

    
607
		var deleteComponentsPromise;
608
		if (this.isDetailsLoaded()) {
609
			// delete components on the server side
610
			deleteComponentsPromise = deleteComponents(graphVersion + 1, newVertexList);
611
		} else {
612
			deleteComponentsPromise = $.when();
613
		}
614

    
615
		deleteComponentsPromise.then(function() {
616
			newVertexList.forEach(function(vertex) {
617
				// change details were loaded
618
				if (self.isDetailsLoaded()) {
619
					// remove edges connected with the vertex
620
					vertex.getInEdgeList().forEach(function(edge) {
621
						edge.remove();
622
					});
623
					vertex.getOutEdgeList().forEach(function(edge) {
624
						edge.remove();
625
					});
626

    
627
					var existingVertex = app.findVertexByName(vertex.name);
628
					if (app.utils.isDefined(existingVertex)) {
629
						// remove vertex from app
630
						app.nodeList.splice(app.nodeList.indexOf(existingVertex), 1);
631
						app.vertexList.splice(app.vertexList.indexOf(existingVertex), 1);
632
					}
633
				}
634

    
635
				// remove vertex from this change
636
				newVertexListComponent.removeChild(vertex);
637
			});
638

    
639
			newVertexList = [];
640

    
641
			self.setTriggered(false);
642
			self.setDetailsLoaded(false);
643

    
644
		}).always(function() {
645
			app.loader.disable();
646
		});
647
	}
648

    
649
	function moveToPostponed() {
650
		if (oldVertexList.length === 0) return;
651
		
652
		newVertexList.forEach(function(vertex) {
653
			// remove vertex from app
654
			var existingVertex = app.findVertexByName(vertex.name);
655
			if (app.utils.isDefined(existingVertex)) {
656
				app.nodeList.splice(app.nodeList.indexOf(existingVertex), 1);
657
				app.vertexList.splice(app.vertexList.indexOf(existingVertex), 1);
658
			}
659
		});
660

    
661
		this.setPostponed(true);
662
		this.remove();
663

    
664
		app.sidebarComponent.setChangePostponed(this);
665
	}
666

    
667
	function moveToActive() {
668
		newVertexList.forEach(function(vertex) {
669
			// add vertex to app
670
			app.nodeList.push(vertex);
671
			app.vertexList.push(vertex);
672
		});
673

    
674
		this.setPostponed(false);
675
		this.remove();
676

    
677
		app.sidebarComponent.setChangeActive(this);
678
	}
679

    
680
	function toggleButtonGroup() {
681
		if (oldVertexList.length > 0) {
682
			buttonGroup.classList.remove('hidden');
683
		} else {
684
			buttonGroup.classList.add('hidden');
685
		}
686
	}
687

    
688
	function getInvolvedComponents() {
689
		var involvedComponents = [];
690

    
691
		oldVertexList.forEach(function(vertex) {
692
			vertex.getInEdgeList().forEach(function(edge) {
693
				involvedComponents.push(edge.getFrom());
694
			});
695

    
696
			vertex.getOutEdgeList().forEach(function(edge) {
697
				involvedComponents.push(edge.getTo());
698
			});
699
		});
700

    
701
		return involvedComponents;
702
	}
703
}
(1-1/20)