1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

Merge branch 'pintura-widget' into 'develop'

Pintura widget

See merge request acs/public/villas/VILLASweb!24
This commit is contained in:
Ricardo Hernandez 2018-01-10 17:21:02 +01:00
commit 8c7f27d681
28 changed files with 6606 additions and 6 deletions

View file

@ -31,6 +31,7 @@
"react-router-dom": "^4.1.2",
"react-scripts": "1.0.10",
"react-sortable-tree": "^0.1.19",
"react-svg-pan-zoom": "^2.14.1",
"superagent": "^3.5.0"
},
"devDependencies": {

View file

@ -0,0 +1,61 @@
#menu {
color:#fff!important;
background-color:#000!important
}
.dark-grey-background {
color:black;
background:#aaa;
}
.light-grey-background {
color:black;
background:#ddd;
}
.blue-grey-background {
color:white;
background:#607d8b;
}
.floating-panel-item {
font-size:12px;
}
#floating-panel-list-div {
background:#aaa;
}
.floating-panel-list {
background:grey;
border:none;
}
.w3-ul li {
border-bottom:0px;
}
#sidebar {
background:#607d8b;
border-right:thick solid white;
}
.component-type-name {
color:black;
font-size:12px;
}
.floating-panel-name {
font-size:12px;
}
.dark-font {
color:black;
}
.dropdown-menu {
border:medium solid black;
background:#607d8b;
border:medium solid black;
border-width: 1px 1px 1px 1px;
}
.dropdown-menu h4 {
color:white;
}
.dropdown-menu a {
color: black;
background:#ddd;
font-size:14px;
}
.dropdown-menu a:hover {
background:#bbb;
color: white;
}

View file

@ -0,0 +1,84 @@
.bar {
stroke: #000;
stroke-width: 3px;
}
.highlighted-node:hover {
stroke: #ff0;
}
line {
stroke: #000;
stroke-width: 1px;
}
.line {
stroke: #000;
stroke-width: 2px;
}
.terminal-connnode {
stroke: #000;
stroke-width: 1px;
}
.terminal-toponode {
stroke: #000;
stroke-width: 1px;
}
.conduct {
stroke: #000;
stroke-width: 1px;
}
.unknown {
stroke: #f0f;
stroke-width: 1px;
height: 20px;
width: 20px;
}
.acline {
stroke: #000;
stroke-width: 2px;
}
#backing {
fill: whitesmoke;
}
/* Below here are SVG elements that we don't want the user to interact with
therefore we disable pointer events */
.svglabel {
visibility: hidden;
pointer-events: none;
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
}
.svglabel-high {
visibility: visible;
font-size: 12px;
font-family: "sans-serif";
text-anchor: right;
fill: black;
stroke-width: 1px;
pointer-events: none;
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
}
.gridLine {
stroke: #aaa;
stroke-width: 1px;
pointer-events: none;
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
}
.gridLabel {
font-size: 8px;
font-family: "sans-serif";
fill: grey;
stroke-width: 0px;
pointer-events: none;
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
}

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 65 KiB

View file

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="4" height="16" width="16" fill-opacity="0" stroke="black" stroke-width="1" />
<line x1="4" y1="12" x2="8" y2="12" fill-opacity="0" stroke="black" stroke-width="1" />
<line x1="8" y1="12" x2="14" y2="8" fill-opacity="0" stroke="black" stroke-width="1" />
<line x1="15" y1="12" x2="20" y2="12" fill-opacity="0" stroke="black" stroke-width="1" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="27" width="27" xmlns="http://www.w3.org/2000/svg">
<circle id="circle" cx="13" cy="14" r="6" fill-opacity="1" stroke="black" stroke-width="1" />
</svg>

After

Width:  |  Height:  |  Size: 909 B

View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
<polygon points="7,10 17,10 12,24" style="fill:black;stroke:black;stroke-width:1" />
<line x1="12" y1="3" x2="12" y2="22" style="fill:black;stroke:black;stroke-width:1" />
</svg>

After

Width:  |  Height:  |  Size: 991 B

View file

