commit c91081fbb79aceba6ce19cecc1b07ee9bcf10a49
Author: Pavel Fidranský <fidransky@outlook.com>
Date:   Wed Aug 22 13:58:12 2018 +0200

    Vertex/Edge attribute rendering

diff --git a/sources/src/main/java/cz/zcu/kiv/offscreen/graph/GraphManager.java b/sources/src/main/java/cz/zcu/kiv/offscreen/graph/GraphManager.java
index 8fa161f..0e414dd 100644
--- a/sources/src/main/java/cz/zcu/kiv/offscreen/graph/GraphManager.java
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/graph/GraphManager.java
@@ -541,7 +541,8 @@ public class GraphManager {
                 attrValue = new StringBuilder(attr.getValue().toString());
                 break;
             case DATE:
-                attrValue = new StringBuilder(attr.getValue().toString());
+                Date value = (Date) attr.getValue();
+                attrValue = new StringBuilder(String.valueOf(value.getTime()));
                 break;
             case ENUM: // Enum attribute type must be a list of integers!
                 List<Integer> valuePositions = (List<Integer>) attr.getValue();
diff --git a/sources/src/main/webapp/js/app.js b/sources/src/main/webapp/js/app.js
index 59d0d4d..6c4f365 100644
--- a/sources/src/main/webapp/js/app.js
+++ b/sources/src/main/webapp/js/app.js
@@ -56,6 +56,9 @@ function App() {
 		icon: [],
 	};
 
+	/** TODO: jsDoc */
+	this.attributeTypeList = [];
+
 	/**
 	 * Loads graph using diagram (if available).
 	 * @param diagramId Diagram identifier.
diff --git a/sources/src/main/webapp/js/components/attribute.js b/sources/src/main/webapp/js/components/attribute.js
new file mode 100644
index 0000000..4eae0eb
--- /dev/null
+++ b/sources/src/main/webapp/js/components/attribute.js
@@ -0,0 +1,58 @@
+/**
+ * TODO: jsDoc
+ * @constructor
+ * @param {array} props Details of the attribute.
+ */
+function Attribute(props) {
+	var attributeType = app.attributeTypeList.find(function(attributeType) {
+		return attributeType.name === props[0];
+	});
+
+	this.name = props[0];
+	this.value = props[1];
+	this.dataType = attributeType.dataType;
+
+	var rootElement;
+
+	this.render = function() {
+		rootElement = app.utils.createHtmlElement('li', {});
+
+		rootElement.appendChild(app.dom.createTextElement(this.name + ': '));
+		rootElement.appendChild(renderValue.call(this));
+
+		return rootElement;
+	};
+
+	function renderValue() {
+		switch (this.name) {
+			case 'URL':
+				return app.dom.htmlStringToElement(`<a href="${this.value}" target="_blank">${this.value}</a>`);
+			case 'Size':
+				return app.dom.createTextElement(formatValue.call(this) + ' B');
+			case 'Estimate':
+			case 'Spent':
+				return app.dom.createTextElement(formatValue.call(this) + ' h');
+			case 'Progress':
+				return app.dom.createTextElement(formatValue.call(this) + '%');
+			default:
+				return app.dom.createTextElement(formatValue.call(this));
+		}
+	}
+
+	function formatValue() {
+		switch (this.dataType) {
+			case 'DATE':
+				var date = new Date(parseInt(this.value));
+				return date.toLocaleString();
+			default:
+			case 'STRING':
+				if (Array.isArray(this.value)) {
+					return this.value.filter(function(value) {
+						return value !== '';
+					}).join(', ');
+				} else {
+					return this.value;
+				}
+			}
+	}
+}
diff --git a/sources/src/main/webapp/js/components/edgePopover.js b/sources/src/main/webapp/js/components/edgePopover.js
index 7a4b0a9..72e31f0 100644
--- a/sources/src/main/webapp/js/components/edgePopover.js
+++ b/sources/src/main/webapp/js/components/edgePopover.js
@@ -26,10 +26,7 @@ function EdgePopover() {
 			detailsListElement.appendChild(listItem);
 
 			subedgeInfo.attributes.forEach(function(attribute) {
-				var listItem = app.dom.createHtmlElement('li', {});
-				listItem.appendChild(app.dom.createTextElement(`${attribute[0]}: ${attribute[1]}`));
-
-				sublist.appendChild(listItem);
+				sublist.appendChild(new Attribute(attribute).render());
 			});
 		});
 	};
diff --git a/sources/src/main/webapp/js/components/vertexPopover.js b/sources/src/main/webapp/js/components/vertexPopover.js
index fb13330..e243e10 100644
--- a/sources/src/main/webapp/js/components/vertexPopover.js
+++ b/sources/src/main/webapp/js/components/vertexPopover.js
@@ -19,10 +19,7 @@ function VertexPopover() {
 		if (attributeList.length === 0) return;
 
 		attributeList.forEach(function(attribute) {
-			var listItem = app.utils.createHtmlElement('li', {});
-			listItem.appendChild(document.createTextNode(`${attribute[0]}: ${attribute[1]}`));
-
-			detailsListElement.appendChild(listItem);
+			detailsListElement.appendChild(new Attribute(attribute).render());
 		});
 	};
 
diff --git a/sources/src/main/webapp/js/graphLoader.js b/sources/src/main/webapp/js/graphLoader.js
index 95f7606..3ea2edf 100644
--- a/sources/src/main/webapp/js/graphLoader.js
+++ b/sources/src/main/webapp/js/graphLoader.js
@@ -20,6 +20,8 @@ function GraphLoader() {
 
 		var canvasSize = ((data.vertices.length * 75) / Math.round(Math.sqrt(data.vertices.length))) + 1000;
 
+		app.attributeTypeList = data.attributeTypes;
+
 		// store archetypes
 		app.archetype.vertex = data.vertexArchetypes;
 		app.archetype.edge = data.edgeArchetypes;
diff --git a/sources/src/main/webapp/showGraph.jsp b/sources/src/main/webapp/showGraph.jsp
index 343adfc..c8c1620 100644
--- a/sources/src/main/webapp/showGraph.jsp
+++ b/sources/src/main/webapp/showGraph.jsp
@@ -19,6 +19,7 @@
 		<script src="js/libs/saveSvgAsPng.js"></script>
 		<script src="js/libs/jstree.min.js"></script>
 
+		<script src="js/components/attribute.js"></script>
 		<script src="js/components/edge.js"></script>
 		<script src="js/components/edgePopover.js"></script>
 		<script src="js/components/floatingPoint.js"></script>
@@ -30,7 +31,6 @@
 		<script src="js/components/statusBar.js"></script>
 		<script src="js/components/vertex.js"></script>
 		<script src="js/components/vertexContextMenuList.js"></script>
-		<script src="js/components/vertexLight.js"></script>
 		<script src="js/components/vertexPopover.js"></script>
 		<script src="js/components/vertexSymbolList.js"></script>
 		<script src="js/components/viewport.js"></script>
