Revize f5d0189e
Přidáno uživatelem Pavel Fidranský před asi 6 roky(ů)
sources/src/main/webapp/js/app.js | ||
---|---|---|
11 | 11 |
this.graphExporter = new GraphExporter; |
12 | 12 |
/** @prop {GraphHistory} graphHistory */ |
13 | 13 |
this.graphHistory = new GraphHistory; |
14 |
/** @prop {JavaComponentChanger} componentChanger */ |
|
15 |
this.componentChanger = new JavaComponentChanger; |
|
16 | 14 |
/** @prop {Loader} loader */ |
17 | 15 |
this.loader = new Loader; |
18 | 16 |
/** @prop {Zoom} zoom */ |
sources/src/main/webapp/js/components/change.js | ||
---|---|---|
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 |
} |
sources/src/main/webapp/js/components/changeModalWindow.js | ||
---|---|---|
1 |
/** |
|
2 |
* Class representing a modal window displaying a change separately from viewport and sidebar. |
|
3 |
* @see Change |
|
4 |
* @constructor |
|
5 |
* @param {Change} change Change to be displayed in the modal window. |
|
6 |
*/ |
|
7 |
function ChangeModalWindow(change) { |
|
8 |
var rootElement; |
|
9 |
|
|
10 |
var change; |
|
11 |
|
|
12 |
/** |
|
13 |
* Sets a change to be displayed in the modal window. |
|
14 |
* @param {Change} newValue Change to be displayed in this modal window. |
|
15 |
*/ |
|
16 |
this.setChange = function(newValue) { |
|
17 |
change = newValue; |
|
18 |
}; |
|
19 |
|
|
20 |
this.open = function() { |
|
21 |
rootElement.classList.remove('hidden'); |
|
22 |
}; |
|
23 |
|
|
24 |
/** |
|
25 |
* Closes this modal window. |
|
26 |
*/ |
|
27 |
this.close = function() { |
|
28 |
rootElement.classList.add('hidden'); |
|
29 |
}; |
|
30 |
|
|
31 |
/** |
|
32 |
* Creates a new HTML DOM element representing the modal window in memory. Binds user interactions to local handler functions. |
|
33 |
* @returns {Element} HTML DOM element. |
|
34 |
*/ |
|
35 |
this.render = function() { |
|
36 |
rootElement = app.utils.createHtmlElement('div', { |
|
37 |
'class': 'change-modal modal hidden', |
|
38 |
}); |
|
39 |
|
|
40 |
var modalContent = app.utils.createHtmlElement('div', { |
|
41 |
'class': 'modal-content', |
|
42 |
}); |
|
43 |
rootElement.appendChild(modalContent); |
|
44 |
|
|
45 |
var closeButton = app.utils.createHtmlElement('button', { |
|
46 |
'class': 'close-button button', |
|
47 |
}); |
|
48 |
closeButton.appendChild(app.utils.createTextElement('×')); |
|
49 |
closeButton.addEventListener('click', closeButtonClick.bind(this)); |
|
50 |
modalContent.appendChild(closeButton); |
|
51 |
|
|
52 |
return rootElement; |
|
53 |
}; |
|
54 |
|
|
55 |
/** |
|
56 |
* Close button click interaction. Closes the modal window. |
|
57 |
* @param {Event} e Click event. |
|
58 |
*/ |
|
59 |
function closeButtonClick(e) { |
|
60 |
this.close(); |
|
61 |
} |
|
62 |
} |
sources/src/main/webapp/js/components/changeVertexList.js | ||
---|---|---|
1 |
/** |
|
2 |
* Class representing a list of vertices in a change. It can be either the list of components to be changed or the list of proposals. |
|
3 |
* @see Change |
|
4 |
* @constructor |
|
5 |
* @param {Change} parentalChange Change this vertex list is bound to. |
|
6 |
*/ |
|
7 |
function ChangeVertexList(parentalChange) { |
|
8 |
var rootElement; |
|
9 |
|
|
10 |
/** |
|
11 |
* Adds a new vertex to the list. |
|
12 |
* @param {vertex} vertex Vertex to be added to this list. |
|
13 |
*/ |
|
14 |
this.appendChild = function(vertex) { |
|
15 |
if (parentalChange.isPostponed()) { |
|
16 |
var listItemElement = app.utils.createHtmlElement('li', { |
|
17 |
'class': 'node vertex', |
|
18 |
'data-id': vertex.id, |
|
19 |
}); |
|
20 |
listItemElement.appendChild(document.createTextNode(vertex.name)); |
|
21 |
|
|
22 |
} else { |
|
23 |
listItemElement = vertex.render(); |
|
24 |
} |
|
25 |
|
|
26 |
rootElement.appendChild(listItemElement); |
|
27 |
}; |
|
28 |
|
|
29 |
/** |
|
30 |
* Removes a vertex from the list. |
|
31 |
* @param {vertex} vertex Vertex to be removed from this list. |
|
32 |
*/ |
|
33 |
this.removeChild = function(vertex) { |
|
34 |
var listItemElement = rootElement.querySelector('[data-id="' + vertex.id + '"]'); |
|
35 |
|
|
36 |
listItemElement.remove(); |
|
37 |
}; |
|
38 |
|
|
39 |
/** |
|
40 |
* Creates a new DOM element representing the list in memory. |
|
41 |
* @returns {Element} HTML DOM element. |
|
42 |
*/ |
|
43 |
this.render = function() { |
|
44 |
rootElement = app.utils.createHtmlElement('ul', {}); |
|
45 |
rootElement.setAttribute('class', 'node-list'); |
|
46 |
|
|
47 |
return rootElement; |
|
48 |
}; |
|
49 |
} |
sources/src/main/webapp/js/components/edge.js | ||
---|---|---|
9 | 9 |
|
10 | 10 |
var rootElement; |
11 | 11 |
|
12 |
var compatible = props.isCompatible; |
|
13 |
var compatibilityInfo = props.compInfoJSON ? JSON.parse(props.compInfoJSON) : []; |
|
14 |
|
|
15 | 12 |
var hidden = false; |
16 | 13 |
var dimmed = false; |
17 | 14 |
var highlighted = false; |
... | ... | |
108 | 105 |
lollipop.setAttribute('transform', `rotate(${rotation}, ${position.x},${position.y}) translate(${position.x},${position.y})`); |
109 | 106 |
}; |
110 | 107 |
|
111 |
/** |
|
112 |
* @returns {boolean} True if the edge is compatible, otherwise false. |
|
113 |
*/ |
|
114 |
this.isCompatible = function() { |
|
115 |
return compatible; |
|
116 |
}; |
|
117 |
|
|
118 |
/** |
|
119 |
* @returns {object} Information on compatibility of the two vertices this edge is connecting. |
|
120 |
*/ |
|
121 |
this.getCompatibilityInfo = function() { |
|
122 |
return compatibilityInfo; |
|
123 |
}; |
|
124 |
|
|
125 | 108 |
/** |
126 | 109 |
* Toggles visibility of the edge. |
127 | 110 |
* @param {boolean} newValue True to hide the edge, false to display it. |
... | ... | |
294 | 277 |
* @param {Event} e Click event. |
295 | 278 |
*/ |
296 | 279 |
function click(e) { |
297 |
if (compatibilityInfo.length > 0) { |
|
298 |
app.viewportComponent.edgePopoverComponent.setContent(compatibilityInfo); |
|
299 |
app.viewportComponent.edgePopoverComponent.setPosition(new Coordinates(e.clientX, e.clientY)); |
|
300 |
app.viewportComponent.edgePopoverComponent.open(); |
|
301 |
} |
|
302 | 280 |
|
303 | 281 |
// unhighlight other edges |
304 | 282 |
app.edgeList.filter(function(edge) { |
sources/src/main/webapp/js/components/sidebar.js | ||
---|---|---|
5 | 5 |
var rootElement; |
6 | 6 |
var activeChangeElement; |
7 | 7 |
|
8 |
/** @prop {SidebarPostponedChangeList} postponedChangeListComponent */ |
|
9 |
this.postponedChangeListComponent = null; |
|
10 | 8 |
/** @prop {SidebarUnconnectedNodeList} unconnectedNodeListComponent */ |
11 | 9 |
this.unconnectedNodeListComponent = null; |
12 |
/** @prop {SidebarMissingClassList} missingClassListComponent */ |
|
13 |
this.missingClassListComponent = null; |
|
14 | 10 |
/** @prop {SidebarExcludedNodeList} excludedNodeListComponent */ |
15 | 11 |
this.excludedNodeListComponent = null; |
16 | 12 |
/** @prop {StatusBar} statusBarComponent */ |
17 | 13 |
this.statusBarComponent = null; |
18 | 14 |
|
19 |
var activeChange = new Change; |
|
20 | 15 |
var floaterList = []; |
21 | 16 |
|
22 | 17 |
this.getFloaters = function() { |
... | ... | |
37 | 32 |
floaterList.splice(floaterList.indexOf(floater), 1); |
38 | 33 |
}; |
39 | 34 |
|
40 |
this.addToChange = function(node) { |
|
41 |
node.removeFromSidebarList(); |
|
42 |
node.remove(true); |
|
43 |
|
|
44 |
activeChange.addVertex(node); |
|
45 |
}; |
|
46 |
|
|
47 |
this.setChangeActive = function(change) { |
|
48 |
// postpone currently active change |
|
49 |
if (activeChange.getOldVertexList().length > 0) { |
|
50 |
activeChange.postpone(); |
|
51 |
this.postponedChangeListComponent.add(activeChange); |
|
52 |
} else { |
|
53 |
activeChange.remove(); |
|
54 |
} |
|
55 |
|
|
56 |
change.activate(); |
|
57 |
activeChange = change; |
|
58 |
activeChangeElement.appendChild(activeChange.render()); |
|
59 |
}; |
|
60 |
|
|
61 |
this.setChangePostponed = function(change) { |
|
62 |
// postpone currently active change if there are some vertices in it |
|
63 |
if (change.getOldVertexList().length > 0) { |
|
64 |
this.postponedChangeListComponent.add(change); |
|
65 |
} |
|
66 |
|
|
67 |
// set a new active change |
|
68 |
activeChange = new Change; |
|
69 |
activeChangeElement.appendChild(activeChange.render()); |
|
70 |
}; |
|
71 |
|
|
72 | 35 |
this.render = function() { |
73 | 36 |
rootElement = app.utils.createHtmlElement('div', { |
74 | 37 |
'class': 'sidebar', |
... | ... | |
82 | 45 |
}); |
83 | 46 |
rootElement.appendChild(sidebarNav); |
84 | 47 |
|
85 |
// change |
|
86 |
var changeButton = app.utils.createHtmlElement('button', { |
|
87 |
'class': 'button', |
|
88 |
'id': 'changeButton', |
|
89 |
'title': 'Active change', |
|
90 |
'data-tooltip': 'top', |
|
91 |
}); |
|
92 |
changeButton.appendChild(app.dom.createHtmlElement('img', { |
|
93 |
'src': 'images/tochange/crce-call-trans.gif', |
|
94 |
'alt': 'Icon of "toggle active change" action', |
|
95 |
})); |
|
96 |
changeButton.appendChild(app.dom.createTextElement('List')); |
|
97 |
changeButton.addEventListener('click', function() { |
|
98 |
document.getElementById('activeChange').classList.toggle('hidden'); |
|
99 |
app.redrawEdges(); |
|
100 |
}); |
|
101 |
sidebarNav.appendChild(changeButton); |
|
102 |
|
|
103 |
// postponed |
|
104 |
var postponedButton = app.utils.createHtmlElement('button', { |
|
105 |
'class': 'button', |
|
106 |
'id': 'postponedButton', |
|
107 |
'title': 'Postponed changes', |
|
108 |
'data-tooltip': 'top', |
|
109 |
}); |
|
110 |
postponedButton.appendChild(app.dom.createHtmlElement('img', { |
|
111 |
'src': 'images/tochange/postpone-trans.gif', |
|
112 |
'alt': 'Icon of "toggle postponed changes list" action', |
|
113 |
})); |
|
114 |
postponedButton.appendChild(app.dom.createTextElement('List')); |
|
115 |
postponedButton.addEventListener('click', function() { |
|
116 |
document.getElementById('postponedChangeListComponent').classList.toggle('hidden'); |
|
117 |
app.redrawEdges(); |
|
118 |
}); |
|
119 |
sidebarNav.appendChild(postponedButton); |
|
120 |
|
|
121 | 48 |
// unconnected |
122 | 49 |
var unconnectedButton = app.utils.createHtmlElement('button', { |
123 | 50 |
'class': 'button', |
... | ... | |
136 | 63 |
}); |
137 | 64 |
sidebarNav.appendChild(unconnectedButton); |
138 | 65 |
|
139 |
// missing |
|
140 |
var missingButton = app.utils.createHtmlElement('button', { |
|
141 |
'class': 'button', |
|
142 |
'id': 'missingButton', |
|
143 |
'title': 'Missing classes', |
|
144 |
'data-tooltip': 'top-left', |
|
145 |
}); |
|
146 |
missingButton.appendChild(app.dom.createHtmlElement('img', { |
|
147 |
'src': 'images/tochange/accept-trans.gif', |
|
148 |
'alt': 'Icon of "toggle missing classes list" action', |
|
149 |
})); |
|
150 |
missingButton.appendChild(app.dom.createTextElement('List')); |
|
151 |
missingButton.addEventListener('click', function() { |
|
152 |
document.getElementById('missingClassListComponent').classList.toggle('hidden'); |
|
153 |
app.redrawEdges(); |
|
154 |
}); |
|
155 |
sidebarNav.appendChild(missingButton); |
|
156 |
|
|
157 | 66 |
|
158 | 67 |
var sidebarContainer = app.utils.createHtmlElement('div', { |
159 | 68 |
'class': 'sidebar-container', |
160 | 69 |
}); |
161 | 70 |
rootElement.appendChild(sidebarContainer); |
162 | 71 |
|
163 |
// active change |
|
164 |
activeChangeElement = app.utils.createHtmlElement('div', { |
|
165 |
'id': 'activeChange', |
|
166 |
'class': 'node-container change-nodes', |
|
167 |
}); |
|
168 |
activeChangeElement.appendChild(app.dom.htmlStringToElement('<h2 class="node-container-title">Active change</h2>')); |
|
169 |
activeChangeElement.appendChild(activeChange.render()); |
|
170 |
|
|
171 |
sidebarContainer.appendChild(activeChangeElement); |
|
172 |
|
|
173 |
// postponed changes |
|
174 |
this.postponedChangeListComponent = new SidebarPostponedChangeList({ |
|
175 |
'id': 'postponedChangeListComponent', |
|
176 |
'class': 'hidden', |
|
177 |
}); |
|
178 |
sidebarContainer.appendChild(this.postponedChangeListComponent.render()); |
|
179 |
|
|
180 | 72 |
// unconnected components |
181 | 73 |
this.unconnectedNodeListComponent = new SidebarUnconnectedNodeList({ |
182 | 74 |
'id': 'unconnectedNodeListComponent', |
... | ... | |
184 | 76 |
}); |
185 | 77 |
sidebarContainer.appendChild(this.unconnectedNodeListComponent.render()); |
186 | 78 |
|
187 |
// missing classes |
|
188 |
this.missingClassListComponent = new SidebarMissingComponentList({ |
|
189 |
'id': 'missingClassListComponent', |
|
190 |
'class': 'hidden', |
|
191 |
}); |
|
192 |
sidebarContainer.appendChild(this.missingClassListComponent.render()); |
|
193 |
|
|
194 | 79 |
|
195 | 80 |
// excluded nodes |
196 | 81 |
this.excludedNodeListComponent = new SidebarExcludedNodeList({ |
... | ... | |
208 | 93 |
}; |
209 | 94 |
|
210 | 95 |
this.reset = function() { |
211 |
// remove active change |
|
212 |
activeChange.remove(); |
|
213 |
|
|
214 |
// set a new active change |
|
215 |
activeChange = new Change; |
|
216 |
activeChangeElement.appendChild(activeChange.render()); |
|
217 |
|
|
218 | 96 |
// reset lists |
219 |
this.postponedChangeListComponent.reset(); |
|
220 | 97 |
this.unconnectedNodeListComponent.reset(); |
221 |
this.missingClassListComponent.reset(); |
|
222 | 98 |
this.excludedNodeListComponent.reset(); |
223 | 99 |
|
224 | 100 |
// reset status bar |
sources/src/main/webapp/js/components/sidebarMissingComponentList.js | ||
---|---|---|
1 |
/** |
|
2 |
* @constructor |
|
3 |
* @param {object} props Properties of the components list. |
|
4 |
*/ |
|
5 |
function SidebarMissingComponentList(props) { |
|
6 |
/** @prop {string} id Identifier of the component. */ |
|
7 |
this.id = props.id; |
|
8 |
|
|
9 |
var rootElement; |
|
10 |
var componentListElement; |
|
11 |
|
|
12 |
var componentList = []; |
|
13 |
|
|
14 |
this.getComponentList = function() { |
|
15 |
return componentList; |
|
16 |
}; |
|
17 |
|
|
18 |
this.add = function(componentName) { |
|
19 |
if (componentList.indexOf(componentName) > -1) return; |
|
20 |
|
|
21 |
componentList.push(componentName); |
|
22 |
|
|
23 |
var listItem = app.utils.createHtmlElement('li', { |
|
24 |
'title': componentName, |
|
25 |
}); |
|
26 |
listItem.appendChild(document.createTextNode(componentName)); |
|
27 |
componentListElement.appendChild(listItem); |
|
28 |
}; |
|
29 |
|
|
30 |
this.render = function() { |
|
31 |
rootElement = app.utils.createHtmlElement('div', { |
|
32 |
'id': props.id, |
|
33 |
'class': 'node-container missing-components ' + (props.class ? props.class : ''), |
|
34 |
}); |
|
35 |
rootElement.addEventListener('scroll', function() { |
|
36 |
app.redrawEdges(); |
|
37 |
}); |
|
38 |
|
|
39 |
// title |
|
40 |
rootElement.appendChild(app.dom.htmlStringToElement('<h2 class="node-container-title">Missing classes</h2>')); |
|
41 |
|
|
42 |
// component list |
|
43 |
componentListElement = app.utils.createHtmlElement('ul', { |
|
44 |
'class': 'node-list', |
|
45 |
}); |
|
46 |
rootElement.appendChild(componentListElement); |
|
47 |
|
|
48 |
return rootElement; |
|
49 |
}; |
|
50 |
|
|
51 |
this.reset = function() { |
|
52 |
componentList = []; |
|
53 |
|
|
54 |
$(componentListElement).empty(); |
|
55 |
}; |
|
56 |
} |
sources/src/main/webapp/js/components/sidebarPostponedChangeList.js | ||
---|---|---|
1 |
/** |
|
2 |
* @constructor |
|
3 |
* @param {object} props Properties of the change list. |
|
4 |
*/ |
|
5 |
function SidebarPostponedChangeList(props) { |
|
6 |
/** @prop {string} id Identifier of the component. */ |
|
7 |
this.id = props.id; |
|
8 |
|
|
9 |
var rootElement; |
|
10 |
var buttonGroup; |
|
11 |
var changeListElement; |
|
12 |
|
|
13 |
var changeList = []; |
|
14 |
|
|
15 |
this.getChangeList = function() { |
|
16 |
return changeList; |
|
17 |
}; |
|
18 |
|
|
19 |
this.add = function(change) { |
|
20 |
if (!(change instanceof Change)) { |
|
21 |
throw new TypeError(change.toString() + 'is not instance of Change'); |
|
22 |
} |
|
23 |
|
|
24 |
change.setPostponed(true); |
|
25 |
|
|
26 |
changeList.push(change); |
|
27 |
changeListElement.appendChild(change.render()); |
|
28 |
|
|
29 |
app.redrawEdges(); |
|
30 |
}; |
|
31 |
|
|
32 |
this.remove = function(change) { |
|
33 |
if (!(change instanceof Change)) { |
|
34 |
throw new TypeError(change.toString() + 'is not instance of Change'); |
|
35 |
} |
|
36 |
|
|
37 |
change.setPostponed(false); |
|
38 |
|
|
39 |
changeList.splice(changeList.indexOf(change), 1); |
|
40 |
change.remove(); |
|
41 |
|
|
42 |
app.redrawEdges(); |
|
43 |
}; |
|
44 |
|
|
45 |
this.render = function() { |
|
46 |
rootElement = app.utils.createHtmlElement('div', { |
|
47 |
'id': props.id, |
|
48 |
'class': 'node-container postponed-nodes ' + (props.class ? props.class : ''), |
|
49 |
}); |
|
50 |
rootElement.addEventListener('scroll', function() { |
|
51 |
app.redrawEdges(); |
|
52 |
}); |
|
53 |
|
|
54 |
// title |
|
55 |
rootElement.appendChild(app.dom.htmlStringToElement('<h2 class="node-container-title">Postponed changes</h2>')); |
|
56 |
|
|
57 |
// list |
|
58 |
changeListElement = app.utils.createHtmlElement('ul', { |
|
59 |
'class': 'change-list', |
|
60 |
}); |
|
61 |
rootElement.appendChild(changeListElement); |
|
62 |
|
|
63 |
return rootElement; |
|
64 |
}; |
|
65 |
|
|
66 |
this.reset = function() { |
|
67 |
console.log('TODO: should SidebarPostponedChangeList.reset() method do something?'); |
|
68 |
}; |
|
69 |
|
|
70 |
function includeAll() { |
|
71 |
var nodeListCopy = nodeList.slice(0); |
|
72 |
nodeListCopy.forEach(function(node) { |
|
73 |
node.include(); |
|
74 |
}, this); |
|
75 |
|
|
76 |
toggleButtonGroup.call(this); |
|
77 |
} |
|
78 |
|
|
79 |
function toggleButtonGroup() { |
|
80 |
if (nodeList.length > 0) { |
|
81 |
buttonGroup.classList.remove('hidden'); |
|
82 |
} else { |
|
83 |
buttonGroup.classList.add('hidden'); |
|
84 |
} |
|
85 |
} |
|
86 |
} |
sources/src/main/webapp/js/components/statusBar.js | ||
---|---|---|
1 | 1 |
/** |
2 |
* Class representing the sidebar status bar. It displays number of components loaded in the diagram and the current graph version.
|
|
2 |
* Class representing the sidebar status bar. It displays number of components loaded in the diagram. |
|
3 | 3 |
* @constructor |
4 | 4 |
*/ |
5 | 5 |
function StatusBar() { |
6 | 6 |
var rootElement; |
7 | 7 |
var componentCounterElement; |
8 |
var graphVersionElement; |
|
9 | 8 |
|
10 | 9 |
/** |
11 | 10 |
* Sets a new count of components loaded in the diagram. |
... | ... | |
16 | 15 |
componentCounterElement.appendChild(app.utils.createTextElement('loaded components: ' + componentCount)); |
17 | 16 |
}; |
18 | 17 |
|
19 |
/** |
|
20 |
* Sets a new graph version. |
|
21 |
* @param {string|integer} graphVersion New graph version. |
|
22 |
*/ |
|
23 |
this.setGraphVersion = function(graphVersion) { |
|
24 |
graphVersionElement.innerHTML = ''; |
|
25 |
graphVersionElement.appendChild(app.utils.createTextElement('graph version: ' + graphVersion)); |
|
26 |
}; |
|
27 |
|
|
28 | 18 |
/** |
29 | 19 |
* Creates a new DOM element representing the status bar in memory. |
30 | 20 |
* @returns {Element} HTML DOM element. |
... | ... | |
39 | 29 |
}); |
40 | 30 |
rootElement.appendChild(componentCounterElement); |
41 | 31 |
|
42 |
graphVersionElement = app.utils.createHtmlElement('span', { |
|
43 |
'class': 'graph-version', |
|
44 |
}); |
|
45 |
rootElement.appendChild(graphVersionElement); |
|
46 |
|
|
47 | 32 |
return rootElement; |
48 | 33 |
}; |
49 | 34 |
|
... | ... | |
52 | 37 |
*/ |
53 | 38 |
this.reset = function() { |
54 | 39 |
componentCounterElement.innerHTML = ''; |
55 |
graphVersionElement.innerHTML = ''; |
|
56 | 40 |
}; |
57 | 41 |
} |
sources/src/main/webapp/js/constants.js | ||
---|---|---|
3 | 3 |
* @constructor |
4 | 4 |
*/ |
5 | 5 |
function Constants() { |
6 |
/** @prop {string} crceApiBase CRCE API base path. */ |
|
7 |
this.crceApiBase = 'http://localhost:8081/rest/v2'; |
|
8 | 6 |
/** @prop {string} notFoundVertexName Name of the vertex that groups all components that were not found while constructing graph. */ |
9 | 7 |
this.notFoundVertexName = 'NOT_FOUND'; |
10 | 8 |
} |
sources/src/main/webapp/js/graphLoader.js | ||
---|---|---|
87 | 87 |
app.sidebarComponent.unconnectedNodeListComponent.add(vertex); |
88 | 88 |
}); |
89 | 89 |
|
90 |
// find missing components |
|
91 |
app.edgeList.filter(function(edge) { |
|
92 |
return edge.getFrom().name === app.constants.notFoundVertexName; |
|
93 |
}).forEach(function(edge) { |
|
94 |
var compatibilityInfoList = edge.getCompatibilityInfo(); |
|
95 |
compatibilityInfoList.forEach(function(compatibilityInfo) { |
|
96 |
if (compatibilityInfo.incomps.length === 0) return; |
|
97 |
|
|
98 |
compatibilityInfo.incomps.forEach(function(incompatibility) { |
|
99 |
if (!incompatibility.desc.isIncompCause) return; |
|
100 |
|
|
101 |
app.sidebarComponent.missingClassListComponent.add(incompatibility.desc.name); |
|
102 |
}); |
|
103 |
}); |
|
104 |
}); |
|
105 |
|
|
106 | 90 |
// update status bar |
107 | 91 |
app.sidebarComponent.statusBarComponent.setComponentCount(data.vertices.length); |
108 |
app.sidebarComponent.statusBarComponent.setGraphVersion(app.cookies.get('graphVersion')); |
|
109 | 92 |
}; |
110 | 93 |
|
111 | 94 |
} |
sources/src/main/webapp/js/javaComponentChanger.js | ||
---|---|---|
1 |
/** |
|
2 |
* @constructor |
|
3 |
*/ |
|
4 |
function JavaComponentChanger() { |
|
5 |
var javaClasses = { |
|
6 |
boolean: 'java.lang.Boolean', |
|
7 |
string: 'java.lang.String', |
|
8 |
list: 'java.util.List', |
|
9 |
set: 'java.util.Set', |
|
10 |
}; |
|
11 |
|
|
12 |
var crceClasses = { |
|
13 |
package: 'crce.api.java.package', |
|
14 |
class: 'crce.api.java.class', |
|
15 |
method: 'crce.api.java.method', |
|
16 |
property: 'crce.api.java.property', |
|
17 |
}; |
|
18 |
|
|
19 |
var ns = ''; // http://relisa.kiv.zcu.cz |
|
20 |
var xsi = 'http://www.w3.org/2001/XMLSchema-instance'; |
|
21 |
var xsd = 'crce.xsd'; |
|
22 |
|
|
23 |
var xmlDocument; |
|
24 |
var nodeCounter; |
|
25 |
|
|
26 |
var xmlParser = new DOMParser(); |
|
27 |
var xmlSerializer = new XMLSerializer(); |
|
28 |
|
|
29 |
/** |
|
30 |
* Sends change requirements to CRCE. |
|
31 |
* @param {array} components Components to be changed. |
|
32 |
* @param {boolean} includeNotFound True if not found classes should be added as change requirements, otherwise false. |
|
33 |
*/ |
|
34 |
this.run = function(components, includeNotFound) { |
|
35 |
if (components.length === 0) return; |
|
36 |
|
|
37 |
// initialize requirements XML to be sent to CRCE |
|
38 |
xmlDocument = constructXmlDocument(components, includeNotFound); |
|
39 |
|
|
40 |
console.log('CRCE request:', xmlDocument); |
|
41 |
|
|
42 |
// trigger change |
|
43 |
return $.ajax({ |
|
44 |
type: 'POST', // jQuery docs tells to use "method" but it doesn't work and always sends GET -> use "type" instead |
|
45 |
url: app.constants.crceApiBase + '/metadata/catalogue/', |
|
46 |
data: xmlSerializer.serializeToString(xmlDocument), |
|
47 |
contentType: 'application/xml', |
|
48 |
timeout: 180 * 1000, // in milliseconds |
|
49 |
|
|
50 |
}).then(function(data, textStatus, jqXHR) { |
|
51 |
console.log('CRCE response:', data); |
|
52 |
|
|
53 |
var resourcesEl = data.childNodes[0]; |
|
54 |
if (resourcesEl.childNodes.length === 0) { |
|
55 |
return $.Deferred().reject('CRCE did not find any resources fitting your requirements.').promise(); |
|
56 |
} |
|
57 |
|
|
58 |
var proposals = []; |
|
59 |
|
|
60 |
resourcesEl.childNodes.forEach(function(resourceEl, index) { |
|
61 |
var proposal = { |
|
62 |
uuid: resourceEl.getAttribute('uuid'), |
|
63 |
}; |
|
64 |
|
|
65 |
var capabilityEl = resourceEl.childNodes[0]; |
|
66 |
capabilityEl.childNodes.forEach(function(attributeEl) { |
|
67 |
if (['name', 'external-id', 'version'].includes(attributeEl.getAttribute('name'))) { |
|
68 |
proposal[attributeEl.getAttribute('name')] = attributeEl.getAttribute('value'); |
|
69 |
} |
|
70 |
}); |
|
71 |
proposals.push(proposal); |
|
72 |
}); |
|
73 |
|
|
74 |
return proposals; |
|
75 |
}); |
|
76 |
}; |
|
77 |
|
|
78 |
function constructXmlDocument(components, includeNotFound) { |
|
79 |
// initialize requirements XML to be sent to CRCE |
|
80 |
xmlDocument = document.implementation.createDocument(ns, 'requirements', null); |
|
81 |
//xmlDocument.documentElement.setAttributeNS(xsi, 'xsi:schemaLocation', ns + ' ' + xsd); |
|
82 |
|
|
83 |
nodeCounter = 0; |
|
84 |
|
|
85 |
// optimize returned results |
|
86 |
var optimizeByRequirementEl = xmlDocument.createElementNS(ns, 'requirement'); |
|
87 |
optimizeByRequirementEl.setAttribute('namespace', 'result.optimize-by'); |
|
88 |
|
|
89 |
var optimizeByFunctionAttributeEl = xmlDocument.createElementNS(ns, 'attribute'); |
|
90 |
optimizeByFunctionAttributeEl.setAttribute('name', 'function-ID'); |
|
91 |
optimizeByFunctionAttributeEl.setAttribute('type', javaClasses.string); |
|
92 |
optimizeByFunctionAttributeEl.setAttribute('value', 'cf-equal-cost'); |
|
93 |
|
|
94 |
var optimizeByMethodAttributeEl = xmlDocument.createElementNS(ns, 'attribute'); |
|
95 |
optimizeByMethodAttributeEl.setAttribute('name', 'method-ID'); |
|
96 |
optimizeByMethodAttributeEl.setAttribute('type', javaClasses.string); |
|
97 |
optimizeByMethodAttributeEl.setAttribute('value', 'ro-ilp-direct-dependencies'); |
|
98 |
|
|
99 |
optimizeByRequirementEl.appendChild(optimizeByFunctionAttributeEl); |
|
100 |
optimizeByRequirementEl.appendChild(optimizeByMethodAttributeEl); |
|
101 |
|
|
102 |
xmlDocument.documentElement.appendChild(optimizeByRequirementEl); |
|
103 |
|
|
104 |
// component does not have to fulfill all requirements |
|
105 |
var directiveEl = xmlDocument.createElementNS(ns, 'directive'); |
|
106 |
directiveEl.setAttribute('name', 'operator'); |
|
107 |
directiveEl.setAttribute('value', 'or'); |
|
108 |
|
|
109 |
xmlDocument.documentElement.appendChild(directiveEl); |
|
110 |
|
|
111 |
// component requirements |
|
112 |
components.forEach(function(component) { |
|
113 |
var inEdgeList = component.getInEdgeList(); |
|
114 |
if (!includeNotFound) { |
|
115 |
inEdgeList = inEdgeList.filter(function(edge) { |
|
116 |
return edge.getFrom().name !== app.constants.notFoundVertexName; |
|
117 |
}); |
|
118 |
} |
|
119 |
|
|
120 |
if (inEdgeList.length === 0) return; |
|
121 |
|
|
122 |
// construct functionality requirements tree |
|
123 |
inEdgeList.forEach(function(edge) { |
|
124 |
var compatibilityInfoList = edge.getCompatibilityInfo(); |
|
125 |
|
|
126 |
compatibilityInfoList.forEach(function(compatibilityInfo) { |
|
127 |
compatibilityInfo.incomps.forEach(function(incompatibility) { |
|
128 |
appendRequirementTree(xmlDocument.documentElement, incompatibility); |
|
129 |
}); |
|
130 |
}); |
|
131 |
}); |
|
132 |
}); |
|
133 |
|
|
134 |
return xmlDocument; |
|
135 |
} |
|
136 |
|
|
137 |
function appendRequirementTree(element, incompatibility) { |
|
138 |
var type = incompatibility.desc.type; |
|
139 |
|
|
140 |
if (app.utils.isUndefined(type)) { |
|
141 |
incompatibility.subtree.forEach(function(incompatibility) { |
|
142 |
appendRequirementTree(element, incompatibility); |
|
143 |
}); |
|
144 |
|
|
145 |
} else { |
|
146 |
// add package for classes |
|
147 |
if (type === 'class') { |
|
148 |
var packageRequirementEl = xmlDocument.createElementNS(ns, 'requirement'); |
|
149 |
packageRequirementEl.setAttribute('uuid', nodeCounter++); |
|
150 |
packageRequirementEl.setAttribute('namespace', crceClasses['package']); |
|
151 |
|
|
152 |
var packageAttributeEl = xmlDocument.createElementNS(ns, 'attribute'); |
|
153 |
packageAttributeEl.setAttribute('name', 'name'); |
|
154 |
packageAttributeEl.setAttribute('type', javaClasses.string); |
|
155 |
packageAttributeEl.setAttribute('value', incompatibility.desc.details.package); |
|
156 |
|
|
157 |
packageRequirementEl.appendChild(packageAttributeEl); |
|
158 |
} |
|
159 |
|
|
160 |
var requirementEl = xmlDocument.createElementNS(ns, 'requirement'); |
|
161 |
requirementEl.setAttribute('uuid', nodeCounter++); |
Také k dispozici: Unified diff
dropped unused functionality