@ -0,0 +1,27 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="36" width="36" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="diagonalHatch" width="8" height="8" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="8" y2="8" style="stroke:black; stroke-width:1" />
<line x1="8" y1="0" x2="0" y2="8" style="stroke:black; stroke-width:1" />
</pattern>
</defs>
<rect x="6" y="6" width="24" height="24" style="fill:url(#diagonalHatch);stroke:black;stroke-width:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="36" width="36" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,2 L0,4 L2,3 z" fill="#000" />
</marker>
<pattern id="diagonalHatch" width="8" height="8" patternUnits="userSpaceOnUse">
<line x1="8" y1="0" x2="0" y2="8" style="stroke:black; stroke-width:1" />
</pattern>
</defs>
<line x1="8" y1="8" x2="12" y2="12" stroke="#000" stroke-width="3" marker-end="url(#arrow)" />
<line x1="16" y1="8" x2="20" y2="12" stroke="#000" stroke-width="3" marker-end="url(#arrow)" />
<rect x="6" y="18" width="24" height="12" style="fill:url(#diagonalHatch);stroke:black;stroke-width:1" />
<rect x="6" y="6" width="24" height="24" style="fill:none;stroke:black;stroke-width:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
<circle id="circle" cx="12" cy="12" r="10" fill-opacity="0" stroke="black" stroke-width="1" />
<path d="M4 12 C 6 5, 10 5, 12 12 S 19 19, 20 12" stroke="black" fill="transparent"/>
</svg>

After

Width:  |  Height:  |  Size: 1,000 B

View file

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="8" width="8" height="8" style="fill:black;stroke:black;stroke-width:1" />
</svg>

After

Width:  |  Height:  |  Size: 904 B

View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="12" width="12" xmlns="http://www.w3.org/2000/svg" stroke-width="1">
<circle cx="6" cy="6" r="1" fill-opacity="1" stroke="black" stroke-width="1" />
<rect x="0" y="0" height="12" width="12" fill-opacity="0" stroke="black" stroke-width="1" />
</svg>

After

Width:  |  Height:  |  Size: 1,009 B

View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!--
Copyright © 2016-2017, RWTH Aachen University
Authors: Richard Marston
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
A copy of the GNU General Public License is in the LICENSE file
in the top level directory of this source tree.
-->
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
<circle id="leftcircle" cx="13" cy="10" r="7" fill-opacity="0" stroke="black" stroke-width="1" />
<circle id="rightcircle" cx="13" cy="14" r="7" fill-opacity="0" stroke="black" stroke-width="1" />
</svg>

After

Width:  |  Height:  |  Size: 1,016 B

View file

