Explorar el Código

WIP: DTDesign plugin

Joeri Exelmans hace 2 años
padre
commit
84a9da3cc7
Se han modificado 1 ficheros con 276 adiciones y 55 borrados
  1. 276 55
      src/main/webapp/myPlugins/dtdesign.js

+ 276 - 55
src/main/webapp/myPlugins/dtdesign.js

@@ -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);