|
@@ -1,62 +1,278 @@
|
|
|
const BACKEND = "http://localhost:5000";
|
|
|
+const SPARQL_SERVER = "http://localhost:3030"
|
|
|
+const SPARQL_ENDPOINT = "/SystemDesignOntology2Layers/sparql"
|
|
|
+
|
|
|
+const dropVocabularyPrefix = str => str.substring(30);
|
|
|
+const dropDescriptionPrefix = str => str.substring(30);
|
|
|
+
|
|
|
+const getCellIRI = cellId => `\
|
|
|
+PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
|
|
|
+
|
|
|
+SELECT DISTINCT ?cell
|
|
|
+WHERE {
|
|
|
+ ?cell drawio:hasDrawioId "${cellId}" .
|
|
|
+
|
|
|
+}`;
|
|
|
+
|
|
|
+
|
|
|
+const whatIsCell = cellId => `\
|
|
|
+PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
|
|
|
+PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
|
|
|
+PREFIX cs_as: <http://ua.be/sdo2l/vocabulary/formalisms/cs_as#>
|
|
|
+prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
|
+prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
|
+PREFIX oml: <http://opencaesar.io/oml#>
|
|
|
+PREFIX purl: <http://purl.org/dc/elements/1.1/>
|
|
|
+PREFIX trac: <http://ua.be/sdo2l/vocabulary/formalisms/traceability_model#>
|
|
|
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
|
+
|
|
|
+SELECT DISTINCT ?cell ?rel_type ?as_element ?as_type
|
|
|
+WHERE {
|
|
|
+ ?cell drawio:hasDrawioId "${cellId}" .
|
|
|
+
|
|
|
+ # Get all incoming and outgoing traceability links for "?cell":
|
|
|
+ {
|
|
|
+ ?rel a trac:TraceabilityLink .
|
|
|
+ ?rel oml:hasSource ?cell .
|
|
|
+ ?rel oml:hasTarget ?as_element .
|
|
|
+ } UNION {
|
|
|
+ ?rel a trac:TraceabilityLink .
|
|
|
+ ?rel oml:hasTarget ?cell .
|
|
|
+ ?rel oml:hasSource ?as_element .
|
|
|
+ }
|
|
|
+
|
|
|
+ ?rel a ?rel_type .
|
|
|
+ ?rel_type rdfs:subClassOf trac:TraceabilityLink .
|
|
|
+
|
|
|
+ # Only interested here in the most concrete type of the traceability link:
|
|
|
+ NOT EXISTS {
|
|
|
+ ?more_concrete_type rdfs:subClassOf ?rel_type .
|
|
|
+ ?rel a ?more_concrete_type .
|
|
|
+ FILTER(?more_concrete_type != ?rel_type) # needed because every type is its own subclass
|
|
|
+ }
|
|
|
+
|
|
|
+ ?as_element a ?as_type .
|
|
|
+ ?as_type rdfs:subClassOf object_diagram:Object . # restrict types to objects
|
|
|
+
|
|
|
+ # Only get most concrete type:
|
|
|
+ NOT EXISTS {
|
|
|
+ ?another_more_concrete_type rdfs:subClassOf ?as_type .
|
|
|
+ ?as_element a ?another_more_concrete_type
|
|
|
+ FILTER(?another_more_concrete_type != ?as_type)
|
|
|
+ }
|
|
|
+}
|
|
|
+`;
|
|
|
+
|
|
|
+const getCellIri = cellId => `\
|
|
|
+PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
|
|
|
+
|
|
|
+SELECT DISTINCT ?cell ?rel_type ?as_element ?as_type
|
|
|
+WHERE {
|
|
|
+ ?cell drawio:hasDrawioId "${cellId}" .
|
|
|
+ ?element a ?type .
|
|
|
+ ?type rdfs:subClassOf object_diagram:Object .
|
|
|
+ NOT EXISTS {
|
|
|
+ ?more_concrete_type rdfs:subClassOf ?type .
|
|
|
+ ?element a ?more_concrete_type .
|
|
|
+ FILTER(?more_concrete_type != ?type)
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
+const getAllOutgoingRelations = iri => `\
|
|
|
+SELECT DISTINCT ?rel ?other
|
|
|
+WHERE {
|
|
|
+ <${iri}> ?rel ?other .
|
|
|
+}
|
|
|
+`;
|
|
|
+
|
|
|
+const getOutgoingLink = (iri, link_type) => `\
|
|
|
+PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
|
|
|
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
|
+
|
|
|
+SELECT DISTINCT ?element ?type
|
|
|
+WHERE {
|
|
|
+ <${iri}> <${link_type}> ?element .
|
|
|
+ ?element a ?type .
|
|
|
+ ?type rdfs:subClassOf object_diagram:Object .
|
|
|
+ NOT EXISTS {
|
|
|
+ ?more_concrete_type rdfs:subClassOf ?type .
|
|
|
+ ?element a ?more_concrete_type .
|
|
|
+ FILTER(?more_concrete_type != ?type)
|
|
|
+ }
|
|
|
+}
|
|
|
+`;
|
|
|
+
|
|
|
+function getQueries(element, type) {
|
|
|
+ if (type.endsWith("formalisms/drawio#Cell")) {
|
|
|
+ return [
|
|
|
+ getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/formalisms/cs_as#parsedAs"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ if (type.endsWith("formalisms/pm#Activity")) {
|
|
|
+ return [
|
|
|
+ getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/formalisms/pm#isTransformation"),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function executeSPARQL(query) {
|
|
|
+ const body = new URLSearchParams();
|
|
|
+ body.append("query", query);
|
|
|
+ return fetch(SPARQL_SERVER+SPARQL_ENDPOINT, {
|
|
|
+ headers: new Headers({"Content-Type": "application/x-www-form-urlencoded"}),
|
|
|
+ method: "POST",
|
|
|
+ body,
|
|
|
+ })
|
|
|
+ .then(res => res.json())
|
|
|
+ .then(json => json.results.bindings)
|
|
|
+}
|
|
|
|
|
|
Draw.loadPlugin(function(ui) {
|
|
|
|
|
|
- // shortcut Ctrl+Space will "commit"/"save" all pages as models in backend:
|
|
|
- window.onkeydown = function(e) {
|
|
|
- if (e.ctrlKey && e.key === ' ') {
|
|
|
- const headers = new Headers({
|
|
|
- "Content-Type": "application/xml",
|
|
|
- });
|
|
|
- const serializer = new XMLSerializer();
|
|
|
- const xmlnode = ui.getXmlFileData(
|
|
|
- null, // ignore selection
|
|
|
- false, // only the current page
|
|
|
- true); // uncompressed
|
|
|
- console.log("committing all pages...");
|
|
|
- Promise.all(Array.from(xmlnode.children).map(diagram => {
|
|
|
- const diagramName = diagram.getAttribute("name");
|
|
|
- const xml = serializer.serializeToString(diagram);
|
|
|
- return fetch(BACKEND + "/diagrams/"+diagramName, {
|
|
|
- method: "PUT",
|
|
|
- headers,
|
|
|
- body: xml,
|
|
|
- })
|
|
|
- .then(res => {
|
|
|
- if (res.status >= 200 && res.status < 300) {
|
|
|
- res.json().then(parsedAs => {
|
|
|
- console.log(diagramName, "-> OML success", JSON.stringify(parsedAs));
|
|
|
- });
|
|
|
- return true;
|
|
|
+ const omlErrDiv = document.createElement('div');
|
|
|
+ // wndDiv.style.color = "red";
|
|
|
+ omlErrDiv.style.overflow = "auto";
|
|
|
+ omlErrDiv.style.height = "100%";
|
|
|
+ const omlErrWnd = new mxWindow("OML Build Error",
|
|
|
+ omlErrDiv, 300, 300, 300, 360, true, true);
|
|
|
+ omlErrWnd.destroyOnClose = false;
|
|
|
+ omlErrWnd.setMaximizable(false);
|
|
|
+ omlErrWnd.setResizable(true);
|
|
|
+ omlErrWnd.setClosable(true);
|
|
|
+
|
|
|
+ let rebuildOMLPromise = Promise.resolve();
|
|
|
+
|
|
|
+ function rebuildOML() {
|
|
|
+ // only perform one rebuild at a time -> queue rebuilds.
|
|
|
+ rebuildOMLPromise = rebuildOMLPromise.then(() => {
|
|
|
+ omlStatusDiv.style.color = null;
|
|
|
+ omlStatusDiv.innerText = "Rebuilding OML...";
|
|
|
+ console.log("rebuilding OML... this may take a while...");
|
|
|
+ return fetch(BACKEND + "/rebuild_oml", {
|
|
|
+ method: "PUT",
|
|
|
+ }).then(res => {
|
|
|
+ res.text().then(text => {
|
|
|
+ if (text.length > 0) {
|
|
|
+ omlErrDiv.innerText = text;
|
|
|
}
|
|
|
else {
|
|
|
- console.log(diagramName, res.status, res.statusText);
|
|
|
- return false;
|
|
|
+ omlErrDiv.innerText = "No error."
|
|
|
}
|
|
|
});
|
|
|
- })).then(ok => {
|
|
|
- if (ok.filter(ok => ok === true).length >= 1) {
|
|
|
- // At least one diagram successfully committed - refresh list of diagrams (fast)
|
|
|
- refreshModels();
|
|
|
- // Rebuild OML (very slow!)
|
|
|
- console.log("rebuilding OML...");
|
|
|
- fetch(BACKEND + "/rebuild_oml", {
|
|
|
- method: "PUT",
|
|
|
- }).then(res => {
|
|
|
- console.log("rebuild OML", res.status, res.statusText);
|
|
|
+ if (res.status >= 500) {
|
|
|
+ omlStatusDiv.innerHTML = "Failed to build OML ";
|
|
|
+ omlStatusDiv.style.color = "red";
|
|
|
+ const anchor = document.createElement('a');
|
|
|
+ anchor.innerText = "Details";
|
|
|
+ anchor.href = "#";
|
|
|
+ anchor.onclick = function() {
|
|
|
+ omlErrWnd.show();
|
|
|
+ };
|
|
|
+ omlStatusDiv.appendChild(anchor);
|
|
|
+ } else {
|
|
|
+ omlStatusDiv.innerText = "Ready";
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function savePages(onlyCurrentPage) {
|
|
|
+ const headers = new Headers({
|
|
|
+ "Content-Type": "application/xml",
|
|
|
+ });
|
|
|
+ const serializer = new XMLSerializer();
|
|
|
+ const xmlnode = ui.getXmlFileData(
|
|
|
+ null, // ignore selection
|
|
|
+ onlyCurrentPage,
|
|
|
+ true); // uncompressed
|
|
|
+ // console.log("committing pages...");
|
|
|
+ Promise.all(Array.from(xmlnode.children).map(diagram => {
|
|
|
+ const diagramName = diagram.getAttribute("name");
|
|
|
+ const xml = serializer.serializeToString(diagram);
|
|
|
+ return fetch(BACKEND + "/diagrams/"+diagramName, {
|
|
|
+ method: "PUT",
|
|
|
+ headers,
|
|
|
+ body: xml,
|
|
|
+ })
|
|
|
+ .then(res => {
|
|
|
+ if (res.status >= 200 && res.status < 300) {
|
|
|
+ res.json().then(parsedAs => {
|
|
|
+ console.log(diagramName, "-> OML success", JSON.stringify(parsedAs));
|
|
|
});
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ console.log(diagramName, res.status, res.statusText);
|
|
|
+ return false;
|
|
|
}
|
|
|
});
|
|
|
+ })).then(ok => {
|
|
|
+ if (ok.filter(ok => ok === true).length >= 1) {
|
|
|
+ // At least one diagram successfully committed - refresh list of diagrams (fast)
|
|
|
+ refreshModels();
|
|
|
+ rebuildOML();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ ui.actions.addAction('whatIsThis', mxUtils.bind(this, function() {}));
|
|
|
+
|
|
|
+ // // Add context menu item
|
|
|
+ // const oldAddPopupMenuItems = ui.menus.addPopupMenuEditItems;
|
|
|
+ // ui.menus.addPopupMenuEditItems = function(menu, cell, evt) {
|
|
|
+ // this.addMenuItems(menu, ['whatIsThis'], null, evt);
|
|
|
+ // oldAddPopupMenuItems.call(this, menu, cell, evt);
|
|
|
+ // console.log("CONTEXT MENU")
|
|
|
+ // }
|
|
|
+
|
|
|
+ const oldFactoryMethod = ui.editor.graph.popupMenuHandler.factoryMethod;
|
|
|
+ ui.editor.graph.popupMenuHandler.factoryMethod = function(menu, cell, evt) {
|
|
|
+ const modelverseMenu = menu.addItem("ModelVerse", null, null);
|
|
|
+ const isAMenu = menu.addItem("Is a...", null, null, modelverseMenu);
|
|
|
+ menu.addSeparator();
|
|
|
+ oldFactoryMethod.apply(this, arguments);
|
|
|
+ if (cell && cell.vertex) {
|
|
|
+ // menu.addItem(`Draw.io cell "${cell.id}"`, null, function(evt) {}, modelverseMenu, null, false);
|
|
|
+ executeSPARQL(whatIsCell(cell.id))
|
|
|
+ .then(json => {
|
|
|
+ for (const {cell, rel_type, as_type, as_element} of json) {
|
|
|
+ const tracelink_type = rel_type.value.split("#")[1];
|
|
|
+ const typeMenu = menu.addItem(dropVocabularyPrefix(as_type.value) + " - " + tracelink_type, null, null, isAMenu);
|
|
|
+
|
|
|
+ const queries = getQueries(as_element.value, as_type.value);
|
|
|
+ Promise.all(queries.map(q => {
|
|
|
+ executeSPARQL(q)
|
|
|
+ .then(json => {
|
|
|
+ console.log("query", q, "json:", json);
|
|
|
+ });
|
|
|
+ }))
|
|
|
+ // executeSPARQL(getAllOutgoingRelations(as_element.value))
|
|
|
+ // .then(json => {
|
|
|
+ // for (const {rel, other} of json) {
|
|
|
+ // menu.addItem(rel.value+" "+other.value, null, null, isAMenu);
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ // for (const [key,val] of Object.entries(el)) {
|
|
|
+ // menu.addItem(key + ": " + val.value, null, null, modelverseMenu, null, false);
|
|
|
+ // }
|
|
|
+ // const [as_element, as_type, cell, rel_type] = json[0];
|
|
|
+
|
|
|
+ }
|
|
|
+ console.log(json);
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
const wndDiv = document.createElement('div');
|
|
|
wndDiv.style.userSelect = 'none';
|
|
|
- // wndDiv.style.overflow = 'auto';
|
|
|
- // wndDiv.style.padding = '10px';
|
|
|
wndDiv.style.height = '100%';
|
|
|
wndDiv.style.color = "rgb(112, 112, 112)";
|
|
|
- // wndDiv.classList.add("geFormatSelection");
|
|
|
+ wndDiv.style.overflow = 'auto';
|
|
|
wndDiv.classList.add("geFormatContainer");
|
|
|
|
|
|
const saveDiv = document.createElement('div');
|
|
@@ -69,30 +285,35 @@ Draw.loadPlugin(function(ui) {
|
|
|
lsDiv.style.padding = '12px 0px 8px 14px';
|
|
|
wndDiv.appendChild(lsDiv);
|
|
|
|
|
|
- const saveLabel = document.createElement('div');
|
|
|
- saveLabel.innerText = "Save";
|
|
|
- saveLabel.style.fontWeight = 'bold';
|
|
|
- saveDiv.appendChild(saveLabel);
|
|
|
+ // const saveLabel = document.createElement('div');
|
|
|
+ // saveLabel.innerText = "Save";
|
|
|
+ // saveLabel.style.fontWeight = 'bold';
|
|
|
+ // saveDiv.appendChild(saveLabel);
|
|
|
|
|
|
- const saveButton = document.createElement('button');
|
|
|
- saveButton.innerText = "Save current page";
|
|
|
+ const saveButton = mxUtils.button("Save current page", function() {
|
|
|
+ savePages(true);
|
|
|
+ });
|
|
|
saveButton.style.width = "210px";
|
|
|
saveButton.style.marginBottom = "2px";
|
|
|
saveDiv.appendChild(saveButton);
|
|
|
|
|
|
- const saveAllButton = document.createElement('button');
|
|
|
- saveAllButton.innerText = "Save all pages";
|
|
|
+ const saveAllButton = mxUtils.button("Save all pages", function() {
|
|
|
+ savePages(false);
|
|
|
+ });
|
|
|
saveAllButton.style.width = "210px";
|
|
|
saveAllButton.style.marginBottom = "2px";
|
|
|
saveDiv.appendChild(saveAllButton);
|
|
|
|
|
|
+ const omlStatusDiv = document.createElement('div');
|
|
|
+ omlStatusDiv.innerText = "Ready";
|
|
|
+ saveDiv.appendChild(omlStatusDiv);
|
|
|
+
|
|
|
const modelsLabel = document.createElement('div');
|
|
|
modelsLabel.innerText = "Models";
|
|
|
modelsLabel.style.fontWeight = 'bold';
|
|
|
lsDiv.appendChild(modelsLabel);
|
|
|
|
|
|
const modelsDiv = document.createElement('div');
|
|
|
- modelsDiv.style.overflow = 'auto';
|
|
|
lsDiv.appendChild(modelsDiv);
|
|
|
|
|
|
function refreshModels() {
|
|
@@ -104,17 +325,17 @@ Draw.loadPlugin(function(ui) {
|
|
|
modelsDiv.replaceChildren(...json.map(modelName => {
|
|
|
const div = document.createElement('div');
|
|
|
div.style.padding = '3px 0px';
|
|
|
- // const loadButton = document.createElement('button');
|
|
|
- // loadButton.innerText = "Load";
|
|
|
- const loadButton = mxUtils.button("Load", function() {
|
|
|
+ const loadButton = mxUtils.button("Open in New Page", function() {
|
|
|
fetch(BACKEND + "/diagrams/" + modelName)
|
|
|
.then(res => res.text())
|
|
|
.then(xmltext => {
|
|
|
- console.log(xmltext);
|
|
|
+ // console.log(xmltext);
|
|
|
const parser = new DOMParser();
|
|
|
const doc = parser.parseFromString(xmltext, "application/xml");
|
|
|
const node = doc.documentElement;
|
|
|
const page = new DiagramPage(node);
|
|
|
+ // if page with same name already exists, erase it:
|
|
|
+ ui.pages = ui.pages.filter(page => page.node.getAttribute("name") != modelName);
|
|
|
ui.pages.push(page);
|
|
|
ui.currentPage = page;
|
|
|
ui.editor.setGraphXml(node);
|
|
@@ -131,7 +352,7 @@ Draw.loadPlugin(function(ui) {
|
|
|
refreshModels();
|
|
|
|
|
|
const wnd = new mxWindow("ModelVerse",
|
|
|
- wndDiv, 100, 100, 240, 400, true, true);
|
|
|
+ wndDiv, 900, 20, 240, 400, true, true);
|
|
|
wnd.destroyOnClose = false;
|
|
|
wnd.setMaximizable(false);
|
|
|
wnd.setResizable(false);
|