@ -0,0 +1,201 @@
/*
* Copyright © 2016-2017, RWTH Aachen University
* Authors: Richard Marston
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* A copy of the GNU General Public License is in the LICENSE file
* in the top level directory of this source tree.
*/
var cimjson = cimjson || (function() {
var pathBase = '';
const imageNames = {
"cim:ACLineSegment": "images/term.svg",
"cim:Terminal": "images/term.svg",
"cim:Breaker": "images/brea.svg",
"cim:ConnectivityNode": "images/conn.svg",
"cim:EnergyConsumer": "images/cons.svg",
"cim:EquivalentInjection": "images/cons.svg",
"cim:ExternalNetworkInjection": "images/net.svg",
"cim:PowerTransformer": "images/trans.svg",
"cim:SolarGeneratingUnit": "images/solar.svg",
"cim:SynchronousMachine": "images/sync.svg",
"cim:TopologicalNode": "images/topo.svg",
"cim:TransformerWinding": "images/trans.svg",
};
const PinturaDiagramObjectPoints = "Pintura:DiagramObjectPoints";
var getImageName = function(type) {
return pathBase + imageNames[type];
}
var convertDiagramObjectToTemplateFormat = function(diagramObject, graph, categoryGraphName, diagramList) {
let originalPoints = diagramObject[PinturaDiagramObjectPoints];
let preOffsetPoints = [];
let imagePoints = [];
let labelPoint;
let object;
let categoryGraph = graph[categoryGraphName];
const imageHeight = 12;
const imageWidth = 12;
if (diagramObject["cim:DiagramObject.IdentifiedObject"] != undefined) {
let rdfId = diagramObject["cim:DiagramObject.IdentifiedObject"]["rdf:resource"].substring(1);
for (let index in originalPoints) {
let point = originalPoints[index];
preOffsetPoints.push(
{
"x": parseInt(point["cim:DiagramObjectPoint.xPosition"]).toString(),
"y": parseInt(point["cim:DiagramObjectPoint.yPosition"]).toString()
});
imagePoints.push(
{
"imageHeight" : imageHeight.toString(),
"imageWidth" : imageWidth.toString(),
"x" : (parseInt(point["cim:DiagramObjectPoint.xPosition"]) - (imageWidth/2)).toString(),
"y" : (parseInt(point["cim:DiagramObjectPoint.yPosition"]) - (imageHeight/2)).toString()
});
};
labelPoint = {
"x": (parseInt(preOffsetPoints[0].x) + (imageWidth/2)).toString(),
"y": (parseInt(preOffsetPoints[0].y) - (imageHeight/2)).toString()
};
object =
{
"pintura:image" : getImageName(categoryGraphName),
"pintura:rdfId" : rdfId,
"pintura:points" : imagePoints,
"pintura:label" : {
"text": categoryGraph[rdfId]["cim:IdentifiedObject.name"],
"x" : labelPoint.x,
"y" : labelPoint.y
}
}
while (preOffsetPoints.length > 1) {
if (object["pintura:line"] == null) {
object["pintura:line"] = [];
}
let line = {
"x1": preOffsetPoints[0].x,
"y1": preOffsetPoints[0].y,
"x2": preOffsetPoints[1].x,
"y2": preOffsetPoints[1].y
};
object["pintura:line"].push(line);
preOffsetPoints.shift()
}
}
let diagram = diagramObject["cim:DiagramObject.Diagram"]["rdf:resource"].substring(1);
if (diagramList[diagram] === undefined){
diagramList[diagram] = { "pintura:name" : graph["cim:Diagram"][diagram]["cim:IdentifiedObject.name"] };
}
if (diagramObject["cim:DiagramObject.IdentifiedObject"]) {
let identifiedObject = diagramObject["cim:DiagramObject.IdentifiedObject"]["rdf:resource"].substring(1);
if (diagramList[diagram]["components"] === undefined){
diagramList[diagram]["components"] = {};
}
if (diagramList[diagram]["components"][categoryGraphName] === undefined){
diagramList[diagram]["components"][categoryGraphName] = {};
}
diagramList[diagram]["components"][categoryGraphName][identifiedObject] = object;
}
};
var convertToTemplatableFormat = function(diagramObjects, graph) {
let output = { 'Diagram' : {} };
let diagramList = output['Diagram'];
for (categoryGraphName in imageNames) {
let categoryGraph = graph[categoryGraphName];
for (let key in categoryGraph) {
let diagramObject = diagramObjects[key];
if (diagramObject != undefined) {
convertDiagramObjectToTemplateFormat(diagramObject, graph, categoryGraphName, diagramList);
}
}
}
return output;
};
var indexDiagramGraphByComponentType = function(input) {
/*
* Index the diagram object graph by the identified object's id so we don't
* have to go hunting for the referenced object inside the diagram objects.
*/
let graph = {};
for (let key in input) {
let diagramObject = input[key];
let diagram = diagramObject["cim:DiagramObject.Diagram"]["rdf:resource"].substring(1);
if (diagramObject["cim:DiagramObject.IdentifiedObject"]) {
let identifiedObject = input[key]["cim:DiagramObject.IdentifiedObject"]["rdf:resource"].substring(1);
graph[identifiedObject] = input[key];
}
}
return graph;
};
var addDiagramObjectPointsToDiagramObjects = function(diagramObjectPointGraph, diagramObjectGraph){
for (let key in diagramObjectPointGraph) {
mergeMatchingDataIntoParentNodeArray(diagramObjectPointGraph[key], "cim:DiagramObjectPoint.DiagramObject", diagramObjectGraph, PinturaDiagramObjectPoints);
}
};
/*
* Create link to a member of an array of e.g. points
*/
var mergeMatchingDataIntoParentNodeArray = function(node, matchingElement, destinationGraph, destinationElement) {
if (node[matchingElement]) {
let id = node[matchingElement]["rdf:resource"].substr(1);
if (destinationGraph[id]) {
if (destinationGraph[id][destinationElement] === undefined) {
destinationGraph[id][destinationElement] = [];
}
destinationGraph[id][destinationElement].push(node);
}
else {
console.log("Could not find destination "+matchingElement+" to merge into "+destinationElement+".");
}
}
else {
console.log("Could not find matching element "+matchingElement+" to merge "+destinationElement+" into .");
}
};
var getTemplateJson = function(graph) {
let updatedDiagramObjects = JSON.parse(JSON.stringify(graph['cim:DiagramObject']));
let diagramObjectPoints = graph['cim:DiagramObjectPoint'];
addDiagramObjectPointsToDiagramObjects(diagramObjectPoints, updatedDiagramObjects);
let diagramObjectsByIdentifiedObjects = indexDiagramGraphByComponentType(updatedDiagramObjects);
templateReadyFormat = convertToTemplatableFormat(diagramObjectsByIdentifiedObjects, graph);
return templateReadyFormat;
};
return {
setImagePathBase : function(path) {
pathBase = path;
},
getTemplateJson,
};
}());
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
cimjson
};
}

140
public/Pintura/js/cimsvg.js Normal file
View file

