|
@@ -3,33 +3,36 @@ const EXPECTED_BACKEND_VERSION = 3; // expected backend version
|
|
|
const SPARQL_SERVER = "http://localhost:3030"
|
|
|
const SPARQL_ENDPOINT = "/SystemDesignOntology2Layers/sparql"
|
|
|
|
|
|
-const dropVocabularyPrefix = str => str.substring(30);
|
|
|
+const dropVocabularyPrefix = str => str.substring(41);
|
|
|
const dropDescriptionPrefix = str => str.substring(30);
|
|
|
+const dropArtifactPrefix = str => str.substring(41);
|
|
|
|
|
|
-// Query that navigates the given link from the given source element, and returns the IRI and most-concrete-type of the target element.
|
|
|
-const getOutgoingLink = (iri, link_type) => `\
|
|
|
+const QUERIES = {
|
|
|
+ // Query that navigates the given link from the given source element, and returns the IRI and most-concrete-type of the target element.
|
|
|
+ // If `reverse` is true, then incoming links are returned instead.
|
|
|
+ getOutgoingLink: (iri, link_type, reverse=false) => `\
|
|
|
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 .
|
|
|
+ ${reverse ? `?element <${link_type}> <${iri}>` : `<${iri}> <${link_type}> ?element`}.
|
|
|
?element a ?type .
|
|
|
?type rdfs:subClassOf object_diagram:Object .
|
|
|
- NOT EXISTS {
|
|
|
+ FILTER(NOT EXISTS {
|
|
|
?more_concrete_type rdfs:subClassOf ?type .
|
|
|
?element a ?more_concrete_type .
|
|
|
FILTER(?more_concrete_type != ?type)
|
|
|
- }
|
|
|
- NOT EXISTS {
|
|
|
- ?more_concrete_link_type rdfs:subClassOf <${link_type}> .
|
|
|
- <${iri}> a ?more_concrete_link_type .
|
|
|
+ })
|
|
|
+ FILTER(NOT EXISTS {
|
|
|
+ ?more_concrete_link_type rdfs:subPropertyOf <${link_type}> .
|
|
|
+ ${reverse ? `?element ?more_concrete_link_type <${iri}>` : `<${iri}> ?more_concrete_link_type ?element`} .
|
|
|
FILTER(?more_concrete_link_type != <${link_type}>)
|
|
|
- }
|
|
|
-}`;
|
|
|
+ })
|
|
|
+}`,
|
|
|
|
|
|
-// Query that gets the IRI and most-concrete-type of a given drawio cell.
|
|
|
-const getCellStuff = (cellId) => `\
|
|
|
+ // Query that gets the IRI and most-concrete-type of a given drawio cell.
|
|
|
+ getCellStuff: (cellId) => `\
|
|
|
PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
|
|
|
PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
|
|
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
@@ -44,10 +47,10 @@ WHERE {
|
|
|
?element a ?more_concrete_type .
|
|
|
FILTER(?more_concrete_type != ?type)
|
|
|
}
|
|
|
-}`;
|
|
|
+}`,
|
|
|
|
|
|
-// Query that, for a given cell IRI, gets the name of the diagram containing the cell, and the ID of the cell.
|
|
|
-const getDiagramAndCellId = cellIri => `\
|
|
|
+ // Query that, for a given cell IRI, gets the name of the diagram containing the cell, and the ID of the cell.
|
|
|
+ getDiagramAndCellId: cellIri => `\
|
|
|
PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
|
|
|
PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
|
|
|
|
|
@@ -56,69 +59,88 @@ WHERE {
|
|
|
<${cellIri}> drawio:hasDrawioId ?cellId .
|
|
|
<${cellIri}> object_diagram:inModel ?model .
|
|
|
?model object_diagram:hasName ?diagramName .
|
|
|
-}`;
|
|
|
+}`,
|
|
|
|
|
|
-// Query that gets ALL subclass relations.
|
|
|
-const getSubClassRelations = `
|
|
|
+ // Query that gets ALL subclass relations.
|
|
|
+ getSubClassRelations: `
|
|
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
|
|
|
|
SELECT DISTINCT ?subclass ?superclass
|
|
|
WHERE {
|
|
|
?subclass rdfs:subClassOf ?superclass
|
|
|
-}`;
|
|
|
+}`,
|
|
|
+};
|
|
|
|
|
|
// (Hardcoded) link types exposed to the user.
|
|
|
const typeToLinkType = new Map([
|
|
|
- ["formalisms/object_diagram#Object", [
|
|
|
+ ["object_diagram#Object", [
|
|
|
{
|
|
|
- query: "formalisms/cs_as#parsedAs",
|
|
|
- name: "Is parsed as",
|
|
|
+ relation: "cs_as#parsedAs",
|
|
|
+ description: (element, type) => `Is parsed as ${type}`,
|
|
|
},
|
|
|
{
|
|
|
- query: "formalisms/cs_as#renderedAs",
|
|
|
- name: "Is rendered as",
|
|
|
+ relation: "cs_as#renderedAs",
|
|
|
+ description: (element, type) => `Is rendered as ${type}`,
|
|
|
},
|
|
|
+ // Outcommented, because it's not an interesting relation:
|
|
|
+ // {
|
|
|
+ // relation: "object_diagram#inModel",
|
|
|
+ // description: (element, type) => `Is part of model ${type}`,
|
|
|
+ // },
|
|
|
{
|
|
|
- query: "formalisms/object_diagram#inModel",
|
|
|
- name: "Is part of model",
|
|
|
+ relation: "traceability_model#traceLinkTo",
|
|
|
+ description: (element, type) => `Trace-link (outgoing) ${element}`,
|
|
|
},
|
|
|
{
|
|
|
- query: "formalisms/traceability_model#traceLinkTo",
|
|
|
- name: "Trace-link (outgoing)",
|
|
|
+ relation: "traceability_model#traceLinkFrom",
|
|
|
+ description: (element, type) => `Trace-link (incoming) ${element}`,
|
|
|
},
|
|
|
+ ]],
|
|
|
+ ["drawio#Model", [
|
|
|
{
|
|
|
- query: "formalisms/traceability_model#traceLinkFrom",
|
|
|
- name: "Trace-link (incoming)",
|
|
|
+ relation: "drawio#hasRootCell",
|
|
|
+ description: (element, type) => `Has root ${type}`,
|
|
|
},
|
|
|
]],
|
|
|
- ["formalisms/drawio#Model", [
|
|
|
+ ["pm#Activity", [
|
|
|
+ {
|
|
|
+ relation: "pm#isTransformation",
|
|
|
+ description: (element, type) => `Is typed by ${type}`,
|
|
|
+ },
|
|
|
{
|
|
|
- query: "formalisms/drawio#hasRootCell",
|
|
|
- name: "Has root",
|
|
|
+ relation: "pm#hasPort",
|
|
|
+ description: (element, type) => `Has port ${type}`,
|
|
|
},
|
|
|
]],
|
|
|
- ["formalisms/pm#Activity", [
|
|
|
+ ["pm#element", [
|
|
|
{
|
|
|
- query: "formalisms/pm#isTransformation",
|
|
|
- name: "Is typed by transformation",
|
|
|
+ relation: "processtraces#relatesTo",
|
|
|
+ description: (element, type) => `Enacted by ${element}`,
|
|
|
+ reverse: true,
|
|
|
},
|
|
|
]],
|
|
|
- ["formalisms/pm#Artifact", [
|
|
|
+ ["processtraces#element", [
|
|
|
{
|
|
|
- query: "formalisms/pm#hasType",
|
|
|
- name: "Is typed by formalism",
|
|
|
+ relation: "processtraces#relatesTo",
|
|
|
+ description: (element, type) => `Enactment of ${element}`,
|
|
|
},
|
|
|
]],
|
|
|
- ["formalisms/ftg#Transformation", [
|
|
|
+ ["pm#Artifact", [
|
|
|
{
|
|
|
- query: "formalisms/pm#occursAsActivity",
|
|
|
- name: "Occurs as activity",
|
|
|
+ relation: "pm#hasType",
|
|
|
+ description: (element, type) => `Is typed by ${type}`,
|
|
|
+ },
|
|
|
+ ]],
|
|
|
+ ["ftg#Transformation", [
|
|
|
+ {
|
|
|
+ relation: "pm#occursAsActivity",
|
|
|
+ description: (element, type) => `Occurs as activity ${element}`,
|
|
|
}
|
|
|
]],
|
|
|
- ["formalisms/ftg#Formalism", [
|
|
|
+ ["ftg#Formalism", [
|
|
|
{
|
|
|
- query: "formalisms/pm#occursAsArtifact",
|
|
|
- name: "Occurs as artifact",
|
|
|
+ relation: "pm#occursAsArtifact",
|
|
|
+ description: (element, type) => `Occurs as artifact ${element}`,
|
|
|
},
|
|
|
]],
|
|
|
]);
|
|
@@ -130,7 +152,7 @@ function getQueries(type) {
|
|
|
const result = [].concat(
|
|
|
typeToLinkType.get(dropVocabularyPrefix(type)) || [],
|
|
|
... (superclasses.get(type) || []).map(supertype => typeToLinkType.get(dropVocabularyPrefix(supertype)) || []));
|
|
|
- // console.log("getQueries,type=",type,"superclasses=",superclasses.get(type),"result=",result);
|
|
|
+ console.log("getQueries,type=",type,"superclasses=",superclasses.get(type),"result=",result);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -174,7 +196,7 @@ fetch(BACKEND+"/version")
|
|
|
|
|
|
// Loads all 'rdfs:subClassOf' relations and stores them in a mapping.
|
|
|
function loadSuperclasses() {
|
|
|
- return executeSPARQL(getSubClassRelations)
|
|
|
+ return executeSPARQL(QUERIES.getSubClassRelations)
|
|
|
.then(results => {
|
|
|
for (const {subclass, superclass} of results) {
|
|
|
const superclasslist = superclasses.get(subclass.value) || (() => {
|
|
@@ -189,6 +211,60 @@ fetch(BACKEND+"/version")
|
|
|
|
|
|
loadSuperclasses();
|
|
|
|
|
|
+ // Override context menu when right-clicking somewhere in the diagram:
|
|
|
+ const oldFactoryMethod = ui.editor.graph.popupMenuHandler.factoryMethod;
|
|
|
+ ui.editor.graph.popupMenuHandler.factoryMethod = function(menu, cell, evt) {
|
|
|
+ const modelverseMenu = menu.addItem("ModelVerse", null, null);
|
|
|
+ menu.addSeparator();
|
|
|
+ oldFactoryMethod.apply(this, arguments);
|
|
|
+
|
|
|
+ const alreadyVisited = new Set();
|
|
|
+ const entry = (element, type) => element+':'+type;
|
|
|
+
|
|
|
+ function addMenuItem(element, type, maxRecursion, parentMenuItem, description) {
|
|
|
+ if (alreadyVisited.has(entry(element, type))) {
|
|
|
+ return; // don't go in circles
|
|
|
+ }
|
|
|
+ const shortType = dropVocabularyPrefix(type);
|
|
|
+ const menuItemText = description(dropArtifactPrefix(element), shortType);
|
|
|
+ let createdMenuItem;
|
|
|
+ // If the menu item is a (subtype of a) drawio cell, then clicking on it will take us to that cell in drawio.
|
|
|
+ if ((superclasses.get(type) || []).some(t => t.endsWith("drawio#Cell"))) {
|
|
|
+ createdMenuItem = menu.addItem(menuItemText, null, () => goto(element), parentMenuItem);
|
|
|
+ } else {
|
|
|
+ createdMenuItem = menu.addItem(menuItemText, null, null, parentMenuItem);
|
|
|
+ }
|
|
|
+ alreadyVisited.add(entry(element, type));
|
|
|
+ if (maxRecursion <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ addSubmenuItems(element, type, createdMenuItem, maxRecursion);
|
|
|
+ }
|
|
|
+ function addSubmenuItems(element, type, parentMenuItem, maxRecursion) {
|
|
|
+ const queries = getQueries(type);
|
|
|
+ for (const {relation, description, reverse} of queries) {
|
|
|
+ executeSPARQL(QUERIES.getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/formalisms/"+relation, reverse))
|
|
|
+ .then(results => {
|
|
|
+ for (const {element, type} of results) {
|
|
|
+ addMenuItem(element.value, type.value, maxRecursion-1, parentMenuItem, description);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Populate ModelVerse submenu asynchronously:
|
|
|
+ if (cell && cell.vertex) {
|
|
|
+ executeSPARQL(QUERIES.getCellStuff(cell.id))
|
|
|
+ .then(results => {
|
|
|
+ for (const {element, type} of results) {
|
|
|
+ alreadyVisited.add(entry(element.value, type.value));
|
|
|
+ addSubmenuItems(element.value, type.value, modelverseMenu, 4);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
function getSetting(prop) {
|
|
|
const result = localStorage.getItem("dtdesign-"+prop);
|
|
|
if (result !== null) {
|
|
@@ -291,7 +367,7 @@ fetch(BACKEND+"/version")
|
|
|
|
|
|
// Given a cell IRI, open the diagram that contains the cell
|
|
|
function goto(cellIri) {
|
|
|
- executeSPARQL(getDiagramAndCellId(cellIri))
|
|
|
+ executeSPARQL(QUERIES.getDiagramAndCellId(cellIri))
|
|
|
.then(([{diagramName, cellId}]) => {
|
|
|
// drop the "_drawio" at the end:
|
|
|
const actualDiagramName = diagramName.value.substring(0, diagramName.value.length-7);
|
|
@@ -307,50 +383,6 @@ fetch(BACKEND+"/version")
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- // Override context menu when right-clicking somewhere in the diagram:
|
|
|
- const oldFactoryMethod = ui.editor.graph.popupMenuHandler.factoryMethod;
|
|
|
- ui.editor.graph.popupMenuHandler.factoryMethod = function(menu, cell, evt) {
|
|
|
- // We recursively construct a submenu where the user can navigate the onthology:
|
|
|
- const modelverseMenu = menu.addItem("ModelVerse", null, null);
|
|
|
- oldFactoryMethod.apply(this, arguments);
|
|
|
- if (cell && cell.vertex) {
|
|
|
- const alreadyVisited = new Set();
|
|
|
- function buildMenu(element, type, maxRecursion, parentMenuItem, relName) {
|
|
|
- if (alreadyVisited.has(element+'---'+type)) {
|
|
|
- return; // don't go in circles
|
|
|
- }
|
|
|
- const shortType = dropVocabularyPrefix(type);
|
|
|
- let createdMenuItem;
|
|
|
- // If the menu item is a (subtype of a) drawio cell, then clicking on it will take us to that cell in drawio.
|
|
|
- if ((superclasses.get(type) || []).some(t => t.endsWith("drawio#Cell"))) {
|
|
|
- createdMenuItem = menu.addItem(relName + ' ' + shortType, null, () => goto(element), parentMenuItem);
|
|
|
- } else {
|
|
|
- createdMenuItem = menu.addItem(relName + ' ' + shortType, null, null, parentMenuItem);
|
|
|
- }
|
|
|
- alreadyVisited.add(element+'---'+type);
|
|
|
- if (maxRecursion <= 0) {
|
|
|
- return;
|
|
|
- }
|
|
|
- const queries = getQueries(type);
|
|
|
- for (const {query, name} of queries) {
|
|
|
- // console.log("RemDepth=",maxRecursion,"element=",element,"queries=",queries);
|
|
|
- executeSPARQL(getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/"+query))
|
|
|
- .then(results => {
|
|
|
- for (const {element, type} of results) {
|
|
|
- buildMenu(element.value, type.value, maxRecursion-1, createdMenuItem, name);
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- executeSPARQL(getCellStuff(cell.id))
|
|
|
- .then(results => {
|
|
|
- for (const {element, type} of results) {
|
|
|
- buildMenu(element.value, type.value, 10, modelverseMenu, "Is a");
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
const wndDiv = document.createElement('div');
|
|
|
wndDiv.style.userSelect = 'none';
|
|
|
wndDiv.style.height = '100%';
|