From 356cd489da941f1d041eb498d8252817da2ef1f9 Mon Sep 17 00:00:00 2001 From: Ralf Aron Date: Mon, 8 Apr 2024 12:13:57 +0200 Subject: [PATCH 1/2] feat: Hierarchical structure submodel template --- aasportal.mdx | 110 +++++------ .../src/lib/aas-table/aas-table.component.ts | 17 +- .../src/lib/aas-tree/aas-tree-search.ts | 47 +++-- .../src/lib/aas-tree/aas-tree.component.html | 49 ++--- .../src/lib/aas-tree/aas-tree.component.ts | 128 ++++++++----- .../src/lib/aas-tree/aas-tree.reducer.ts | 22 ++- .../src/lib/aas-tree/aas-tree.state.ts | 125 ++++++------ .../aas-lib/src/lib/index-change.service.ts | 1 + .../aas-lib/src/lib/types/aas-query-params.ts | 9 - .../src/test/assets/sample-document.ts | 22 +-- .../aas-portal/src/app/aas/aas-api.service.ts | 2 +- .../aas-portal/src/app/aas/aas.component.ts | 49 ++--- .../aas-portal/src/app/aas/aas.selectors.ts | 5 +- .../src/app/aas/can-activate-aas.guard.ts | 2 +- .../aas-portal/src/app/app-routing.module.ts | 2 +- .../aas-portal/src/app/main/main.component.ts | 12 +- .../src/test/aas/aas-api.service.spec.ts | 11 +- .../src/test/aas/aas.component.spec.ts | 7 +- .../src/test/assets/sample-document.ts | 12 +- .../src/test/assets/test-document.ts | 2 + projects/aas-server/esbuild.dev.js | 8 +- .../src/app/aas-index/lowdb/lowdb-index.ts | 11 +- .../src/app/aas-index/lowdb/lowdb-types.ts | 1 + .../src/app/aas-index/mysql/mysql-index.ts | 28 +-- .../src/app/aas-index/mysql/mysql-types.ts | 1 + .../src/app/aas-provider/aas-provider.ts | 31 +-- .../aas-provider/hierarchical-structure.ts | 134 +++++++++++++ .../packages/aas-server/aas-server-package.ts | 1 + .../src/app/packages/create-xml-reader.ts | 2 +- .../app/packages/file-system/aasx-package.ts | 1 + .../src/app/packages/json-reader.ts | 6 +- .../src/app/packages/opcua/opcua-package.ts | 1 + .../{xml-reader_v2.ts => xml-reader-v2.ts} | 180 ++++++++++++++++-- .../aas-server/src/app/packages/xml-reader.ts | 62 ++++-- projects/aasportal-index/schema.sql | 2 + projects/common/esbuild.dev.js | 5 +- projects/common/src/lib/convert.ts | 1 + projects/common/src/lib/document.ts | 142 +++++++++++--- projects/common/src/lib/types.ts | 2 + projects/common/src/test/document.spec.ts | 2 + 40 files changed, 865 insertions(+), 390 deletions(-) create mode 100644 projects/aas-server/src/app/aas-provider/hierarchical-structure.ts rename projects/aas-server/src/app/packages/{xml-reader_v2.ts => xml-reader-v2.ts} (82%) diff --git a/aasportal.mdx b/aasportal.mdx index 31464788..aa3ac4b5 100644 --- a/aasportal.mdx +++ b/aasportal.mdx @@ -2861,7 +2861,7 @@ {"border-color": "91,155,213,255","fill-color": "151,188,228,255","font-color": "0,0,0,255","gradient": false,"shadow": false} - {"bounds": "371,36,181,80","model": "YU49xadxiUm+XA2jmjG0IA","name": "PC, Tablet, Phone","render": [{"bounds": "371,36,181,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "413,59,92,19","text": "<<device>>","type": "Text"},{"bounds": "386,78,146,19","fontStyle": "2","text": "PC, Tablet, Phone","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "371,36,181,80","model": "YU49xadxiUm+XA2jmjG0IA","name": "PC, Tablet, Phone","render": [{"bounds": "371,36,181,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "423,62,72,16","text": "<<device>>","type": "Text"},{"bounds": "403,78,112,16","fontStyle": "2","text": "PC, Tablet, Phone","type": "Text"}],"type": "UMLNodeView"} 371,36,181,80 @@ -2871,7 +2871,7 @@ {"fill-color": "255,255,255,255"} - {"bounds": "371,197,166,80","model": "99kwFUgB7EqpO13lxMSpzg","name": "aas-portal","render": [{"bounds": "371,197,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "404,220,96,19","text": "<<service>>","type": "Text"},{"bounds": "410,239,84,19","fontStyle": "2","text": "aas-portal","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "371,197,166,80","model": "99kwFUgB7EqpO13lxMSpzg","name": "aas-portal","render": [{"bounds": "371,197,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "414,223,76,16","text": "<<service>>","type": "Text"},{"bounds": "418,239,68,16","fontStyle": "2","text": "aas-portal","type": "Text"}],"type": "UMLNodeView"} 371,197,166,80 @@ -3007,7 +3007,7 @@ 5 - {"model": "M8ySx8TZlk6RyQIVYtyXCg","name": "","render": [{"lineWidth": "0","points": "459,115,455,197,","type": "Line"},{"bounds": "439,125,9,19","text": "*","type": "Text"},{"bounds": "440,164,9,19","text": "1","type": "Text"}],"type": "UMLAssociationView"} + {"model": "M8ySx8TZlk6RyQIVYtyXCg","name": "","render": [{"lineWidth": "0","points": "459,115,455,197,","type": "Line"},{"bounds": "439,126,8,16","text": "*","type": "Text"},{"bounds": "441,165,7,16","text": "1","type": "Text"}],"type": "UMLAssociationView"} 459,115,455,197, @@ -3049,7 +3049,7 @@ fromMultiView true - 439,125,9,19 + 439,126,8,16 16 20,15 * @@ -3073,7 +3073,7 @@ toMultiView true - 440,164,9,19 + 441,165,7,16 18 23,-12 1 @@ -3439,7 +3439,7 @@ {"fill-color": "255,255,255,255"} - {"bounds": "371,365,166,80","model": "qdayvcplfEapRY3chDyZvA","name": "aas-server","render": [{"bounds": "371,365,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "404,388,96,19","text": "<<service>>","type": "Text"},{"bounds": "409,407,86,19","fontStyle": "2","text": "aas-server","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "371,365,166,80","model": "qdayvcplfEapRY3chDyZvA","name": "aas-server","render": [{"bounds": "371,365,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "414,391,76,16","text": "<<service>>","type": "Text"},{"bounds": "416,407,72,16","fontStyle": "2","text": "aas-server","type": "Text"}],"type": "UMLNodeView"} 371,365,166,80 @@ -3448,7 +3448,7 @@ 17 - {"bounds": "50,325,147,80","model": "cmOEgyeti02akOoZ1BETVg","name": "aas-server","render": [{"bounds": "50,325,147,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "74,345,100,19","text": "<<node.js>>","type": "Text"},{"bounds": "81,364,86,19","fontStyle": "2","text": "aas-server","type": "Text"}],"type": "UMLArtifactView"} + {"bounds": "50,325,147,80","model": "cmOEgyeti02akOoZ1BETVg","name": "aas-server","render": [{"bounds": "50,325,147,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "85,348,78,16","text": "<<node.js>>","type": "Text"},{"bounds": "88,364,72,16","fontStyle": "2","text": "aas-server","type": "Text"}],"type": "UMLArtifactView"} 50,325,147,80 @@ -3458,7 +3458,7 @@ {"border-color": "91,155,213,255","fill-color": "255,255,255,255","font-color": "0,0,0,255","gradient": false,"shadow": false} - {"bounds": "689,246,166,80","model": "DEyatjst4Euy6JghnVkkzw","name": "aasportal-users","render": [{"bounds": "689,246,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "722,269,96,19","text": "<<service>>","type": "Text"},{"bounds": "706,288,127,19","fontStyle": "2","text": "aasportal-users","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "689,246,166,80","model": "DEyatjst4Euy6JghnVkkzw","name": "aasportal-users","render": [{"bounds": "689,246,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "732,272,76,16","text": "<<service>>","type": "Text"},{"bounds": "718,288,104,16","fontStyle": "2","text": "aasportal-users","type": "Text"}],"type": "UMLNodeView"} 689,246,166,80 @@ -3467,11 +3467,11 @@ 22 - {"bounds": "50,476,166,80","model": "nRhw4pRJ1kKmCyELXdav5w","name": "aas-scan-worker","render": [{"bounds": "50,476,166,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "83,496,100,19","text": "<<node.js>>","type": "Text"},{"bounds": "65,515,136,19","fontStyle": "2","text": "aas-scan-worker","type": "Text"}],"type": "UMLArtifactView"} + {"bounds": "50,476,166,80","model": "nRhw4pRJ1kKmCyELXdav5w","name": "aas-scan-worker","render": [{"bounds": "50,476,166,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "94,499,78,16","text": "<<node.js>>","type": "Text"},{"bounds": "78,515,111,16","fontStyle": "2","text": "aas-scan-worker","type": "Text"}],"type": "UMLArtifactView"} 50,476,166,80 - {"model": "VOaFYxEa+kq15XnfVi0Q4w","name": "","render": [{"lineWidth": "0","points": "130,476,126,404,","type": "Line"},{"bounds": "139,448,9,19","text": "*","type": "Text"},{"bounds": "138,414,9,19","text": "1","type": "Text"}],"type": "UMLAssociationView"} + {"model": "VOaFYxEa+kq15XnfVi0Q4w","name": "","render": [{"lineWidth": "0","points": "130,476,126,404,","type": "Line"},{"bounds": "139,449,8,16","text": "*","type": "Text"},{"bounds": "139,415,7,16","text": "1","type": "Text"}],"type": "UMLAssociationView"} 130,476,126,404, @@ -3513,7 +3513,7 @@ fromMultiView true - 139,448,9,19 + 139,449,8,16 16 18,14 * @@ -3537,7 +3537,7 @@ toMultiView true - 138,414,9,19 + 139,415,7,16 18 20,-15 1 @@ -3594,11 +3594,11 @@ 22 - {"bounds": "50,97,154,80","model": "SvIxLoASkki055TXQB2qlA","name": "NGINX","render": [{"bounds": "50,97,154,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "65,117,124,19","text": "<<http server>>","type": "Text"},{"bounds": "100,136,54,19","fontStyle": "2","text": "NGINX","type": "Text"}],"type": "UMLArtifactView"} + {"bounds": "50,97,154,80","model": "SvIxLoASkki055TXQB2qlA","name": "NGINX","render": [{"bounds": "50,97,154,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "78,120,98,16","text": "<<http server>>","type": "Text"},{"bounds": "108,136,38,16","fontStyle": "2","text": "NGINX","type": "Text"}],"type": "UMLArtifactView"} 50,97,154,80 - {"model": "dvgraY4s8EG1YRNFL4I92Q","name": "","render": [{"lineWidth": "0","points": "453,276,453,365,","type": "Line"},{"bounds": "434,287,9,19","text": "1","type": "Text"},{"bounds": "434,336,9,19","text": "1","type": "Text"}],"type": "UMLAssociationView"} + {"model": "dvgraY4s8EG1YRNFL4I92Q","name": "","render": [{"lineWidth": "0","points": "453,276,453,365,","type": "Line"},{"bounds": "435,288,7,16","text": "1","type": "Text"},{"bounds": "435,337,7,16","text": "1","type": "Text"}],"type": "UMLAssociationView"} 453,276,453,365, @@ -3640,7 +3640,7 @@ fromMultiView true - 434,287,9,19 + 435,288,7,16 16 20,15 1 @@ -3664,7 +3664,7 @@ toMultiView true - 434,336,9,19 + 435,337,7,16 18 20,-15 1 @@ -3716,12 +3716,12 @@ uml.onEditAttribute(view, model, text) - {"model": "jc0ng+UQr0uqOGFNWYls6Q","name": "mongodb:","render": [{"lineWidth": "0","points": "536,373,689,316,","type": "Line"},{"bounds": "572,334,74,19","text": "mongodb:","type": "Text"},{"bounds": "671,328,9,19","text": "1","type": "Text"}],"type": "UMLAssociationView"} + {"model": "jc0ng+UQr0uqOGFNWYls6Q","name": "mongodb:","render": [{"lineWidth": "0","points": "536,373,689,316,","type": "Line"},{"bounds": "580,335,58,16","text": "mongodb:","type": "Text"},{"bounds": "672,329,7,16","text": "1","type": "Text"}],"type": "UMLAssociationView"} 536,373,689,316, true - 572,334,74,19 + 580,335,58,16 17 -3,-3 mongodb: @@ -3782,7 +3782,7 @@ toMultiView true - 671,328,9,19 + 672,329,7,16 18 20,-15 1 @@ -3834,7 +3834,7 @@ uml.onEditAttribute(view, model, text) - {"model": "uXBYfjACfUiDMLEejN+nWw","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "215,486,371,432,","type": "Line"},{"bounds": "371,423,16,18","path": ":/images/dummy/arrow.svg","rotate": "709","type": "EdgeEnd"},{"bounds": "227,431,95,19","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} + {"model": "uXBYfjACfUiDMLEejN+nWw","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "215,486,371,432,","type": "Line"},{"bounds": "371,423,16,18","path": ":/images/dummy/arrow.svg","rotate": "709","type": "EdgeEnd"},{"bounds": "238,432,73,16","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} 215,486,371,432, @@ -3851,7 +3851,7 @@ true - 227,431,95,19 + 238,432,73,16 17 -12,-24 <<deploy>> @@ -3862,7 +3862,7 @@ uml.onEditStereotype(view, model, text) - {"model": "58ton5tBSUaDQ59frULkfw","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "196,373,371,394,","type": "Line"},{"bounds": "371,385,16,18","path": ":/images/dummy/arrow.svg","rotate": "-2631","type": "EdgeEnd"},{"bounds": "241,345,95,19","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} + {"model": "58ton5tBSUaDQ59frULkfw","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "196,373,371,394,","type": "Line"},{"bounds": "371,385,16,18","path": ":/images/dummy/arrow.svg","rotate": "-2631","type": "EdgeEnd"},{"bounds": "252,346,73,16","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} 196,373,371,394, @@ -3879,7 +3879,7 @@ true - 241,345,95,19 + 252,346,73,16 17 0,-30 <<deploy>> @@ -3894,11 +3894,11 @@ 23 - {"bounds": "979,246,140,80","model": "fUCEV8SyUkiKgOKj6d69ZA","name": "MongoDB","render": [{"bounds": "979,246,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "1011,276,77,19","fontStyle": "2","text": "MongoDB","type": "Text"}],"type": "UMLArtifactView"} + {"bounds": "979,246,140,80","model": "fUCEV8SyUkiKgOKj6d69ZA","name": "MongoDB","render": [{"bounds": "979,246,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "1019,277,60,16","fontStyle": "2","text": "MongoDB","type": "Text"}],"type": "UMLArtifactView"} 979,246,140,80 - {"model": "msqagNoCsUmfXbUckUzT7Q","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "203,160,371,211,","type": "Line"},{"bounds": "371,202,16,18","path": ":/images/dummy/arrow.svg","rotate": "-2531","type": "EdgeEnd"},{"bounds": "246,159,95,19","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} + {"model": "msqagNoCsUmfXbUckUzT7Q","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "203,160,371,211,","type": "Line"},{"bounds": "371,202,16,18","path": ":/images/dummy/arrow.svg","rotate": "-2531","type": "EdgeEnd"},{"bounds": "257,160,73,16","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} 203,160,371,211, @@ -3915,7 +3915,7 @@ true - 246,159,95,19 + 257,160,73,16 17 0,-19 <<deploy>> @@ -3931,7 +3931,7 @@ 23 - {"bounds": "50,216,140,80","model": "P6w27jhd6EePFn9YYQjiIA","name": "aas-portal","render": [{"bounds": "50,216,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "65,236,110,19","text": "<<web side>>","type": "Text"},{"bounds": "78,255,84,19","fontStyle": "2","text": "aas-portal","type": "Text"}],"type": "UMLArtifactView"} + {"bounds": "50,216,140,80","model": "P6w27jhd6EePFn9YYQjiIA","name": "aas-portal","render": [{"bounds": "50,216,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "77,239,87,16","text": "<<web side>>","type": "Text"},{"bounds": "86,255,68,16","fontStyle": "2","text": "aas-portal","type": "Text"}],"type": "UMLArtifactView"} 50,216,140,80 @@ -4846,7 +4846,7 @@ {"border-color": "165,165,165,255","fill-color": "255,255,255,255","font-color": "0,0,0,255","gradient": false,"shadow": false} - {"bounds": "689,365,166,80","model": "iBChC93lJkilnUz46XxJ+Q","name": "aasportal-index","render": [{"bounds": "689,365,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "722,388,96,19","text": "<<service>>","type": "Text"},{"bounds": "705,407,129,19","fontStyle": "2","text": "aasportal-index","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "689,365,166,80","model": "iBChC93lJkilnUz46XxJ+Q","name": "aasportal-index","render": [{"bounds": "689,365,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "732,391,76,16","text": "<<service>>","type": "Text"},{"bounds": "719,407,102,16","fontStyle": "2","text": "aasportal-index","type": "Text"}],"type": "UMLNodeView"} 689,365,166,80 @@ -4856,16 +4856,16 @@ {"fill-color": "255,255,255,255"} - {"bounds": "689,487,166,80","model": "9R9lfABHQku4VaZOVMJrmw","name": "aasportal-cloud","render": [{"bounds": "689,487,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "722,510,96,19","text": "<<service>>","type": "Text"},{"bounds": "706,529,127,19","fontStyle": "2","text": "aasportal-cloud","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "689,487,166,80","model": "9R9lfABHQku4VaZOVMJrmw","name": "aasportal-cloud","render": [{"bounds": "689,487,166,80","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "732,513,76,16","text": "<<service>>","type": "Text"},{"bounds": "719,529,102,16","fontStyle": "2","text": "aasportal-cloud","type": "Text"}],"type": "UMLNodeView"} 689,487,166,80 - {"model": "jtrRI8DnzEOJH0Ws9TRJSg","name": "http:","render": [{"lineWidth": "0","points": "689,404,536,404,","type": "Line"},{"bounds": "596,410,34,19","text": "http:","type": "Text"},{"bounds": "666,378,9,19","text": "1","type": "Text"}],"type": "UMLAssociationView"} + {"model": "jtrRI8DnzEOJH0Ws9TRJSg","name": "http:","render": [{"lineWidth": "0","points": "689,404,536,404,","type": "Line"},{"bounds": "600,411,27,16","text": "http:","type": "Text"},{"bounds": "667,379,7,16","text": "1","type": "Text"}],"type": "UMLAssociationView"} 689,404,536,404, true - 596,410,34,19 + 600,411,27,16 17 0,-15 http: @@ -4902,7 +4902,7 @@ fromMultiView true - 666,378,9,19 + 667,379,7,16 16 19,17 1 @@ -4978,7 +4978,7 @@ uml.onEditAttribute(view, model, text) - {"model": "x0WSXqCmsU2F06qK3KNU3Q","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "979,285,854,285,","type": "Line"},{"bounds": "854,276,16,18","path": ":/images/dummy/arrow.svg","rotate": "-900","type": "EdgeEnd"},{"bounds": "878,297,95,19","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} + {"model": "x0WSXqCmsU2F06qK3KNU3Q","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "979,285,854,285,","type": "Line"},{"bounds": "854,276,16,18","path": ":/images/dummy/arrow.svg","rotate": "-900","type": "EdgeEnd"},{"bounds": "889,298,73,16","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} 979,285,854,285, @@ -4995,7 +4995,7 @@ true - 878,297,95,19 + 889,298,73,16 17 -8,-21 <<deploy>> @@ -5006,7 +5006,7 @@ uml.onEditStereotype(view, model, text) - {"model": "NEqycTCW9k2q/kvexvnVpQ","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "189,251,371,241,","type": "Line"},{"bounds": "371,232,16,18","path": ":/images/dummy/arrow.svg","rotate": "868","type": "EdgeEnd"},{"bounds": "231,207,95,19","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} + {"model": "NEqycTCW9k2q/kvexvnVpQ","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "189,251,371,241,","type": "Line"},{"bounds": "371,232,16,18","path": ":/images/dummy/arrow.svg","rotate": "868","type": "EdgeEnd"},{"bounds": "242,208,73,16","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} 189,251,371,241, @@ -5023,7 +5023,7 @@ true - 231,207,95,19 + 242,208,73,16 17 0,-30 <<deploy>> @@ -5038,7 +5038,7 @@ 20 - {"bounds": "979,365,140,80","model": "bsotc6VFTEKlYpU4uCYZvQ","name": "MySQL","render": [{"bounds": "979,365,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "1022,395,54,19","fontStyle": "2","text": "MySQL","type": "Text"}],"type": "UMLArtifactView"} + {"bounds": "979,365,140,80","model": "bsotc6VFTEKlYpU4uCYZvQ","name": "MySQL","render": [{"bounds": "979,365,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "1028,396,43,16","fontStyle": "2","text": "MySQL","type": "Text"}],"type": "UMLArtifactView"} 979,365,140,80 @@ -5048,7 +5048,7 @@ {"border-color": "237,125,49,255","fill-color": "247,170,137,255","font-color": "0,0,0,255","gradient": false,"shadow": false} - {"bounds": "279,531,162,75","model": "OjJ8jzkOjkG3ITn0NLKE6A","name": "AAS API Server","render": [{"bounds": "279,531,162,75","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "312,551,91,19","text": "<<server>>","type": "Text"},{"bounds": "294,570,127,19","fontStyle": "2","text": "AAS API Server","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "279,531,162,75","model": "OjJ8jzkOjkG3ITn0NLKE6A","name": "AAS API Server","render": [{"bounds": "279,531,162,75","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "322,554,72,16","text": "<<server>>","type": "Text"},{"bounds": "306,570,103,16","fontStyle": "2","text": "AAS API Server","type": "Text"}],"type": "UMLNodeView"} 279,531,162,75 @@ -5056,17 +5056,17 @@ 22 - {"bounds": "979,487,140,80","model": "PDNvFWD7ckK6KJluMdDsfg","name": "NextCloud","render": [{"bounds": "979,487,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "1007,517,85,19","fontStyle": "2","text": "NextCloud","type": "Text"}],"type": "UMLArtifactView"} + {"bounds": "979,487,140,80","model": "PDNvFWD7ckK6KJluMdDsfg","name": "NextCloud","render": [{"bounds": "979,487,140,80","path": "shapes/artifact.png","preserveRatio": "true","sliceMargin": "5,22,19,22","type": "Image"},{"bounds": "1017,518,64,16","fontStyle": "2","text": "NextCloud","type": "Text"}],"type": "UMLArtifactView"} 979,487,140,80 - {"model": "lZsEF3tSCEKLFINc8GzbaQ","name": "http(s): WebDAV","render": [{"lineWidth": "0","points": "689,495,536,436,","type": "Line"},{"bounds": "549,453,119,19","text": "http(s): WebDAV","type": "Text"},{"bounds": "671,492,9,19","text": "1","type": "Text"}],"type": "UMLAssociationView"} + {"model": "lZsEF3tSCEKLFINc8GzbaQ","name": "http(s): WebDAV","render": [{"lineWidth": "0","points": "689,495,536,436,","type": "Line"},{"bounds": "560,454,96,16","text": "http(s): WebDAV","type": "Text"},{"bounds": "672,493,7,16","text": "1","type": "Text"}],"type": "UMLAssociationView"} 689,495,536,436, true {"fill-color": "255,255,255,255"} - 549,453,119,19 + 560,454,96,16 17 6,2 http(s): WebDAV @@ -5103,7 +5103,7 @@ fromMultiView true - 671,492,9,19 + 672,493,7,16 16 11,-11 1 @@ -5179,7 +5179,7 @@ uml.onEditAttribute(view, model, text) - {"model": "lCogz1zvtk6ntkQJ+PdDgg","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "979,404,854,404,","type": "Line"},{"bounds": "854,395,16,18","path": ":/images/dummy/arrow.svg","rotate": "-900","type": "EdgeEnd"},{"bounds": "873,417,95,19","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} + {"model": "lCogz1zvtk6ntkQJ+PdDgg","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "979,404,854,404,","type": "Line"},{"bounds": "854,395,16,18","path": ":/images/dummy/arrow.svg","rotate": "-900","type": "EdgeEnd"},{"bounds": "884,418,73,16","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} 979,404,854,404, @@ -5196,7 +5196,7 @@ true - 873,417,95,19 + 884,418,73,16 17 -3,-22 <<deploy>> @@ -5207,7 +5207,7 @@ uml.onEditStereotype(view, model, text) - {"model": "Laro1O/aJ06Rit+memdNKA","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "979,526,854,526,","type": "Line"},{"bounds": "854,517,16,18","path": ":/images/dummy/arrow.svg","rotate": "-900","type": "EdgeEnd"},{"bounds": "872,538,95,19","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} + {"model": "Laro1O/aJ06Rit+memdNKA","name": "","render": [{"lineStyle": "2","lineWidth": "0","points": "979,526,854,526,","type": "Line"},{"bounds": "854,517,16,18","path": ":/images/dummy/arrow.svg","rotate": "-900","type": "EdgeEnd"},{"bounds": "883,539,73,16","text": "<<deploy>>","type": "Text"}],"type": "UMLDependencyView"} 979,526,854,526, @@ -5224,7 +5224,7 @@ true - 872,538,95,19 + 883,539,73,16 17 -2,-21 <<deploy>> @@ -5242,7 +5242,7 @@ FAAAST BaSyx]]> - {"bounds": "153,635,157,67","model": "tbO7QEXmeE2hsNe9+Sg56Q","name": "Comment1","render": [{"bounds": "153,635,157,67","path": "shapes/comment.png","preserveRatio": "true","sliceMargin": "1,14,13,1","type": "Image"},{"bounds": "158,640,90,57","multiline": "true","text": "AASX Server\r\nFAAAST\r\nBaSyx","type": "Text"}],"type": "CommentView"} + {"bounds": "153,635,157,67","model": "tbO7QEXmeE2hsNe9+Sg56Q","name": "Comment1","render": [{"bounds": "153,635,157,67","path": "shapes/comment.png","preserveRatio": "true","sliceMargin": "1,14,13,1","type": "Image"},{"bounds": "158,640,74,48","multiline": "true","text": "AASX Server\r\nFAAAST\r\nBaSyx","type": "Text"}],"type": "CommentView"} 153,635,157,67 @@ -5252,7 +5252,7 @@ BaSyx]]> Static .aasx files]]> - {"bounds": "707,615,132,50","model": "nvq7J+QtYEWAUdAXIk4UEw","name": "Comment2","render": [{"bounds": "707,615,132,50","path": "shapes/comment.png","preserveRatio": "true","sliceMargin": "1,14,13,1","type": "Image"},{"bounds": "712,620,112,38","multiline": "true","text": "Templates\r\nStatic .aasx files","type": "Text"}],"type": "CommentView"} + {"bounds": "707,615,132,50","model": "nvq7J+QtYEWAUdAXIk4UEw","name": "Comment2","render": [{"bounds": "707,615,132,50","path": "shapes/comment.png","preserveRatio": "true","sliceMargin": "1,14,13,1","type": "Image"},{"bounds": "712,620,93,32","multiline": "true","text": "Templates\r\nStatic .aasx files","type": "Text"}],"type": "CommentView"} 707,615,132,50 @@ -5515,12 +5515,12 @@ Static .aasx files]]> 273,635,312,605, - {"model": "DlrFj8B2xUyZ1dGbwM9vQQ","name": "http(s): AAS API","render": [{"lineWidth": "0","points": "380,531,430,444,","type": "Line"},{"bounds": "329,483,120,19","text": "http(s): AAS API","type": "Text"},{"bounds": "372,505,9,19","text": "*","type": "Text"}],"type": "UMLAssociationView"} + {"model": "DlrFj8B2xUyZ1dGbwM9vQQ","name": "http(s): AAS API","render": [{"lineWidth": "0","points": "380,531,430,444,","type": "Line"},{"bounds": "342,484,94,16","text": "http(s): AAS API","type": "Text"},{"bounds": "372,506,8,16","text": "*","type": "Text"}],"type": "UMLAssociationView"} 380,531,430,444, true - 329,483,120,19 + 342,484,94,16 17 -12,-12 http(s): AAS API @@ -5557,7 +5557,7 @@ Static .aasx files]]> fromMultiView true - 372,505,9,19 + 372,506,8,16 16 13,-12 * @@ -5639,17 +5639,17 @@ Static .aasx files]]> {"fill-color": "255,170,127,255"} - {"bounds": "466,531,156,75","model": "onN3uYmb9U2OYr0QPXyz8A","name": "OPC UA Server","render": [{"bounds": "466,531,156,75","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "496,551,91,19","text": "<<server>>","type": "Text"},{"bounds": "481,570,121,19","fontStyle": "2","text": "OPC UA Server","type": "Text"}],"type": "UMLNodeView"} + {"bounds": "466,531,156,75","model": "onN3uYmb9U2OYr0QPXyz8A","name": "OPC UA Server","render": [{"bounds": "466,531,156,75","path": "shapes/node.png","preserveRatio": "true","sliceMargin": "28,22,29,22","type": "Image"},{"bounds": "506,554,72,16","text": "<<server>>","type": "Text"},{"bounds": "494,570,95,16","fontStyle": "2","text": "OPC UA Server","type": "Text"}],"type": "UMLNodeView"} 466,531,156,75 - {"model": "vnwck2Aj+U69t5z9b9nB5A","name": "opc.tcp:","render": [{"lineWidth": "0","points": "475,444,523,531,","type": "Line"},{"bounds": "470,471,57,19","text": "opc.tcp:","type": "Text"},{"bounds": "496,512,9,19","text": "*","type": "Text"}],"type": "UMLAssociationView"} + {"model": "vnwck2Aj+U69t5z9b9nB5A","name": "opc.tcp:","render": [{"lineWidth": "0","points": "475,444,523,531,","type": "Line"},{"bounds": "475,472,46,16","text": "opc.tcp:","type": "Text"},{"bounds": "496,513,8,16","text": "*","type": "Text"}],"type": "UMLAssociationView"} 475,444,523,531, true {"fill-color": "255,255,255,255"} - 470,471,57,19 + 475,472,46,16 17 -8,-3 opc.tcp: @@ -5710,7 +5710,7 @@ Static .aasx files]]> toMultiView true - 496,512,9,19 + 496,513,8,16 18 20,-15 * diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.component.ts b/projects/aas-lib/src/lib/aas-table/aas-table.component.ts index 6cb4651a..ef5afc34 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.component.ts +++ b/projects/aas-lib/src/lib/aas-table/aas-table.component.ts @@ -15,7 +15,6 @@ import { Observable, Subscription } from 'rxjs'; import { AASTableRow, AASTableFeatureState } from './aas-table.state'; import * as AASTableSelectors from './aas-table.selectors'; import * as AASTableActions from './aas-table.actions'; -import { AASQuery } from '../types/aas-query-params'; import { ClipboardService } from '../clipboard.service'; import { WindowService } from '../window.service'; import { ViewMode } from '../types/view-mode'; @@ -144,14 +143,14 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { } public open(row: AASTableRow): void { - const query: AASQuery = { - id: row.id, - name: row.endpoint, - document: row.document, - }; - - this.clipboard.set('AASQuery', query); - this.router.navigateByUrl('/aas?format=AASQuery', { skipLocationChange: true }); + this.clipboard.set('AASDocument', row.document); + this.router.navigate(['/aas'], { + skipLocationChange: true, + queryParams: { + id: row.id, + endpoint: row.endpoint, + }, + }); } public getToolTip(row: AASTableRow): string { diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts index 1802c8a9..76be0e86 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts @@ -71,33 +71,40 @@ export class AASTreeSearch { this.subscription.unsubscribe(); } + public find(referable: aas.Referable): void { + const index = this.rows.findIndex(row => row.element === referable); + if (index >= 0) { + this.store.dispatch(AASTreeActions.setMatchIndex({ index: index })); + } + } + public start(value: string) { - if (value) { - const terms: SearchTerm[] = []; - for (const expression of this.splitOr(value)) { - const term: SearchTerm = {}; - if (expression.length >= 3) { - if (expression.startsWith('#')) { - const query = this.parseExpression(expression); - if (query) { - term.query = query; - } - } else { - term.text = expression.toLocaleLowerCase(this.translate.currentLang); + if (!value) return; + + const terms: SearchTerm[] = []; + for (const expression of this.splitOr(value)) { + const term: SearchTerm = {}; + if (expression.length >= 3) { + if (expression.startsWith('#')) { + const query = this.parseExpression(expression); + if (query) { + term.query = query; } - } - - if (term.text || term.query) { - terms.push(term); + } else { + term.text = expression.toLocaleLowerCase(this.translate.currentLang); } } - if (terms.length > 0) { - this.store.dispatch(AASTreeActions.setSearchText({ terms })); - } else { - this.store.dispatch(AASTreeActions.setMatchIndex({ index: -1 })); + if (term.text || term.query) { + terms.push(term); } } + + if (terms.length > 0) { + this.store.dispatch(AASTreeActions.setSearchText({ terms })); + } else { + this.store.dispatch(AASTreeActions.setMatchIndex({ index: -1 })); + } } public findNext(): boolean { diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.html b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.html index e57b5ead..0cb34b69 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.html +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.html @@ -79,40 +79,45 @@ @if (node.isLeaf) { @switch(node.element.modelType) { - @case ('AnnotatedRelationshipElement') {} - @case ('AssetAdministrationShell') {} - @case ('BasicEventElement') {} - @case ('Blob') {} - @case ('Capability') {} - @case ('ConceptDescription') {} - @case ('DataSpecificationIec61360') {} - @case ('Entity') {} - @case ('File') { + @case ('AnnotatedRelationshipElement') { + + } + @case ('Blob') { + {{node.value}} + } + @case('Entity') { {{node.file?.value}} + (click)="openReference(node.entity?.globalAssetId)">{{node.value}} } - @case ('MultiLanguageProperty') {} - @case ('Operation') {} - @case ('Property') { -
{{1}}
+ @case ('File') { + {{node.value}} + } + @case ('Operation') { + {{node.value}} } - @case ('Range') {} @case ('ReferenceElement') { - {{node.reference?.value}} + {{node.value}} } @case ('RelationshipElement') { -
- first - seconde + } @case ('Submodel') {} @case ('SubmodelElementCollection') {} @case ('SubmodelElementList') {} @default { -
-
+
{{node.value}}
} } diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts index a215cf81..4955989b 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts @@ -25,6 +25,7 @@ import { selectSubmodel, getIdShortPath, mimeTypeToExtension, + selectReferable, } from 'common'; import { AASTreeRow, AASTreeFeatureState } from './aas-tree.state'; @@ -34,7 +35,6 @@ import { ShowVideoFormComponent } from './show-video-form/show-video-form.compon import { OperationCallFormComponent } from './operation-call-form/operation-call-form.component'; import { AASTreeSearch } from './aas-tree-search'; import { basename, encodeBase64Url } from '../convert'; -import { AASQuery } from '../types/aas-query-params'; import { ViewQuery } from '../types/view-query-params'; import { WindowService } from '../window.service'; import { DocumentService } from '../document.service'; @@ -68,6 +68,7 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { private readonly liveNodes: LiveNode[] = []; private readonly map = new Map(); private readonly subscription = new Subscription(); + private searchSubscription?: Subscription; private _selected: aas.Referable[] = []; private shiftKey = false; private altKey = false; @@ -93,16 +94,6 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { this.someSelected = this.store.select(AASTreeSelectors.selectSomeSelected); this.everySelected = this.store.select(AASTreeSelectors.selectEverySelected); - this.subscription.add( - this.store - .select(AASTreeSelectors.selectSelectedElements) - .pipe() - .subscribe(elements => { - this._selected = elements; - this.selectedChange.emit(elements); - }), - ); - this.window.addEventListener('keyup', this.keyup); this.window.addEventListener('keydown', this.keydown); } @@ -149,6 +140,16 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { public nodes: Observable; public ngOnInit(): void { + this.subscription.add( + this.store + .select(AASTreeSelectors.selectSelectedElements) + .pipe() + .subscribe(elements => { + this._selected = elements; + this.selectedChange.emit(elements); + }), + ); + this.subscription.add( this.store .select(AASTreeSelectors.selectError) @@ -207,8 +208,15 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { ); } - if (changes['search'] && this.search) { - this.subscription.add(this.search.subscribe(value => this.searching.start(value))); + if (changes['search']) { + if (this.searchSubscription) { + this.searchSubscription.unsubscribe(); + this.searchSubscription = undefined; + } + + if (this.search) { + this.searchSubscription = this.search.subscribe(value => this.searching.start(value)); + } } const stateChange = changes['state']; @@ -225,6 +233,7 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { public ngOnDestroy(): void { this.subscription.unsubscribe(); + this.searchSubscription?.unsubscribe(); this.webSocketSubject?.unsubscribe(); this.searching?.destroy(); this.window.removeEventListener('keyup', this.keyup); @@ -286,15 +295,6 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { this.store.dispatch(AASTreeActions.toggleSelected({ row: node, altKey: this.altKey, shiftKey: this.shiftKey })); } - public openEntity(entity: aas.Entity): void { - if (this.state === 'online') return; - - if (entity && entity.globalAssetId) { - this.clipboard.set('AASQuery', { id: entity.globalAssetId } as AASQuery); - this.router.navigateByUrl('/aas?format=AASQuery', { skipLocationChange: true }); - } - } - public async openFile(file: aas.File | undefined): Promise { if (!file || !file.value || this.state === 'online') return; @@ -361,18 +361,22 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { } } - public openReference(reference: aas.ReferenceElement | undefined): void { - if (!reference?.value || this.state === 'online') return; + public openReference(reference: aas.Reference | string | undefined): void { + if (!reference || this.state === 'online') return; - this.clipboard.set('AASQuery', { id: reference.value.keys[0].value } as AASQuery); - this.router.navigateByUrl('/aas?format=AASQuery', { skipLocationChange: true }); - } + if (typeof reference === 'string') { + this.openDocumentByAssetId(reference); + } else { + if (reference.keys.length === 0) { + return; + } - public openRelationship( - relationship: aas.RelationshipElement | undefined, - reference: aas.Reference | undefined, - ): void { - if (!relationship || !reference || this.state === 'online') return; + if (reference.type === 'ExternalReference') { + this.openExternalReference(reference); + } else { + this.selectModelReference(reference); + } + } } public openSubmodel(submodel: aas.Submodel | undefined): void { @@ -399,20 +403,6 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { } } - public open(node: AASTreeRow): void { - try { - switch (node.element.modelType) { - case 'Property': { - const property = node.element as aas.Property; - this.window.open(String(property.value)); - break; - } - } - } catch (error) { - this.notify.error(error); - } - } - public findNext(): void { this.searching.findNext(); } @@ -421,6 +411,14 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { this.searching.findPrevious(); } + public toString(value: aas.Reference | undefined): string { + if (!value) { + return '-'; + } + + return value.keys.map(key => key.value).join('.'); + } + private async showImageAsync(name: string, src: string): Promise { try { const modalRef = this.modal.open(ShowImageFormComponent, { backdrop: 'static' }); @@ -524,6 +522,44 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { } } + private openDocumentByAssetId(assetId: string): void { + if (assetId) { + this.clipboard.clear('AASDocument'); + this.router.navigate(['/aas'], { + skipLocationChange: true, + onSameUrlNavigation: 'reload', + queryParams: { id: assetId }, + }); + } + } + + private openExternalReference(reference: aas.Reference): void { + this.clipboard.clear('AASDocument'); + this.router.navigate(['/aas'], { + skipLocationChange: true, + onSameUrlNavigation: 'reload', + queryParams: { id: reference.keys[0].value }, + }); + } + + private selectModelReference(reference: aas.Reference): void { + if (!this.document?.content) { + return; + } + + const referable = selectReferable(this.document.content, reference); + if (referable) { + this.searching.find(referable); + } else if (reference.keys[0].type === 'AssetAdministrationShell') { + this.clipboard.clear('AASDocument'); + this.router.navigate(['/aas'], { + skipLocationChange: true, + onSameUrlNavigation: 'reload', + queryParams: { id: reference.keys[0].value }, + }); + } + } + private createMessage(document: AASDocument): WebSocketData { return { type: 'LiveRequest', diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts index 4428b0eb..bdd1d93d 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { createReducer, on } from '@ngrx/store'; -import { aas } from 'common'; +import { AASDocument, aas } from 'common'; import { AASTree, AASTreeRow, AASTreeState, SearchTerm } from './aas-tree.state'; import * as AASTreeActions from './aas-tree.actions'; @@ -30,16 +30,22 @@ export const aasTreeReducer = createReducer( toggleSelected(state, row, altKey, shiftKey), ), on(AASTreeActions.toggleSelections, state => toggleSelections(state)), - on(AASTreeActions.updateRows, (state, { document, localeId }) => { - try { + on(AASTreeActions.updateRows, (state, { document, localeId }) => updateRows(state, document, localeId)), + on(AASTreeActions.setSelectedElements, (state, { elements }) => setSelectedElements(state, elements)), +); + +function updateRows(state: AASTreeState, document: AASDocument | null, localeId: string): AASTreeState { + try { + if (document) { const tree = AASTree.from(document, localeId); return { ...state, rows: tree.nodes, error: null }; - } catch (error) { - return { ...state, error }; } - }), - on(AASTreeActions.setSelectedElements, (state, { elements }) => setSelectedElements(state, elements)), -); + + return { ...state, rows: [], index: -1, error: null }; + } catch (error) { + return { ...state, error }; + } +} function expandRow(state: AASTreeState, arg: number | AASTreeRow): AASTreeState { const tree = new AASTree(state.rows); diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts index 7daa08ab..5a26f145 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts @@ -79,6 +79,10 @@ export class AASTreeRow extends TreeNode { return this.element.modelType === 'File' ? (this.element as aas.File) : undefined; } + public get operation(): aas.Operation | undefined { + return this.element.modelType === 'Operation' ? (this.element as aas.Operation) : undefined; + } + public get property(): aas.Property | undefined { return this.element.modelType === 'Property' ? (this.element as aas.Property) : undefined; } @@ -266,29 +270,29 @@ class TreeInitialize { private getValue(referable: aas.Referable | null, localeId: string): boolean | string | undefined { if (referable) { switch (referable.modelType) { - case 'Property': - return this.getPropertyValue(referable as aas.Property, localeId); - case 'Range': { - const range = referable as aas.Range; - return `${convertToString(range.min, localeId)} ... ${convertToString(range.max, localeId)}`; + case 'Blob': { + const blob = referable as aas.Blob; + const extension = mimeTypeToExtension(blob.contentType) ?? ''; + return blob.contentType ? `${blob.idShort}${extension}` : '-'; } + case 'Entity': + return (referable as aas.Entity).globalAssetId ?? '-'; case 'File': { const file = referable as aas.File; return file.value ? basename(normalize(file.value)) : '-'; } - case 'Blob': { - const blob = referable as aas.Blob; - const extension = mimeTypeToExtension(blob.contentType) ?? ''; - return blob.contentType ? `${blob.idShort}${extension}` : '-'; + case 'MultiLanguageProperty': + return getLocaleValue((referable as aas.MultiLanguageProperty).value, localeId) ?? '-'; + case 'Operation': + return `${referable.idShort}()`; + case 'Property': + return this.getPropertyValue(referable as aas.Property, localeId); + case 'Range': { + const range = referable as aas.Range; + return `${convertToString(range.min, localeId)} ... ${convertToString(range.max, localeId)}`; } case 'ReferenceElement': return (referable as aas.ReferenceElement).value?.keys.map(item => item.value).join('/'); - case 'RelationshipElement': - return this.getRelationshipElementValue(referable as aas.RelationshipElement); - case 'MultiLanguageProperty': - return getLocaleValue((referable as aas.MultiLanguageProperty).value, localeId) ?? '-'; - case 'Entity': - return (referable as aas.Entity).globalAssetId ?? '-'; default: return '-'; } @@ -336,27 +340,21 @@ class TreeInitialize { } } - private getRelationshipElementValue(relationship: aas.RelationshipElement): string { - const first = relationship.first.keys.map(key => key.value).join('/'); - const second = relationship.second.keys.map(key => key.value).join('/'); - return `1. ${first}; 2. ${second}`; - } - private getTypeInfo(referable: aas.Referable | null): string { let value: string; if (referable) { switch (referable.modelType) { + case 'AnnotatedRelationshipElement': + value = (referable as aas.AnnotatedRelationshipElement).annotations?.length.toString() ?? '-'; + break; case 'AssetAdministrationShell': value = (referable as aas.Submodel).id; break; - case 'Submodel': - value = `Semantic ID: ${this.referenceToString((referable as aas.Submodel).semanticId)}`; - break; - case 'SubmodelElementCollection': - value = (referable as aas.SubmodelElementCollection).value?.length.toString() ?? '0'; + case 'Blob': + value = (referable as aas.Blob).contentType; break; - case 'SubmodelElementList': - value = (referable as aas.SubmodelElementList).value?.length.toString() ?? '0'; + case 'File': + value = (referable as aas.File).contentType; break; case 'Property': value = (referable as aas.Property).valueType; @@ -364,44 +362,57 @@ class TreeInitialize { case 'Range': value = (referable as aas.Range).valueType; break; - case 'File': - value = (referable as aas.File).contentType; + case 'ReferenceElement': + { + const keys = (referable as aas.ReferenceElement).value?.keys; + value = keys && keys.length > 0 ? keys[0].type : '-'; + } break; - case 'Blob': - value = (referable as aas.Blob).contentType; + case 'Submodel': + value = `Semantic ID: ${this.referenceToString((referable as aas.Submodel).semanticId)}`; break; - case 'MultiLanguageProperty': { - const mlp = referable as aas.MultiLanguageProperty; - value = ''; - if (mlp && Array.isArray(mlp.value)) { - value += `${mlp.value.map(item => item.language).join(', ')}`; - } + case 'SubmodelElementCollection': + value = (referable as aas.SubmodelElementCollection).value?.length.toString() ?? '0'; break; - } - case 'Entity': { - const entity = referable as aas.Entity; - value = ''; - if (entity?.globalAssetId) { - value = entity.globalAssetId; + case 'SubmodelElementList': + value = (referable as aas.SubmodelElementList).value?.length.toString() ?? '0'; + break; + case 'MultiLanguageProperty': + { + const mlp = referable as aas.MultiLanguageProperty; + value = ''; + if (mlp && Array.isArray(mlp.value)) { + value += `${mlp.value.map(item => item.language).join(', ')}`; + } } break; - } - case 'Operation': { - const operation = referable as aas.Operation; - value = ''; - if (operation.inputVariables && operation.inputVariables.length > 0) { - value += - '(' + operation.inputVariables.map(v => this.variableToString(v.value)).join(', ') + ')'; + case 'Entity': + { + const entity = referable as aas.Entity; + value = entity.entityType; } + break; + case 'Operation': + { + const operation = referable as aas.Operation; + value = ''; + if (operation.inputVariables && operation.inputVariables.length > 0) { + value += + '(' + + operation.inputVariables.map(v => this.variableToString(v.value)).join(', ') + + ')'; + } - if (operation.outputVariables && operation.outputVariables.length === 1) { - value += `: ${this.variableToString(operation.outputVariables[0].value)}`; - } else if (operation.outputVariables && operation.outputVariables.length > 1) { - value += - ': {' + operation.outputVariables.map(v => this.variableToString(v.value)).join(', ') + '}'; + if (operation.outputVariables && operation.outputVariables.length === 1) { + value += `: ${this.variableToString(operation.outputVariables[0].value)}`; + } else if (operation.outputVariables && operation.outputVariables.length > 1) { + value += + ': {' + + operation.outputVariables.map(v => this.variableToString(v.value)).join(', ') + + '}'; + } } break; - } default: value = '-'; break; diff --git a/projects/aas-lib/src/lib/index-change.service.ts b/projects/aas-lib/src/lib/index-change.service.ts index f2851141..bcf8c316 100644 --- a/projects/aas-lib/src/lib/index-change.service.ts +++ b/projects/aas-lib/src/lib/index-change.service.ts @@ -110,6 +110,7 @@ export class IndexChangeService { this.endpointRemoved(); break; case 'Reset': + this.reset.emit(); break; } } diff --git a/projects/aas-lib/src/lib/types/aas-query-params.ts b/projects/aas-lib/src/lib/types/aas-query-params.ts index 0193afc5..da0c7344 100644 --- a/projects/aas-lib/src/lib/types/aas-query-params.ts +++ b/projects/aas-lib/src/lib/types/aas-query-params.ts @@ -6,16 +6,7 @@ * *****************************************************************************/ -import { AASDocument } from 'common'; - export interface AASQueryParams { format?: string; id?: string; } - -export interface AASQuery { - id: string; - name?: string; - search?: string; - document?: AASDocument; -} diff --git a/projects/aas-lib/src/test/assets/sample-document.ts b/projects/aas-lib/src/test/assets/sample-document.ts index 95fa9c64..30e14762 100644 --- a/projects/aas-lib/src/test/assets/sample-document.ts +++ b/projects/aas-lib/src/test/assets/sample-document.ts @@ -17,15 +17,7 @@ const content: object = { "id": "http://customer.com/aas/9175_7013_7091_9168", "assetInformation": { "assetKind": "Instance", - "globalAssetId": { - "type": "ModelReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://customer.com/assets/KHBVZJSQKIY" - } - ] - } + "globalAssetId": "http://customer.com/assets/KHBVZJSQKIY" }, "submodels": [ { @@ -1497,15 +1489,7 @@ const sampleNoTechnicalData: object = { "id": "http://customer.com/aas/9175_7013_7091_9168", "assetInformation": { "assetKind": "Instance", - "globalAssetId": { - "type": "ModelReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://customer.com/assets/KHBVZJSQKIY" - } - ] - } + "globalAssetId": "http://customer.com/assets/KHBVZJSQKIY" }, "submodels": [ { @@ -2972,6 +2956,7 @@ const technicalData: object = { export const sampleDocument: AASDocument = { id: "http://customer.com/aas/9175_7013_7091_9168", idShort: "ExampleMotor", + assetId: 'http://customer.com/assets/KHBVZJSQKIY', endpoint: 'Samples', address: "ExampleMotor.aasx", modified: false, @@ -2985,6 +2970,7 @@ export const sampleDocument: AASDocument = { export const aasNoTechnicalData: AASDocument = { id: "http://customer.com/aas/9175_7013_7091_9168", idShort: "ExampleMotor", + assetId: 'http://customer.com/assets/KHBVZJSQKIY', endpoint: 'Samples', address: "ExampleMotor.aasx", modified: false, diff --git a/projects/aas-portal/src/app/aas/aas-api.service.ts b/projects/aas-portal/src/app/aas/aas-api.service.ts index 8b33ca5c..c5febaaf 100644 --- a/projects/aas-portal/src/app/aas/aas-api.service.ts +++ b/projects/aas-portal/src/app/aas/aas-api.service.ts @@ -22,7 +22,7 @@ export class AASApiService { /** * Gets the AAS document with the specified identifier. * @param id The AAS identifier. - * @param name The AAS container URL. + * @param name The AAS container name. * @returns The requested AAS document. */ public getDocument(id: string, name?: string): Observable { diff --git a/projects/aas-portal/src/app/aas/aas.component.ts b/projects/aas-portal/src/app/aas/aas.component.ts index 0ece7b8f..38252b4d 100644 --- a/projects/aas-portal/src/app/aas/aas.component.ts +++ b/projects/aas-portal/src/app/aas/aas.component.ts @@ -12,15 +12,7 @@ import { EMPTY, map, mergeMap, Observable, Subscription, from, of, first, catchE import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Store } from '@ngrx/store'; import { aas, isProperty, isNumberType, isBlob, AASDocument } from 'common'; -import { - AASQuery, - AASQueryParams, - AuthService, - ClipboardService, - DownloadService, - NotifyService, - OnlineState, -} from 'aas-lib'; +import { AuthService, ClipboardService, DownloadService, NotifyService, OnlineState } from 'aas-lib'; import { CommandHandlerService } from '../aas/command-handler.service'; import { EditElementFormComponent } from './edit-element-form/edit-element-form.component'; @@ -153,27 +145,22 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { } public ngOnInit(): void { - const params = this.route.snapshot.queryParams as AASQueryParams; - let query: AASQuery | undefined; - if (params.format) { - query = this.clipboard.get(params.format); - } else if (params.id) { - query = { - id: params.id, - }; - } - - if (query?.search) { - this.store.dispatch(AASActions.setSearch({ search: query.search })); - } + this.subscription.add( + this.route.queryParams.subscribe(params => { + if (params?.search) { + this.store.dispatch(AASActions.setSearch({ search: params.search })); + } - if (query) { - if (!query.document || !query.document.content) { - this.store.dispatch(AASActions.getDocument({ id: query.id, name: query.name })); - } else { - this.store.dispatch(AASActions.setDocument({ document: query.document })); - } - } + if (params) { + const document: AASDocument = this.clipboard.get('AASDocument'); + if (!document || !document.content) { + this.store.dispatch(AASActions.getDocument({ id: params.id, name: params.endpoint })); + } else { + this.store.dispatch(AASActions.setDocument({ document: document })); + } + } + }), + ); this.subscription.add( this.dashboard.name.subscribe(name => { @@ -217,7 +204,9 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { ); this.clipboard.set('DashboardQuery', { page: this.dashboardPage } as DashboardQuery); - return from(this.router.navigateByUrl('/dashboard?format=DashboardQuery')); + return from( + this.router.navigateByUrl('/dashboard?format=DashboardQuery', { skipLocationChange: true }), + ); }), catchError(error => { this.notify.error(error); diff --git a/projects/aas-portal/src/app/aas/aas.selectors.ts b/projects/aas-portal/src/app/aas/aas.selectors.ts index 88ec5094..44ce9302 100644 --- a/projects/aas-portal/src/app/aas/aas.selectors.ts +++ b/projects/aas-portal/src/app/aas/aas.selectors.ts @@ -51,10 +51,7 @@ export const selectVersion = createSelector(getDocument, document => versionToString(head(document?.content?.assetAdministrationShells)?.administration), ); -export const selectAssetId = createSelector( - getDocument, - document => head(document?.content?.assetAdministrationShells)?.assetInformation.globalAssetId ?? '-', -); +export const selectAssetId = createSelector(getDocument, document => document?.assetId ?? '-'); export const selectThumbnail = createSelector(getDocument, document => { if (document) { diff --git a/projects/aas-portal/src/app/aas/can-activate-aas.guard.ts b/projects/aas-portal/src/app/aas/can-activate-aas.guard.ts index 9ad19b28..59be8c94 100644 --- a/projects/aas-portal/src/app/aas/can-activate-aas.guard.ts +++ b/projects/aas-portal/src/app/aas/can-activate-aas.guard.ts @@ -25,7 +25,7 @@ export class CanActivateAAS { route: ActivatedRouteSnapshot, ): boolean | UrlTree | Observable | Promise { const params: AASQueryParams = route.queryParams; - if (params.id || params.format) { + if (params.id) { return true; } diff --git a/projects/aas-portal/src/app/app-routing.module.ts b/projects/aas-portal/src/app/app-routing.module.ts index 511b4dcc..fe7ee1f2 100644 --- a/projects/aas-portal/src/app/app-routing.module.ts +++ b/projects/aas-portal/src/app/app-routing.module.ts @@ -25,7 +25,7 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })], exports: [RouterModule], providers: [CanActivateAAS], }) diff --git a/projects/aas-portal/src/app/main/main.component.ts b/projects/aas-portal/src/app/main/main.component.ts index e9457eba..9156b57a 100644 --- a/projects/aas-portal/src/app/main/main.component.ts +++ b/projects/aas-portal/src/app/main/main.component.ts @@ -9,7 +9,7 @@ import { Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { Router } from '@angular/router'; import { Observable, Subscription, first, map } from 'rxjs'; -import { ClipboardService, WindowService, AASQuery } from 'aas-lib'; +import { ClipboardService, WindowService } from 'aas-lib'; import { ToolbarService } from '../toolbar.service'; import { MainApiService } from './main-api.service'; @@ -93,14 +93,16 @@ export class MainComponent implements OnInit, OnDestroy { .pipe(first()) .subscribe(document => { if (document) { - this.clipboard.set('AASQuery', { id: document.id } as AASQuery); - this.router.navigateByUrl('/aas?format=AASQuery', { skipLocationChange: true }); + this.router.navigate(['/aas'], { + skipLocationChange: true, + queryParams: { id: document.id, endpoint: document.endpoint }, + }); } else { - this.router.navigateByUrl('/start', { skipLocationChange: true }); + this.router.navigate(['/start'], { skipLocationChange: true }); } }); } else { - this.router.navigateByUrl('/start', { skipLocationChange: true }); + this.router.navigate(['/start'], { skipLocationChange: true }); } } diff --git a/projects/aas-portal/src/test/aas/aas-api.service.spec.ts b/projects/aas-portal/src/test/aas/aas-api.service.spec.ts index 315e837c..c865a25c 100644 --- a/projects/aas-portal/src/test/aas/aas-api.service.spec.ts +++ b/projects/aas-portal/src/test/aas/aas-api.service.spec.ts @@ -17,7 +17,6 @@ describe('AASApiService', function () { let httpTestingController: HttpTestingController; beforeEach(function () { - TestBed.configureTestingModule({ declarations: [], providers: [], @@ -63,14 +62,10 @@ describe('AASApiService', function () { }); describe('getTemplates', () => { - it('ToDo', () => { - - }); + it('ToDo', () => {}); }); describe('putDocument', () => { - it('ToDo', () => { - - }); + it('ToDo', () => {}); }); -}); \ No newline at end of file +}); diff --git a/projects/aas-portal/src/test/aas/aas.component.spec.ts b/projects/aas-portal/src/test/aas/aas.component.spec.ts index 13d984d4..81a4a146 100644 --- a/projects/aas-portal/src/test/aas/aas.component.spec.ts +++ b/projects/aas-portal/src/test/aas/aas.component.spec.ts @@ -160,11 +160,8 @@ describe('AASComponent', () => { }); it('shows the document assetId', (done: DoneFn) => { - const aas = sampleDocument.content!.assetAdministrationShells[0]; - const assetId = aas.assetInformation.globalAssetId ?? '-'; - component.assetId.pipe(first()).subscribe(value => { - expect(value).toEqual(assetId); + expect(value).toEqual('http://customer.com/assets/KHBVZJSQKIY'); done(); }); }); @@ -176,7 +173,7 @@ describe('AASComponent', () => { }); }); - it('shows the document idShort', (done: DoneFn) => { + it('shows the document version', (done: DoneFn) => { component.version.pipe(first()).subscribe(value => { expect(value).toEqual('-'); done(); diff --git a/projects/aas-portal/src/test/assets/sample-document.ts b/projects/aas-portal/src/test/assets/sample-document.ts index 95fa9c64..4ccacde7 100644 --- a/projects/aas-portal/src/test/assets/sample-document.ts +++ b/projects/aas-portal/src/test/assets/sample-document.ts @@ -17,15 +17,7 @@ const content: object = { "id": "http://customer.com/aas/9175_7013_7091_9168", "assetInformation": { "assetKind": "Instance", - "globalAssetId": { - "type": "ModelReference", - "keys": [ - { - "type": "GlobalReference", - "value": "http://customer.com/assets/KHBVZJSQKIY" - } - ] - } + "globalAssetId": "http://customer.com/assets/KHBVZJSQKIY", }, "submodels": [ { @@ -2972,6 +2964,7 @@ const technicalData: object = { export const sampleDocument: AASDocument = { id: "http://customer.com/aas/9175_7013_7091_9168", idShort: "ExampleMotor", + assetId: "http://customer.com/assets/KHBVZJSQKIY", endpoint: 'Samples', address: "ExampleMotor.aasx", modified: false, @@ -2985,6 +2978,7 @@ export const sampleDocument: AASDocument = { export const aasNoTechnicalData: AASDocument = { id: "http://customer.com/aas/9175_7013_7091_9168", idShort: "ExampleMotor", + assetId: "http://customer.com/assets/KHBVZJSQKIY", endpoint: 'Samples', address: "ExampleMotor.aasx", modified: false, diff --git a/projects/aas-portal/src/test/assets/test-document.ts b/projects/aas-portal/src/test/assets/test-document.ts index 4cac61a4..126bc9e2 100644 --- a/projects/aas-portal/src/test/assets/test-document.ts +++ b/projects/aas-portal/src/test/assets/test-document.ts @@ -21,6 +21,7 @@ export function createDocument(name: string, endpoint= "http://localhost/contain const document: AASDocument = { id: `http://localhost/aas/${name}`, idShort: name, + assetId: 'http://localhost/asset/${name}', endpoint: endpoint, address: '', modified: false, @@ -38,6 +39,7 @@ export function createDocumentHeader(name: string, endpoint: string): AASDocumen const document: AASDocument = { id: `http://localhost/aas/${name}`, idShort: name, + assetId: 'http://localhost/asset/${name}', endpoint: endpoint, address: '', modified: false, diff --git a/projects/aas-server/esbuild.dev.js b/projects/aas-server/esbuild.dev.js index 3914fac6..d75621b3 100644 --- a/projects/aas-server/esbuild.dev.js +++ b/projects/aas-server/esbuild.dev.js @@ -16,7 +16,8 @@ await esbuild.build({ format: 'esm', target: 'es2022', tsconfig: 'tsconfig.app.json', - packages: 'external' + packages: 'external', + minify: false, }); await esbuild.build({ @@ -27,5 +28,6 @@ await esbuild.build({ format: 'esm', target: 'es2022', tsconfig: 'tsconfig.app.json', - packages: 'external' -}); \ No newline at end of file + packages: 'external', + minify: false, +}); diff --git a/projects/aas-server/src/app/aas-index/lowdb/lowdb-index.ts b/projects/aas-server/src/app/aas-index/lowdb/lowdb-index.ts index 719dd3f8..d3d0cb81 100644 --- a/projects/aas-server/src/app/aas-index/lowdb/lowdb-index.ts +++ b/projects/aas-server/src/app/aas-index/lowdb/lowdb-index.ts @@ -18,6 +18,7 @@ import { BaseValueType, aas, flat, + isIdentifiable, } from 'common'; import { AASIndex } from '../aas-index.js'; import { LowDbQuery } from './lowdb-query.js'; @@ -126,8 +127,10 @@ export class LowDbIndex extends AASIndex { public async find(endpointName: string | undefined, id: string): Promise { await this.promise; const document = endpointName - ? this.db.data.documents.find(item => item.endpoint === endpointName && item.id === id) - : this.db.data.documents.find(item => item.id === id); + ? this.db.data.documents.find( + item => item.endpoint === endpointName && (item.id === id || item.assetId === id), + ) + : this.db.data.documents.find(item => item.id === id || item.assetId === id); if (document) { return this.toDocument(document); @@ -385,6 +388,10 @@ export class LowDbIndex extends AASIndex { idShort: referable.idShort, }; + if (isIdentifiable(referable)) { + element.id = referable.id; + } + let value: BaseValueType | undefined = this.toStringValue(referable); if (value) { element.value = value; diff --git a/projects/aas-server/src/app/aas-index/lowdb/lowdb-types.ts b/projects/aas-server/src/app/aas-index/lowdb/lowdb-types.ts index 6686f080..bf30352e 100644 --- a/projects/aas-server/src/app/aas-index/lowdb/lowdb-types.ts +++ b/projects/aas-server/src/app/aas-index/lowdb/lowdb-types.ts @@ -13,6 +13,7 @@ export type LowDbElementValueType = 'string' | 'boolean' | 'number' | 'Date' | ' export interface LowDbElement { uuid: string; modelType: string; + id?: string; idShort: string; value?: string; valueType?: LowDbElementValueType; diff --git a/projects/aas-server/src/app/aas-index/mysql/mysql-index.ts b/projects/aas-server/src/app/aas-index/mysql/mysql-index.ts index 200f22d5..f71538bb 100644 --- a/projects/aas-server/src/app/aas-index/mysql/mysql-index.ts +++ b/projects/aas-server/src/app/aas-index/mysql/mysql-index.ts @@ -8,7 +8,7 @@ import { v4 } from 'uuid'; import mysql, { Connection, ResultSetHeader } from 'mysql2/promise'; -import { AASEndpoint, AASCursor, AASPage, AASDocument, flat, aas, AASDocumentId } from 'common'; +import { AASEndpoint, AASCursor, AASPage, AASDocument, flat, aas, AASDocumentId, isIdentifiable } from 'common'; import { AASIndex } from '../aas-index.js'; import { Variable } from '../../variable.js'; import { urlToEndpoint } from '../../configuration.js'; @@ -146,7 +146,7 @@ export class MySqlIndex extends AASIndex { await connection.beginTransaction(); const uuid = v4(); await connection.query( - 'INSERT INTO `documents` (uuid, address, crc32, endpoint, id, idShort, onlineReady, readonly, thumbnail, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + 'INSERT INTO `documents` (uuid, address, crc32, endpoint, id, idShort, assetId, onlineReady, readonly, thumbnail, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ uuid, document.address, @@ -154,10 +154,11 @@ export class MySqlIndex extends AASIndex { document.endpoint, document.id, document.idShort, + document.assetId, !!document.onlineReady, document.readonly, document.thumbnail ?? '', - document.timestamp, + BigInt(document.timestamp), ], ); @@ -195,11 +196,8 @@ export class MySqlIndex extends AASIndex { } const uuid = results[0].uuid; - await connection.query('DELETE FROM `elements` WHERE uuid = ?;', [uuid]); - await connection.query('DELETE FROM `documents` WHERE uuid = ?;', [uuid]); - await connection.commit(); return true; } catch (error) { @@ -352,7 +350,11 @@ export class MySqlIndex extends AASIndex { private async getEndpointDocument(endpoint: string, id: string): Promise { const [results] = await ( await this.connection - ).query('SELECT * FROM `documents` WHERE endpoint = ? AND id = ?', [endpoint, id]); + ).query('SELECT * FROM `documents` WHERE endpoint = ? AND (id = ? OR assetId = ?)', [ + endpoint, + id, + id, + ]); if (results.length === 0) { throw new Error(`A document with the id "${id}" does not exist in "${endpoint}".`); @@ -364,7 +366,7 @@ export class MySqlIndex extends AASIndex { private async getDocument(id: string): Promise { const [results] = await ( await this.connection - ).query('SELECT * FROM `documents` WHERE id = ?', [id]); + ).query('SELECT * FROM `documents` WHERE (id = ? OR assetId = ?)', [id, id]); if (results.length === 0) { throw new Error(`A document with the id "${id}" does not exist.`); @@ -383,10 +385,11 @@ export class MySqlIndex extends AASIndex { private async writeElement(connection: Connection, uuid: string, referable: aas.Referable): Promise { await connection.query( - 'INSERT INTO `elements` (uuid, modelType, idShort, stringValue, numberValue, dateValue, booleanValue, bigintValue) VALUES (?, ?, ?, ?, ?, ?, ?, ?);', + 'INSERT INTO `elements` (uuid, modelType, id, idShort, stringValue, numberValue, dateValue, booleanValue, bigintValue) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);', [ uuid, this.toAbbreviation(referable), + isIdentifiable(referable) ? referable.id : undefined, referable.idShort, this.toStringValue(referable), this.toNumberValue(referable), @@ -404,10 +407,11 @@ export class MySqlIndex extends AASIndex { endpoint: result.endpoint, id: result.id, idShort: result.idShort, - readonly: result.readonly, - timestamp: result.timestamp, + assetId: result.assetId, + readonly: result.readonly ? true : false, + timestamp: Number(result.timestamp), content: null, - onlineReady: result.onlineReady, + onlineReady: result.onlineReady ? true : false, thumbnail: result.thumbnail, }; } diff --git a/projects/aas-server/src/app/aas-index/mysql/mysql-types.ts b/projects/aas-server/src/app/aas-index/mysql/mysql-types.ts index cef04e6f..b30279d5 100644 --- a/projects/aas-server/src/app/aas-index/mysql/mysql-types.ts +++ b/projects/aas-server/src/app/aas-index/mysql/mysql-types.ts @@ -18,6 +18,7 @@ export interface MySqlDocument extends AASDocument, RowDataPacket { export interface MySqlElement extends RowDataPacket { uuid: string; modelType: string; + id?: string; idShort: string; stringValue?: string; numberValue?: number; diff --git a/projects/aas-server/src/app/aas-provider/aas-provider.ts b/projects/aas-server/src/app/aas-provider/aas-provider.ts index 6cc795a5..1edc75c7 100644 --- a/projects/aas-server/src/app/aas-provider/aas-provider.ts +++ b/projects/aas-server/src/app/aas-provider/aas-provider.ts @@ -39,6 +39,7 @@ import { Variable } from '../variable.js'; import { WSServer } from '../ws-server.js'; import { ERRORS } from '../errors.js'; import { TaskHandler } from './task-handler.js'; +import { HierarchicalStructure } from './hierarchical-structure.js'; @singleton() export class AASProvider { @@ -529,16 +530,24 @@ export class AASProvider { private async collectDescendants(parent: AASDocument, nodes: AASDocument[]): Promise { const content = await this.getDocumentContentAsync(parent); - for (const reference of this.whereReferenceElement(content.submodels)) { - if (reference.value) { - const childId = reference.value.keys[0].value; - const child = - (await this.index.find(parent.endpoint, childId)) ?? (await this.index.find(undefined, childId)); - - if (child) { - const node: AASDocument = { ...child, parent: { ...parent }, content: null }; - nodes.push(node); - await this.collectDescendants(node, nodes); + const hierarchicalStructures = content.submodels.filter(sm => + HierarchicalStructure.isHierarchicalStructure(sm), + ); + + if (hierarchicalStructures.length > 0) { + } else { + for (const reference of this.whereReferenceElement(content.submodels)) { + if (reference.value) { + const childId = reference.value.keys[0].value; + const child = + (await this.index.find(parent.endpoint, childId)) ?? + (await this.index.find(undefined, childId)); + + if (child) { + const node: AASDocument = { ...child, parent: { ...parent }, content: null }; + nodes.push(node); + await this.collectDescendants(node, nodes); + } } } } @@ -569,7 +578,7 @@ export class AASProvider { } } - public async getDocumentContentAsync(document: AASDocument): Promise { + private async getDocumentContentAsync(document: AASDocument): Promise { const endpoint = await this.index.getEndpoint(document.endpoint); const resource = this.resourceFactory.create(endpoint); try { diff --git a/projects/aas-server/src/app/aas-provider/hierarchical-structure.ts b/projects/aas-server/src/app/aas-provider/hierarchical-structure.ts new file mode 100644 index 00000000..f9f72b5d --- /dev/null +++ b/projects/aas-server/src/app/aas-provider/hierarchical-structure.ts @@ -0,0 +1,134 @@ +import { aas } from 'common'; + +export type ArcheType = 'Full' | 'OneDown' | 'OneUp'; + +export abstract class HierarchicalStructureElement { + protected getProperty( + submodelElements: aas.SubmodelElement[] | undefined, + semanticId: string, + ): aas.Property | undefined { + return submodelElements?.find( + element => + element.modelType === 'Property' && + HierarchicalStructureElement.getSemanticId(element.semanticId) === semanticId, + ) as aas.Property; + } + + protected getEntity( + submodelElements: aas.SubmodelElement[] | undefined, + semanticId: string, + ): aas.Entity | undefined { + return submodelElements?.find( + element => + element.modelType === 'Entity' && + HierarchicalStructureElement.getSemanticId(element.semanticId) === semanticId, + ) as aas.Entity; + } + + protected getRelationship( + submodelElements: aas.SubmodelElement[] | undefined, + semanticId: string, + ): aas.RelationshipElement | undefined { + return submodelElements?.find( + element => + element.modelType === 'RelationshipElement' && + HierarchicalStructureElement.getSemanticId(element.semanticId) === semanticId, + ) as aas.RelationshipElement; + } + + protected static getSemanticId(reference: aas.Reference | undefined): string | undefined { + return reference && reference.keys.length > 0 ? reference.keys[0].value : undefined; + } +} + +export class EntryNode extends HierarchicalStructureElement { + public constructor( + public readonly archeType: ArcheType, + private readonly entity: aas.Entity, + ) { + super(); + } + + public get parent(): Node | null { + return null; + } + + public get children(): Node[] { + if (this.archeType === 'OneDown') { + } + + return []; + } +} + +export class Node extends HierarchicalStructureElement { + public constructor( + private readonly entryNode: EntryNode, + private readonly entity: aas.Entity, + ) { + super(); + + this.bulkCount = this.initBulkCount(); + } + + public readonly bulkCount: number; + + private initBulkCount(): number { + const property = this.getProperty( + this.entity.statements, + 'https://admin-shell.io/idta/HierarchicalStructures/BulkCount/1/0', + ); + + if (!property || !property.value) { + throw new Error('Missing BulkCount Property.'); + } + + return Number(property.value); + } +} + +export class HierarchicalStructure extends HierarchicalStructureElement { + public constructor(private readonly submodel: aas.Submodel) { + super(); + + this.archeType = this.initArcheType(); + this.entryNode = this.initEntryNode(this.archeType); + } + + public readonly archeType: ArcheType; + + public readonly entryNode: EntryNode; + + public static isHierarchicalStructure(submodel: aas.Submodel): boolean { + return ( + HierarchicalStructureElement.getSemanticId(submodel.semanticId) === + 'https://admin-shell.io/idta/HierarchicalStructures/1/0/Submodel' + ); + } + + private initArcheType(): ArcheType { + const property = this.getProperty( + this.submodel.submodelElements, + 'https://admin-shell.io/idta/HierarchicalStructures/ArcheType/1/0', + ); + + if (!property || !property.value) { + throw new Error('Missing ArcheType Property.'); + } + + return property.value as ArcheType; + } + + private initEntryNode(archeType: ArcheType): EntryNode { + const entity = this.getEntity( + this.submodel.submodelElements, + 'https://admin-shell.io/idta/HierarchicalStructures/EntryNode/1/0', + ); + + if (!entity) { + throw new Error('Missing EntryNode Entity.'); + } + + return new EntryNode(archeType, entity); + } +} diff --git a/projects/aas-server/src/app/packages/aas-server/aas-server-package.ts b/projects/aas-server/src/app/packages/aas-server/aas-server-package.ts index 67efe8c2..3aeb2a8b 100644 --- a/projects/aas-server/src/app/packages/aas-server/aas-server-package.ts +++ b/projects/aas-server/src/app/packages/aas-server/aas-server-package.ts @@ -49,6 +49,7 @@ export class AASServerPackage extends AASPackage { endpoint: this.server.name, address: this.idShort, idShort: environment.assetAdministrationShells[0].idShort, + assetId: environment.assetAdministrationShells[0].assetInformation.globalAssetId, readonly: this.server.readOnly, onlineReady: true, content: environment, diff --git a/projects/aas-server/src/app/packages/create-xml-reader.ts b/projects/aas-server/src/app/packages/create-xml-reader.ts index e6d0d68e..4e0e68a4 100644 --- a/projects/aas-server/src/app/packages/create-xml-reader.ts +++ b/projects/aas-server/src/app/packages/create-xml-reader.ts @@ -10,7 +10,7 @@ import { DOMParser } from '@xmldom/xmldom'; import { AASReader } from './aas-reader.js'; import { HTMLDocumentElement } from '../types/html-document-element.js'; import { XmlReaderV1 } from './xml-reader-v1.js'; -import { XmlReaderV2 } from './xml-reader_v2.js'; +import { XmlReaderV2 } from './xml-reader-v2.js'; import { XmlReader } from './xml-reader.js'; export function createXmlReader(xml: string): AASReader { diff --git a/projects/aas-server/src/app/packages/file-system/aasx-package.ts b/projects/aas-server/src/app/packages/file-system/aasx-package.ts index 7497d60d..b2384c31 100644 --- a/projects/aas-server/src/app/packages/file-system/aasx-package.ts +++ b/projects/aas-server/src/app/packages/file-system/aasx-package.ts @@ -49,6 +49,7 @@ export class AasxPackage extends AASPackage { endpoint: this.source.name, address: this.file, idShort: environment.assetAdministrationShells[0].idShort, + assetId: environment.assetAdministrationShells[0].assetInformation.globalAssetId, readonly: this.source.readOnly, onlineReady: this.source.onlineReady, content: environment, diff --git a/projects/aas-server/src/app/packages/json-reader.ts b/projects/aas-server/src/app/packages/json-reader.ts index 03fd6737..8ae6e111 100644 --- a/projects/aas-server/src/app/packages/json-reader.ts +++ b/projects/aas-server/src/app/packages/json-reader.ts @@ -398,10 +398,10 @@ export class JsonReader extends AASReader { if (i >= 0) { contentType = extensionToMimeType(value.substring(i)); } + } - if (!contentType) { - throw new Error('File.contentType'); - } + if (contentType == null) { + contentType = ''; } const file: aas.File = { diff --git a/projects/aas-server/src/app/packages/opcua/opcua-package.ts b/projects/aas-server/src/app/packages/opcua/opcua-package.ts index 555466bb..d1944b6c 100644 --- a/projects/aas-server/src/app/packages/opcua/opcua-package.ts +++ b/projects/aas-server/src/app/packages/opcua/opcua-package.ts @@ -45,6 +45,7 @@ export class OpcuaPackage extends AASPackage { endpoint: this.server.name, address: this.nodeId, idShort: component.browseName, + assetId: 'ToDo...', readonly: this.server.readOnly, onlineReady: this.server.onlineReady, content, diff --git a/projects/aas-server/src/app/packages/xml-reader_v2.ts b/projects/aas-server/src/app/packages/xml-reader-v2.ts similarity index 82% rename from projects/aas-server/src/app/packages/xml-reader_v2.ts rename to projects/aas-server/src/app/packages/xml-reader-v2.ts index c0b54a3a..c86079db 100644 --- a/projects/aas-server/src/app/packages/xml-reader_v2.ts +++ b/projects/aas-server/src/app/packages/xml-reader-v2.ts @@ -122,7 +122,7 @@ export class XmlReaderV2 extends AASReader { private readSubmodel(node: Node): aas.Submodel { const submodel: aas.Submodel = { ...this.readIdentifiable(node), - ...this.readHaSemantic(node), + ...this.readHasSemantics(node), ...this.readQualifiable(node), ...this.readHasKind(node), ...this.readHasDataSpecification(node), @@ -181,6 +181,9 @@ export class XmlReaderV2 extends AASReader { case 'Blob': submodelElement = this.readBlob(node, parent); break; + case 'Entity': + submodelElement = this.readEntity(node, parent); + break; case 'File': submodelElement = this.readFile(node, parent); break; @@ -238,6 +241,86 @@ export class XmlReaderV2 extends AASReader { return blob; } + private readEntity(node: Node, parent?: aas.Reference): aas.Entity { + const entityType = this.selectNode('./aas:entityType', node)?.textContent as aas.EntityType; + if (!entityType) { + throw new Error('File.contentType'); + } + + const entity: aas.Entity = { + ...this.readSubmodelElementType(node, parent), + entityType, + }; + + const globalAssetId = this.selectNode('./aas:globalAssetId', node)?.textContent; + if (globalAssetId) { + entity.globalAssetId = globalAssetId; + } + + const specificAssetIds = this.readSpecificAssetIds(this.selectNode('./aas:specificAssetIds', node)); + if (specificAssetIds) { + entity.specificAssetIds = specificAssetIds; + } + + const statements = this.readStatements(node, parent ? this.createReference(parent, entity) : undefined); + if (statements.length > 0) { + entity.statements = statements; + } + + return entity; + } + + private readStatements(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { + const statements: aas.SubmodelElement[] = []; + for (const child of this.selectNodes('./aas:statements/*', node)) { + const se = this.selectNode('./*[1]', child); + if (se) { + const submodelElement = this.readSubmodelElement(se, parent); + if (submodelElement) { + statements.push(submodelElement); + } + } + } + + return statements; + } + + private readSpecificAssetIds(node: Node | undefined): aas.SpecificAssetId[] | undefined { + if (!node) { + return undefined; + } + + const values: aas.SpecificAssetId[] = []; + for (const child of this.selectNodes('./aas:specificAssetId', node)) { + const value = this.readSpecificAssetId(child); + if (value) { + values.push(value); + } + } + + return values; + } + + private readSpecificAssetId(node: Node | undefined): aas.SpecificAssetId | undefined { + if (!node) { + return undefined; + } + + const externalSubjectId = this.readReference('./aas:externalSubjectId', node); + if (!externalSubjectId) { + throw new Error('SpecificAssetId.externalSubjectId'); + } + + const value: aas.SpecificAssetId = { + ...this.readHasSemantics(node), + name: this.getTextContent('./aas:name', node), + value: this.getTextContent('./aas:value', node), + externalSubjectId, + }; + + return value; + } + private readSubmodelElementCollection(node: Node, parent?: aas.Reference): aas.SubmodelElementCollection { const base = this.readSubmodelElementType(node, parent); const collection: aas.SubmodelElementCollection = { @@ -275,14 +358,41 @@ export class XmlReaderV2 extends AASReader { return property; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars private readRange(node: Node, parent?: aas.Reference): aas.Range { - throw new Error('Method not implemented.'); + const range: aas.Range = { + ...this.readSubmodelElementType(node, parent), + valueType: this.getTextContent('./aas:valueType', node) as aas.DataTypeDefXsd, + }; + + const min = this.selectTextContent('./aas:min', node); + if (min) { + range.min = min; + } + + const max = this.selectTextContent('./aas:max', node); + if (max) { + range.max = max; + } + + return range; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars private readRelationshipElement(node: Node, parent?: aas.Reference): aas.RelationshipElement { - throw new Error('Method not implemented.'); + const first = this.readReference('./aas:first', node); + if (!first) { + throw new Error('RelationshipElement.first'); + } + + const second = this.readReference('./aas:second', node); + if (!second) { + throw new Error('RelationshipElement.second'); + } + + return { + ...this.readSubmodelElementType(node, parent), + first, + second, + }; } private readFile(node: Node, parent?: aas.Reference): aas.File { @@ -317,7 +427,7 @@ export class XmlReaderV2 extends AASReader { private readSubmodelElementType(node: Node, parent?: aas.Reference): aas.SubmodelElement { return { ...this.readReferable(node, undefined, parent), - ...this.readHaSemantic(node), + ...this.readHasSemantics(node), ...this.readHasKind(node), ...this.readHasDataSpecification(node), ...this.readQualifiable(node), @@ -358,7 +468,7 @@ export class XmlReaderV2 extends AASReader { return referable; } - private readHaSemantic(node: Node): aas.HasSemantics { + private readHasSemantics(node: Node): aas.HasSemantics { const semanticId = this.readReference('./aas:semanticId', node); return semanticId ? { semanticId } : {}; } @@ -482,7 +592,7 @@ export class XmlReaderV2 extends AASReader { private readQualifiable(node: Node): aas.Qualifiable { const qualifiable: aas.Qualifiable = {}; - const qualifiers = this.readQualifiers('./aas:qualifier', node); + const qualifiers = this.readQualifiers('./aas:qualifier/aas:qualifier', node); if (qualifiers) { qualifiable.qualifiers = qualifiers; } @@ -491,15 +601,44 @@ export class XmlReaderV2 extends AASReader { } private readQualifiers(path: string, parent: Node): aas.Qualifier[] | undefined { - let qualifiers: aas.Qualifier[] | undefined; - const node = this.selectNode(path, parent); - if (node) { - qualifiers = []; // ToDo. + const qualifiers: aas.Qualifier[] = []; + for (const node of this.selectNodes(path, parent)) { + qualifiers.push(this.readQualifier(node)); + } + + if (qualifiers.length === 0) { + return undefined; } return qualifiers; } + private readQualifier(node: Node): aas.Qualifier { + const type = this.getTextContent('./aas:type', node); + const valueType = this.getTextContent('./aas:valueType', node) as aas.DataTypeDefXsd; + const qualifier: aas.Qualifier = { + type, + valueType, + }; + + const kind = this.selectTextContent('./aas:kind', node) as aas.QualifierKind; + if (kind) { + qualifier.kind = kind; + } + + const value = this.selectTextContent('./aas:value', node); + if (value != null) { + qualifier.value = value; + } + + const valueId = this.readReference('./valueId', node); + if (valueId) { + qualifier.valueId = valueId; + } + + return qualifier; + } + private readReference(path: string, parent: Node): aas.Reference | undefined { let reference: aas.Reference | undefined; const node = this.selectNode(path, parent); @@ -640,6 +779,23 @@ export class XmlReaderV2 extends AASReader { return id; } + private selectTextContent(expression: string, node: Node): string | null | undefined { + return (this.select(expression, node, true) as Node)?.textContent; + } + + private getTextContent(expression: string, node: Node, defaultValue?: string): string { + let value = (this.select(expression, node, true) as Node)?.textContent; + if (value == null) { + if (defaultValue == null) { + throw new Error(`${expression} ToDo.`); + } + + value = defaultValue; + } + + return value; + } + private toDataTypeDefXsd(source: string): aas.DataTypeDefXsd { switch (source) { case 'anyURI': diff --git a/projects/aas-server/src/app/packages/xml-reader.ts b/projects/aas-server/src/app/packages/xml-reader.ts index e04395e6..bf43aaa9 100644 --- a/projects/aas-server/src/app/packages/xml-reader.ts +++ b/projects/aas-server/src/app/packages/xml-reader.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { aas, determineType, toBoolean } from 'common'; +import { aas, determineType, extensionToMimeType, toBoolean } from 'common'; import { useNamespaces, XPathSelect } from 'xpath'; import { DOMParser } from '@xmldom/xmldom'; import { AASReader } from './aas-reader.js'; @@ -125,7 +125,7 @@ export class XmlReader extends AASReader { } const value: aas.SpecificAssetId = { - ...this.readHasSemantic(node), + ...this.readHasSemantics(node), name: this.getTextContent('./aas:name', node), value: this.getTextContent('./aas:value', node), externalSubjectId, @@ -185,7 +185,7 @@ export class XmlReader extends AASReader { private readSubmodel(node: Node): aas.Submodel { const submodel: aas.Submodel = { ...this.readIdentifiable(node), - ...this.readHasSemantic(node), + ...this.readHasSemantics(node), ...this.readQualifiable(node), ...this.readHasKind(node), ...this.readHasDataSpecification(node), @@ -540,7 +540,15 @@ export class XmlReader extends AASReader { private readFile(node: Node, parent?: aas.Reference): aas.File { let contentType = this.selectNode('./aas:mimeType', node)?.textContent; - if (!contentType) { + const value = this.selectNode('./aas:value', node)?.textContent; + if (!contentType && value) { + const i = value.lastIndexOf('.'); + if (i >= 0) { + contentType = extensionToMimeType(value.substring(i)); + } + } + + if (contentType == null) { contentType = ''; } @@ -549,7 +557,6 @@ export class XmlReader extends AASReader { contentType, }; - const value = this.selectNode('./aas:value', node)?.textContent; if (value) { file.value = value; } @@ -578,7 +585,7 @@ export class XmlReader extends AASReader { private readSubmodelElementType(node: Node, parent?: aas.Reference): aas.SubmodelElement { return { ...this.readReferable(node, undefined, parent), - ...this.readHasSemantic(node), + ...this.readHasSemantics(node), ...this.readHasKind(node), ...this.readHasDataSpecification(node), ...this.readQualifiable(node), @@ -637,14 +644,14 @@ export class XmlReader extends AASReader { private readExtension(node: Node): aas.Extension { const extension: aas.Extension = { - ...this.readHasSemantic(node), + ...this.readHasSemantics(node), name: this.getTextContent('./aas:name', node), }; return extension; } - private readHasSemantic(node: Node): aas.HasSemantics { + private readHasSemantics(node: Node): aas.HasSemantics { const semanticId = this.readReference(this.selectNode('./aas:semanticId', node)); return semanticId ? { semanticId } : {}; } @@ -817,7 +824,7 @@ export class XmlReader extends AASReader { private readQualifiable(node: Node): aas.Qualifiable { const qualifiable: aas.Qualifiable = {}; - const qualifiers = this.readQualifiers('./aas:qualifier', node); + const qualifiers = this.readQualifiers('./aas:qualifiers/aas:qualifier', node); if (qualifiers) { qualifiable.qualifiers = qualifiers; } @@ -826,15 +833,44 @@ export class XmlReader extends AASReader { } private readQualifiers(path: string, parent: Node): aas.Qualifier[] | undefined { - let qualifiers: aas.Qualifier[] | undefined; - const node = this.selectNode(path, parent); - if (node) { - qualifiers = []; // ToDo. + const qualifiers: aas.Qualifier[] = []; + for (const node of this.selectNodes(path, parent)) { + qualifiers.push(this.readQualifier(node)); + } + + if (qualifiers.length === 0) { + return undefined; } return qualifiers; } + private readQualifier(node: Node): aas.Qualifier { + const type = this.getTextContent('./aas:type', node); + const valueType = this.getTextContent('./aas:valueType', node) as aas.DataTypeDefXsd; + const qualifier: aas.Qualifier = { + type, + valueType, + }; + + const kind = this.selectTextContent('./aas:kind', node) as aas.QualifierKind; + if (kind) { + qualifier.kind = kind; + } + + const value = this.selectTextContent('./aas:value', node); + if (value != null) { + qualifier.value = value; + } + + const valueId = this.readReference(this.selectNode('./valueId', node)); + if (valueId) { + qualifier.valueId = valueId; + } + + return qualifier; + } + private readReference(node: Node | undefined): aas.Reference | undefined { if (!node) { return undefined; diff --git a/projects/aasportal-index/schema.sql b/projects/aasportal-index/schema.sql index 90f61512..9022eb2e 100644 --- a/projects/aasportal-index/schema.sql +++ b/projects/aasportal-index/schema.sql @@ -14,6 +14,7 @@ CREATE TABLE documents ( endpoint VARCHAR(100), id VARCHAR(255), idShort VARCHAR(100), + assetId VARCHAR(255), onlineReady BOOL, readonly BOOL, thumbnail VARCHAR(7167), @@ -23,6 +24,7 @@ CREATE TABLE documents ( CREATE TABLE elements ( uuid CHAR(36) NOT NULL, modelType VARCHAR(5) NOT NULL, + id VARCHAR(255), idShort VARCHAR(100) NOT NULL, stringValue VARCHAR(512), numberValue DOUBLE, diff --git a/projects/common/esbuild.dev.js b/projects/common/esbuild.dev.js index 12d6d38b..f092079b 100644 --- a/projects/common/esbuild.dev.js +++ b/projects/common/esbuild.dev.js @@ -16,5 +16,6 @@ await esbuild.build({ format: 'esm', target: 'es2022', tsconfig: 'tsconfig.lib.json', - external: ['lodash-es'] -}); \ No newline at end of file + external: ['lodash-es'], + minify: false, +}); diff --git a/projects/common/src/lib/convert.ts b/projects/common/src/lib/convert.ts index 9ae4b8ad..15161480 100644 --- a/projects/common/src/lib/convert.ts +++ b/projects/common/src/lib/convert.ts @@ -744,6 +744,7 @@ export function mimeTypeToExtension(mimeType: string): string | undefined { /** Returns the MIME type that corresponds ti the specified file extension */ export function extensionToMimeType(extension: string): string | undefined { + extension = extension?.toLowerCase(); for (const tuple of mimeTypes) { if (tuple[1] === extension) { return tuple[0]; diff --git a/projects/common/src/lib/document.ts b/projects/common/src/lib/document.ts index 4d108f66..44b11fb7 100644 --- a/projects/common/src/lib/document.ts +++ b/projects/common/src/lib/document.ts @@ -379,10 +379,37 @@ export function isDeepEqual(a?: aas.Environment, b?: aas.Environment): boolean { function equal(a: aas.Referable, b: aas.Referable): boolean { switch (a.modelType) { - case 'Property': - return equalProperty(a as aas.Property, b as aas.Property); + case 'AssetAdministrationShell': + return equalShell(a as aas.AssetAdministrationShell, b as aas.AssetAdministrationShell); + case 'AnnotatedRelationshipElement': + return equalAnnotatedRelationshipElement( + a as aas.AnnotatedRelationshipElement, + b as aas.AnnotatedRelationshipElement, + ); + case 'BasicEventElement': + return equalBasicEventElement(a as aas.BasicEventElement, b as aas.BasicEventElement); + case 'Capability': + return equalCapability(a as aas.Capability, b as aas.Capability); + case 'Blob': + return equalBlob(a as aas.Blob, b as aas.Blob); + case 'ConceptDescription': + return equalConceptDescription(a as aas.ConceptDescription, b as aas.ConceptDescription); + case 'Entity': + return equalEntity(a as aas.Entity, b as aas.Entity); + case 'File': + return equalFile(a as aas.File, b as aas.File); case 'MultiLanguageProperty': return equalMultiLanguageProperty(a as aas.MultiLanguageProperty, b as aas.MultiLanguageProperty); + case 'Operation': + return equalOperation(a as aas.Operation, b as aas.Operation); + case 'Property': + return equalProperty(a as aas.Property, b as aas.Property); + case 'Range': + return equalRange(a as aas.Range, b as aas.Range); + case 'ReferenceElement': + return equalReferenceElement(a as aas.ReferenceElement, b as aas.ReferenceElement); + case 'RelationshipElement': + return equalRelationshipElement(a as aas.RelationshipElement, b as aas.RelationshipElement); case 'Submodel': return equalSubmodel(a as aas.Submodel, b as aas.Submodel); case 'SubmodelElementCollection': @@ -392,18 +419,6 @@ export function isDeepEqual(a?: aas.Environment, b?: aas.Environment): boolean { ); case 'SubmodelElementList': return equalSubmodelElementList(a as aas.SubmodelElementList, b as aas.SubmodelElementList); - case 'AssetAdministrationShell': - return equalShell(a as aas.AssetAdministrationShell, b as aas.AssetAdministrationShell); - case 'ReferenceElement': - return equalReferenceElement(a as aas.ReferenceElement, b as aas.ReferenceElement); - case 'Entity': - return equalEntity(a as aas.Entity, b as aas.Entity); - case 'File': - return equalFile(a as aas.File, b as aas.File); - case 'Blob': - return equalBlob(a as aas.Blob, b as aas.Blob); - case 'ConceptDescription': - return equalConceptDescription(a as aas.ConceptDescription, b as aas.ConceptDescription); default: return true; } @@ -465,13 +480,11 @@ export function isDeepEqual(a?: aas.Environment, b?: aas.Environment): boolean { ); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars function equalDataSpecificationContent(a: aas.DataSpecificationContent, b: aas.DataSpecificationContent): boolean { - // ToDo: - return true; + return isEqual(a, b); } - function equalHasSemantic(a: aas.HasSemantics, b: aas.HasSemantics): boolean { + function equalHasSemantics(a: aas.HasSemantics, b: aas.HasSemantics): boolean { return equalReference(a.semanticId, b.semanticId); } @@ -558,7 +571,7 @@ export function isDeepEqual(a?: aas.Environment, b?: aas.Environment): boolean { return ( equalIdentifiable(a, b) && equalHasDataSpecification(a, b) && - equalHasSemantic(a, b) && + equalHasSemantics(a, b) && equalQualifiable(a, b) && equalHasKind(a, b) && queueReferables(a.submodelElements, b.submodelElements) @@ -577,7 +590,7 @@ export function isDeepEqual(a?: aas.Environment, b?: aas.Environment): boolean { function equalSubmodelElement(a: aas.SubmodelElement, b: aas.SubmodelElement): boolean { return ( - equalReferable(a, b) && equalHasDataSpecification(a, b) && equalHasSemantic(a, b) && equalQualifiable(a, b) + equalReferable(a, b) && equalHasDataSpecification(a, b) && equalHasSemantics(a, b) && equalQualifiable(a, b) ); } @@ -638,6 +651,81 @@ export function isDeepEqual(a?: aas.Environment, b?: aas.Environment): boolean { return false; } + function equalAnnotatedRelationshipElement( + a: aas.AnnotatedRelationshipElement, + b: aas.AnnotatedRelationshipElement, + ): boolean { + return ( + equalSubmodelElement(a, b) && + equalRelationshipElement(a, b) && + queueReferables(a.annotations, b.annotations) + ); + } + + function equalBasicEventElement(a: aas.BasicEventElement, b: aas.BasicEventElement): boolean { + return ( + equalSubmodelElement(a, b) && + a.direction === b.direction && + a.lastUpdate === b.lastUpdate && + a.maxInterval === b.maxInterval && + equalReference(a.messageBroker, b.messageBroker) && + a.messageTopic === b.messageTopic && + a.minInterval === b.minInterval && + equalReference(a.observed, b.observed) && + a.state === b.state + ); + } + + function equalCapability(a: aas.Capability, b: aas.Capability): boolean { + return equalSubmodelElement(a, b); + } + + function equalOperation(a: aas.Operation, b: aas.Operation): boolean { + return ( + equalSubmodelElement(a, b) && + equalOperationVariables(a.inputVariables, b.inoutputVariables) && + equalOperationVariables(a.inoutputVariables, b.inoutputVariables) && + equalOperationVariables(a.outputVariables, b.outputVariables) + ); + } + + function equalOperationVariables( + a: aas.OperationVariable[] | undefined, + b: aas.OperationVariable[] | undefined, + ): boolean { + if (a === b) { + return true; + } + + if (!a || !b || a.length !== b.length) { + return false; + } + + for (let i = 0, n = a.length; i < n; i++) { + if (!equalOperationVariable(a[i], b[i])) { + return false; + } + } + + return true; + } + + function equalOperationVariable(a: aas.OperationVariable, b: aas.OperationVariable): boolean { + if (a === b) { + return true; + } + + return equal(a.value, b.value); + } + + function equalRange(a: aas.Range, b: aas.Range): boolean { + return equalSubmodelElement(a, b) && a.max === b.max && a.min === b.min && a.valueType === b.valueType; + } + + function equalRelationshipElement(a: aas.RelationshipElement, b: aas.RelationshipElement): boolean { + return equalSubmodelElement(a, b) && equalReference(a.first, b.first) && equalReference(a.second, b.second); + } + function equalReferenceElement(a: aas.ReferenceElement, b: aas.ReferenceElement): boolean { return equalSubmodelElement(a, b) && equalReference(a.value, b.value); } @@ -675,7 +763,7 @@ export function isDeepEqual(a?: aas.Environment, b?: aas.Environment): boolean { a === b || (a != null && b != null && - equalHasSemantic(a, b) && + equalHasSemantics(a, b) && a.name === b.name && a.value === b.value && equalReference(a.externalSubjectId, b.externalSubjectId)) @@ -763,6 +851,8 @@ export function getAbbreviation(modelType: aas.ModelType): AASAbbreviation | und return 'RelA'; case 'AssetAdministrationShell': return 'AAS'; + case 'BasicEventElement': + return 'Evt'; case 'Capability': return 'Cap'; case 'ConceptDescription': @@ -775,8 +865,6 @@ export function getAbbreviation(modelType: aas.ModelType): AASAbbreviation | und return 'Range'; case 'Entity': return 'Ent'; - case 'BasicEventElement': - return 'Evt'; case 'File': return 'File'; case 'Blob': @@ -808,8 +896,14 @@ export function getModelTypeFromAbbreviation(abbreviation: AASAbbreviation): aas return 'AssetAdministrationShell'; case 'blob': return 'Blob'; + case 'cap': + return 'Capability'; + case 'cd': + return 'ConceptDescription'; case 'ent': return 'Entity'; + case 'evt': + return 'BasicEventElement'; case 'file': return 'File'; case 'mlp': @@ -824,6 +918,8 @@ export function getModelTypeFromAbbreviation(abbreviation: AASAbbreviation): aas return 'ReferenceElement'; case 'rel': return 'RelationshipElement'; + case 'rela': + return 'AnnotatedRelationshipElement'; case 'sm': return 'Submodel'; case 'smc': diff --git a/projects/common/src/lib/types.ts b/projects/common/src/lib/types.ts index dc3e862a..794d0eea 100644 --- a/projects/common/src/lib/types.ts +++ b/projects/common/src/lib/types.ts @@ -80,6 +80,8 @@ export interface AASDocument extends AASDocumentId { crc32: number; /** The name of the AAS. */ idShort: string; + /** The Asset identifier */ + assetId?: string; /** Indicates whether the document is modified. */ modified?: boolean; /** Indicates whether communication can be established with the system represented by the AAS. */ diff --git a/projects/common/src/test/document.spec.ts b/projects/common/src/test/document.spec.ts index b9723ab4..98df962d 100644 --- a/projects/common/src/test/document.spec.ts +++ b/projects/common/src/test/document.spec.ts @@ -25,6 +25,7 @@ describe('Document', function () { endpoint: 'Test', address: 'a.json', idShort: 'A', + assetId: 'http://customer.com/asset/a', readonly: true, crc32: 0, timestamp: 0, @@ -35,6 +36,7 @@ describe('Document', function () { endpoint: 'Test', address: 'b.json', idShort: 'B', + assetId: 'http://customer.com/asset/b', readonly: true, crc32: 0, timestamp: 0, From 6afba37680335e5f2c55836412596f350422c7b4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 8 Apr 2024 10:15:21 +0000 Subject: [PATCH 2/2] chore(Release): 3.0.0-development.41 # [3.0.0-development.41](https://github.com/FraunhoferIOSB/AASPortal/compare/v3.0.0-development.40...v3.0.0-development.41) (2024-04-08) ### Features * Hierarchical structure submodel template ([356cd48](https://github.com/FraunhoferIOSB/AASPortal/commit/356cd489da941f1d041eb498d8252817da2ef1f9)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76257582..7327ad51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aas-portal-project", - "version": "3.0.0-development.40", + "version": "3.0.0-development.41", "description": "Web-based visualization and control of asset administration shells.", "type": "module", "scripts": {