@ -0,0 +1,140 @@
/*
* Copyright © 2016-2017, RWTH Aachen University
* Authors: Richard Marston
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* A copy of the GNU General Public License is in the LICENSE file
* in the top level directory of this source tree.
*/
var cimsvg = cimsvg || (function() {
var svgNode = null;
var xmlNode = null;
var pinturaNode = null;
var sidebarNode = null;
function handler() {
//console.log(this.getResponseHeader('content-type'));
}
var includeFile = function(fileName, callback) {
let dom = svgNode.ownerDocument;
let newTag = dom.createElement("script");
newTag.type = "text/javascript";
newTag.src=fileName;
if ( callback != undefined ) {
newTag.onload=function() {
callback();
};
}
svgNode.parentElement.appendChild(newTag);
};
var applyTemplate = function(data) {
var template = Handlebars.templates['cim2svg'];
return template(data);
};
var loadFile = function(fileContents) {
if (cimxml.moreXmlData(fileContents)) {
baseJson = cimxml.getBaseJson();
templateJson = cimjson.getTemplateJson(baseJson);
svgNode.ownerDocument.getElementById('diagrams').innerHTML = applyTemplate(templateJson);
if(sidebarNode != null) {
cimmenu.populateSidebar(sidebarNode, templateJson);
}
}
};
var setFileCount = function(count) {
cimxml.setRdfFileCount(count);
};
var isNode = false;
if (typeof module !== 'undefined' && module.exports) {
isNode = true;
}
var updateComponent = function(type, id, attribute, value) {
cimxml.updateComponentInBaseJson(type, id, attribute, value)
baseJson = cimxml.getBaseJson();
templateJson = cimjson.getTemplateJson(baseJson);
if (attribute === "cim:IdentifiedObject.name") {
buttonId = '#' + id + "-sidebar-button"
button = sidebarNode.querySelector(buttonId)
button.innerHTML = value;
}
};
var loadXml = function(fileName, callback) {
// Create a connection to the file.
var Connect = new XMLHttpRequest();
// Define which file to open and
Connect.open("GET", fileName, true);
Connect.setRequestHeader("Content-Type", "text/xml");
Connect.onreadystatechange = handler;
Connect.onload = function (e) {
if(Connect.readyState === 4) {
if(Connect.status === 200) {
callback(Connect.responseXML);
}
else {
console.log(Connect.statusText);
}
}
};
// send the request.
Connect.send(null);
};
return {
init : function(svg, sidebar, componentAttributes, componentCreation) {
svgNode = svg;
sidebarNode = sidebar;
includeFile("handlebars.runtime.js", function() {
includeFile("src/cimview.js", function() {
cimview.init(svgNode);
if(sidebarNode != undefined) {
includeFile("src/cimmenu.js", function() {
cimmenu.init(componentAttributes)
});
}
includeFile("templates/template.js", function(){
includeFile("src/cimxml.js", function(){
includeFile("src/cimjson.js", function(){});
});
});
});
});
loadXml("templates/generated_add_components/menu.xml", function(xml){
if(componentCreation != undefined) {
accordion = componentCreation.querySelector('#component-creation-list-div')
accordion.innerHTML = xml.documentElement.innerHTML;
}
});
},
setSVG : function(svg) {
svgNode = svg;
},
loadFile,
setFileCount,
updateComponent,
};
}());
if (cimsvg.isNode) {
module.exports = {
cimsvg
}
}

View file

@ -0,0 +1,208 @@
/*
* Copyright © 2016-2017, RWTH Aachen University
* Authors: Richard Marston
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* A copy of the GNU General Public License is in the LICENSE file
* in the top level directory of this source tree.
*/
var cimview = cimview || (function() {
var svgNode = null;
var zoomSizes = [
{ width: 1024, height: 768 },
{ width: 920, height: 690 },
{ width: 816, height: 612 },
{ width: 712, height: 532 },
{ width: 608, height: 456 },
{ width: 504, height: 378 },
{ width: 400, height: 300 },
];
var zoomLevel = 0;
var zoomToLevel = function(level) {
zoomLevel = level;
let rect = getViewBox();
centreOfGrid = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
rect.width = zoomSizes[level].width;
rect.height = zoomSizes[level].height;
rect.x = centreOfGrid.x - (rect.width / 2);
rect.y = centreOfGrid.y - (rect.height / 2);
setViewBox(rect);
};
var zoomOut = function() {
let level = zoomLevel-1;
if (level < 0) {
level = 0;
}
zoomToLevel(level);
//document.getElementById("zoomer").value=level;
};
var zoomIn = function() {
let level = zoomLevel+1;
let lastIndex = zoomSizes.length-1;
if (level > lastIndex) {
level = lastIndex;
}
zoomToLevel(level);
//document.getElementById("zoomer").value=level;
};
var pan = function(point) {
let rect = getViewBox();
rect.x += point.x;
rect.y += point.y;
setViewBox(rect);
};
var clearGrid = function() {
let oldLines = Array.from(svgNode.getElementsByClassName("gridLine"));
oldLines.forEach(function(key) {
key.remove();
});
let oldLabels = Array.from(svgNode.getElementsByClassName("gridLabel"));
oldLabels.forEach(function(key) {
key.remove();
});
};
var createLocationMarker = function(id, loc, x, y) {
let grid = svgNode.ownerDocument.getElementById("grid");
let textAttributes = {
"x": x,
"y": y,
"class": "gridLabel",
"id": id,
};
let text = createSvgTag("text", textAttributes);
text.innerHTML = loc;
grid.appendChild(text);
};
var createGridLine = function(x1, y1, x2, y2) {
let grid = svgNode.ownerDocument.getElementById("grid");
let lineAttributes = {
"x1": x1,
"x2": x2,
"y1": y1,
"y2": y2,
"class": "gridLine",
};
let line = createSvgTag("line", lineAttributes);
grid.appendChild(line);
};
/*
* Create a tag in the svg namespace
*/
const createSvgTag = function(tagname, attributes) {
let xmlns="http://www.w3.org/2000/svg";
let newTag = svgNode.ownerDocument.createElementNS(xmlns, tagname);
for (let key in attributes) {
newTag.setAttribute(key, attributes[key]);
}
return newTag;
};
var calculateStartOffset = function(distanceFromOrigin, gridSize) {
let offset;
if (distanceFromOrigin < 0) {
offset = distanceFromOrigin - ( distanceFromOrigin % gridSize );
}
else {
offset = distanceFromOrigin + gridSize - ( distanceFromOrigin % gridSize );
}
return offset;
};
var createGrid = function() {
clearGrid();
let gridSize = 100;
let viewBoxRect = getViewBox();
/* horizontal lines */
let startOffsetY = calculateStartOffset(viewBoxRect.y, gridSize);
let startOffsetX = calculateStartOffset(viewBoxRect.x, gridSize);
for (let i=0; i<(viewBoxRect.height/gridSize); i++) {
let yval = i*gridSize+startOffsetY;
createGridLine(viewBoxRect.x, yval, viewBoxRect.width+viewBoxRect.x, yval);
createLocationMarker(yval+"y", yval.toString(), viewBoxRect.x+10, yval+20);
}
/* vertical lines */
for (let i=0; i<(viewBoxRect.width/gridSize); i++) {
let xval = i*gridSize+startOffsetX;
createGridLine(xval, viewBoxRect.y, xval, viewBoxRect.height+viewBoxRect.y);
createLocationMarker(xval+"x", xval.toString(), xval+10, viewBoxRect.y+20);
}
};
var getViewBox = function() {
let rect = {};
viewBoxString = svgNode.getAttribute("viewBox");
viewBoxElements = viewBoxString.split(" ");
rect.x = parseInt(viewBoxElements[0]);
rect.y = parseInt(viewBoxElements[1]);
rect.width = parseInt(viewBoxElements[2]);
rect.height = parseInt(viewBoxElements[3]);
return rect;
};
var setViewBox = function(rect) {
let viewBoxString = rect.x+" "+rect.y+" "+rect.width+" "+rect.height;
svgNode.setAttribute("viewBox", viewBoxString);
let bg = svgNode.ownerDocument.getElementById("backing");
bg.setAttribute("x", rect.x);
bg.setAttribute("y", rect.y);
bg.setAttribute("width", "100%");
bg.setAttribute("height", "100%");
createGrid();
};
var clearDisplay = function() {
while (svgNode.firstChild) {
svgNode.removeChild(svgNode.firstChild);
}
};
var hideAllLabels = function() {
Array.from(svgNode.getElementsByClassName("svglabel")).forEach(function (label) {
label.setAttributeNS(null, "visibility", "hidden");
});
};
var init = function(svg) {
svgNode = svg;
let rect = { x: "-100", y: "-100", width: "1024", height: "768" };
setViewBox(rect);
};
/*
* Specify the functions that this module exports
*/
return {
init,
pan,
zoomIn,
zoomOut,
};
}());
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
cimview
}
}

253
public/Pintura/js/cimxml.js Normal file
View file

@ -0,0 +1,253 @@
/*
* Copyright © 2016-2017, RWTH Aachen University
* Authors: Richard Marston
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* A copy of the GNU General Public License is in the LICENSE file
* in the top level directory of this source tree.
*/
var cimxml = cimxml || (function() {
var xmlDoc;
var rdfFileCount = 0;
var rdfFileReceived = 0;
var jsonBaseData = null;
const xmlnsString = "xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:cim='http://iec.ch/TC57/2012/CIM-schema-cim16#' xmlns:md='http://iec.ch/TC57/61970-552/ModelDescription/1#' xmlns:entsoe='http://entsoe.eu/Secretariat/ProfileExtension/2#'";
var getRawXML = function() {
return xmlDoc;
};
var getBaseJson = function() {
return jsonBaseData;
};
/*
* Convert a small data item into XML and add it to a node
*/
var addChild = function(object, name, doc, owner) {
let child;
if (typeof object == "object") {
child = doc.createElement(name);
child.setAttribute("rdf:resource", object["rdf:resource"]);
}
else {
child = doc.createElement(name);
child.innerHTML = object;
}
owner.appendChild(child);
};
var copyXmlDataIntoObject = function(object, node) {
let childNodes = node.children;
for (let childIndex = 0; childIndex < childNodes.length; childIndex++) {
let thisChild = childNodes[childIndex];
if (thisChild.nodeType == Node.ELEMENT_NODE) {
if (thisChild.attributes.length > 0) {
object[thisChild.nodeName] = { "rdf:resource": thisChild.getAttribute("rdf:resource")};
}
else {
object[thisChild.nodeName] = thisChild.innerHTML;
}
}
}
};
var importXmlNodeIntoGraph = function(graph, nodeCategory, node, id) {
let thisObject = { };
thisObject['rdfid'] = id
copyXmlDataIntoObject(thisObject, node);
if (!graph[nodeCategory]) {
graph[nodeCategory] = {};
}
/* add the new object to the graph */
let categoryGraph = graph[nodeCategory];
categoryGraph[id] = thisObject;
};
var importAboutDataIntoGraph = function(graph, nodeCategory, thisNode, id) {
if (graph[nodeCategory] && graph[nodeCategory][id]) {
let thisObject = graph[nodeCategory][id].about = [];
copyXmlDataIntoObject(thisObject, thisNode);
}
};
/*
* What is the rdf:ID attribute for this node
*/
var getRdfId = function(node) {
let rdfId = node.getAttribute("rdf:ID");
return rdfId;
};
/*
* What is the rdf:about attribute for this node
*/
var getRdfAbout = function(node) {
let rdfAbout = node.getAttribute("rdf:about");
return rdfAbout;
};
/*
* Clear the buffer of XML data that we use to handle multiple file imports
*/
var clearXmlData = function() {
xmlDoc = null;
};
var xmlns = function(){
return xmlnsString;
};
/*
* Function to create a JSON document from an RDF (XML) DOM.
* RDF is a shallow xml format so we don"t have to dig too
* deep, a node will only ever have children or attributes.
*/
var createObjectGraphFromXml = function( xmlData ) {
let graph = {};
let topLevelNodes = xmlData.documentElement.childNodes;
/* loop through all of the top level nodes */
for (let topLevelIndex=0; topLevelIndex<topLevelNodes.length; topLevelIndex++) {
let thisNode = topLevelNodes[topLevelIndex];
if (thisNode.nodeType == Node.ELEMENT_NODE)
{
/* find out what type of node we are reading */
let nodeCategory = thisNode.nodeName;
let id = getRdfId(thisNode);
if (id) {
importXmlNodeIntoGraph(graph, nodeCategory, thisNode, id);
}
}
}
/* we need all of the rdf:id nodes before importing the rdf:about nodes */
for (let topLevelIndex=0; topLevelIndex<topLevelNodes.length; topLevelIndex++) {
let thisNode = topLevelNodes[topLevelIndex];
if (thisNode.nodeType == Node.ELEMENT_NODE)
{
/* find out what type of node we are reading */
let nodeCategory = thisNode.nodeName;
let id = getRdfAbout(thisNode);
if (id) {
importAboutDataIntoGraph(graph, nodeCategory, thisNode, id.substr(1));
}
}
}
return graph;
};
/*
* Tell this module how many pieces the data will be arriving in
*/
var setRdfFileCount = function(count) {
rdfFileCount = count;
};
/*
* Have we received all the data files yet?
*/
var checkIfParseReady = function() {
if (rdfFileReceived > 0) {
if (rdfFileCount == rdfFileReceived) {
return true;
}
}
};
/*
* Here comes some more data
*/
var moreXmlData = function(text, draw=true) {
if (!xmlDoc) {
xmlDoc = getDOM("<rdf:RDF "+xmlns()+"/>");
}
let newDoc = getDOM(text);
let nodes = newDoc.documentElement.childNodes;
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType == Node.ELEMENT_NODE) {
if (nodes[i].nodeName != "md:FullModel") {
xmlDoc.documentElement.appendChild(nodes[i].cloneNode(true));
}
}
}
rdfFileReceived++;
if (checkIfParseReady()) {
jsonBaseData = createObjectGraphFromXml(xmlDoc);
rdfFileReceived = 0;
rdfFileCount = 0;
return true;
}
return false;
};
/*
* Different method of getting DOM required for some platforms
*/
var getDOM = function(text) {
let newDoc;
if ( window.DOMParser ) {
newDoc = ( new DOMParser() ).parseFromString( text, "application/xml" );
}
else if( window.ActiveXObject ) {
let xmlObject = new ActiveXObject( "Microsoft.XMLDOM" );
xmlObject.async = false;
xmlObject.loadXML( text );
newDoc = xmlObject;
xmlObject = undefined;
}
else {
throw new Error( "Cannot find an XML parser!" );
}
return newDoc;
};
var updateComponentInBaseJson = function(type, id, attribute, value) {
jsonBaseData[type][id][attribute] = value
};
return {
getBaseJson,
setRdfFileCount,
clearXmlData,
moreXmlData,
getRawXML,
updateComponentInBaseJson,
};
}());
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
cimxml
};
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -27,5 +27,12 @@
To begin the development, run `npm start`.
To create a production bundle, use `npm run build`.
-->
<link rel="stylesheet" href="%PUBLIC_URL%/Pintura/css/svg.css"/>
<script type="text/javascript" src="%PUBLIC_URL%/Pintura/js/cimsvg.js"></script>
<script type="text/javascript" src="%PUBLIC_URL%/Pintura/js/cimview.js"></script>
<script type="text/javascript" src="%PUBLIC_URL%/Pintura/js/cimxml.js"></script>
<script type="text/javascript" src="%PUBLIC_URL%/Pintura/js/cimjson.js"></script>
<script type="text/javascript" src="%PUBLIC_URL%/Pintura/js/handlebars.runtime.js"></script>
<script type="text/javascript" src="%PUBLIC_URL%/Pintura/templates/template.js"></script>
</body>
</html>

View file

@ -83,8 +83,10 @@ export default function createControls(widgetType = null, widget = null, session
);
break;
case 'Image':
// Restrict to only image file types (MIME)
let imageControlFiles = files == null? [] : files.filter(file => file.type.includes('image'));
dialogControls.push(
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={files} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={imageControlFiles} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetAspectControl key={1} widget={widget} handleChange={e => handleChange(e)} />
);
break;
@ -141,6 +143,13 @@ export default function createControls(widgetType = null, widget = null, session
<EditWidgetHTMLContent key={0} widget={widget} placeholder='HTML Code' controlId='content' handleChange={e => handleChange(e)} />
);
break;
case 'Topology':
// Restrict to only xml files (MIME)
let topologyControlFiles = files == null? [] : files.filter( file => file.type.includes('xml'));
dialogControls.push(
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={topologyControlFiles} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
);
break;
default:
console.log('Non-valid widget type: ' + widgetType);

View file

@ -58,7 +58,7 @@ class EditWidgetDialog extends React.Component {
// scale width to match aspect
const aspectRatio = file.dimensions.width / file.dimensions.height;
changeObject.width = this.state.temporal.height * aspectRatio;
return changeObject;
}
@ -90,8 +90,10 @@ class EditWidgetDialog extends React.Component {
} else if (e.target.id === 'file') {
changeObject[e.target.id] = e.target.value;
// get file and update size
changeObject = this.assignAspectRatio(changeObject, e.target.value);
// get file and update size (if it's an image)
if ('lockAspect' in this.state.temporal && this.state.temporal.lockAspect) {
changeObject = this.assignAspectRatio(changeObject, e.target.value);
}
} else if (e.target.type === 'checkbox') {
changeObject[e.target.id] = e.target.checked;
} else if (e.target.type === 'number') {

View file

@ -53,13 +53,13 @@ class ToolboxItem extends React.Component {
if (this.props.disabled === false) {
return this.props.connectDragSource(
<span className={itemClass}>
<span className={itemClass} title={this.props.title}>
{this.props.name}
</span>
, {dropEffect});
} else {
return (
<span className={itemClass}>
<span className={itemClass} title={this.props.title}>
{this.props.name}
</span>
);

View file

@ -143,6 +143,10 @@ class WidgetFactory {
case 'HTML':
widget.content = '<i>Hello World</i>';
break;
case 'Topology':
widget.width = 600;
widget.height = 400;
break;
default:
widget.width = 100;

View file

@ -0,0 +1,176 @@
/**
* File: widget-topology.js
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
* Date: 08.01.2018
*
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import {ReactSVGPanZoom} from 'react-svg-pan-zoom';
import config from '../config';
import '../styles/simple-spinner.css';
// Do not show Pintura's grid
const pinturaGridStyle = {
display: 'none'
}
// Avoid another color in the frontend
const pinturaBackingStyle = {
fill: 'transparent'
}
// Center spinner
const spinnerContainerStyle = {
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
// Topology failed message
const msgContainerStyle = Object.assign({
backgroundColor: '#ececec'
},spinnerContainerStyle)
const msgStyle = {
fontWeight: 'bold'
}
// Initialize functions
function attachComponentEvents() {
window.onMouseOver = (e) => show(textSibling(e));
window.onMouseLeave = (e) => hide(textSibling(e));
}
function textSibling(e) {
// Find sibling text element and toggle its visibility
let gParent = e.target.parentElement;
return gParent.getElementsByTagName('text')[0];
}
function show(element) {
element.style.visibility = 'inherit';
}
function hide(element) {
element.style.visibility = 'hidden';
}
// De-initialize functions
function detachComponentEvents() {
window.onMouseOver = null;
window.onMouseLeave = null;
}
class WidgetTopology extends React.Component {
constructor(props) {
super(props);
this.svgElem = null;
this.Viewer = null;
this.state = {
visualizationState: 'initial'
};
}
componentDidMount() {
if (this.svgElem) {
window.cimjson.setImagePathBase(process.env.PUBLIC_URL + '/Pintura/');
window.cimsvg.setSVG(this.svgElem); // function not available in upstream source
window.cimview.init(this.svgElem);
window.onMouseLeave = function() {};
window.onMouseOver = function() {};
window.onMouseLeave = function() {};
window.onMouseUp = function() {};
window.onMouseDown = function() {};
window.onMouseMove = function() {};
}
}
componentWillUnmount() {
detachComponentEvents();
}
componentWillReceiveProps(nextProps) {
const file = nextProps.files.find(file => file._id === nextProps.widget.file);
// Ensure model is requested only once or a different was selected
if (this.props.widget.file !== nextProps.widget.file || (this.state.visualizationState === 'initial' && file)) {
this.setState({'visualizationState': 'loading' });
if (file) {
fetch(new Request('/' + config.publicPathBase + file.path))
.then( response => {
if (response.status === 200) {
this.setState({'visualizationState': 'ready' });
window.cimxml.clearXmlData()
window.cimsvg.setFileCount(1);
return response.text().then( contents => {
window.cimsvg.loadFile(contents);
attachComponentEvents();
});
} else {
throw new Error('Request failed');
}
})
.catch(error => {
this.setState({
'visualizationState': 'show_message',
'message': 'Topology could not be loaded.'});
});
}
} else {
// No file has been selected
if (!nextProps.widget.file) {
this.setState({
'visualizationState': 'show_message',
'message': 'Select a topology model first.'});
}
}
}
render() {
var markup = null;
switch(this.state.visualizationState) {
case 'loading':
markup = <div style={spinnerContainerStyle}><div className="loader" /></div>; break;
case 'show_message':
markup = <div style={msgContainerStyle}><div style={msgStyle}>{ this.state.message }</div></div>; break;
default:
markup = (<div>
<ReactSVGPanZoom
ref={Viewer => this.Viewer = Viewer}
style={{outline: "1px solid black"}}
detectAutoPan={false}
width={this.props.widget.width-2} height={this.props.widget.height-2} >
<svg width={this.props.widget.width} height={this.props.widget.height}>
<svg ref={ c => this.svgElem = c }width={this.props.widget.width} height={this.props.widget.height}>
<rect id="backing" style={pinturaBackingStyle} />
<g id="grid" style={pinturaGridStyle} />
<g id="diagrams"/>
</svg>
</svg>
</ReactSVGPanZoom>
</div>);
}
return markup;
}
}
export default WidgetTopology;

View file

@ -454,6 +454,10 @@ class Visualization extends React.Component {
</Button>
);
// Only one topology widget at the time is supported
let thereIsTopologyWidget = current_widgets && Object.values(current_widgets).filter( widget => widget.type === 'Topology').length > 0;
let topologyItemMsg = !thereIsTopologyWidget? '' : 'Currently only one is supported';
return (
<div className={boxClasses} >
<div className='section-header box-header'>
@ -482,6 +486,7 @@ class Visualization extends React.Component {
<ToolboxItem name="Gauge" type="widget" />
<ToolboxItem name="Box" type="widget" />
<ToolboxItem name="HTML" type="html" />
<ToolboxItem name="Topology" type="widget" disabled={thereIsTopologyWidget} title={topologyItemMsg}/>
<div className="section-buttons-group-right">
{ editingControls }

View file

@ -43,6 +43,7 @@ import WidgetSlider from '../components/widget-slider';
import WidgetGauge from '../components/widget-gauge';
import WidgetBox from '../components/widget-box';
import WidgetHTML from '../components/widget-html';
import WidgetTopology from '../components/widget-topology';
import '../styles/widgets.css';
@ -188,6 +189,8 @@ class Widget extends React.Component {
element = <WidgetBox widget={widget} editing={this.props.editing} />
} else if (widget.type === 'HTML') {
element = <WidgetHTML widget={widget} editing={this.props.editing} />
} else if (widget.type === 'Topology') {
element = <WidgetTopology widget={widget} files={this.state.files} />
}
const widgetClasses = classNames({

View file

@ -0,0 +1,19 @@
/*
Basic spinner animation
taken from: https://www.w3schools.com/howto/howto_css_loader.asp
*/
.loader {
border: 16px solid #f3f3f3;
border-top: 16px solid #6ea2b0;
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}