Skip to content

Commit

Permalink
Visualization component loading (#106)
Browse files Browse the repository at this point in the history
* Add folder structure for Visualizations + move existent Visualizations

* Adapt and simplify Visualization component loading

* Cleanup

* Back to Plugin wording

* Back to Plugin wording /2

* Fix space

* adds fix to checkSemanticId for Handover Documentation IDs

* adds fix to ComponentVisualization not showing file or blob previews

* removes faulty comments

* comments out console logs

---------

Co-authored-by: Aaron Zielstorff <[email protected]>
  • Loading branch information
seicke and aaronzi authored Dec 7, 2024
1 parent 9a2f54e commit 29f913d
Show file tree
Hide file tree
Showing 20 changed files with 159 additions and 235 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ See examples in the [basyx-java-server-sdk](https://github.com/eclipse-basyx/bas

The BaSyx-UI includes a Feature to develop your own Plugins. They can be used to display and interact with a Submodel (and/or SubmodelElements).

Plugins will be displayed in the `Visualization`-Part of the UI. In order for Plugins to be shown, a Submodel(Element) has to have a SemanticID which matches with the configured SemanticID of the desired Plugin.
Plugins will be displayed in the `Visualization`-Part of the UI. In order for Plugins to be loaded, a Submodel(Element) has to have a SemanticID which matches with the configured `semanticId` of the desired Plugin. The configuration of a Plugin `semanticId` can be done via a string (e.g. `'http://hello.world.de/plugin_submodel'`) or via an array for multiple SemanticIds (e.g. `['http://hello.world.de/plugin_submodel', 'http://hello.world.de/plugin_property']`)

To include your own Plugin, you have to create a Vue.js Component and add it to the `UserPlugins`-Folder in the `aas-web-ui/src`-Directory. The Plugin will then be automatically loaded and displayed in the UI.
To include your own Plugins, you have to create a Vue.js Component and add it to the `UserPlugins`-Folder in the `aas-web-ui/src`-Directory. The Plugin will then be automatically loaded and displayed in the UI.

> If you plan on including your own plugins, keep in mind that you have to build the Docker Image yourself!
Expand Down
2 changes: 1 addition & 1 deletion aas-web-ui/src/UserPlugins/HelloWorldPlugin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@
methods: {
// Function to initialize the HelloWorld-Plugin
initializePlugin() {
// Check if a Node is selected
if (Object.keys(this.submodelElementData).length == 0) {
this.pluginData = {}; // Reset the Plugin Data when no Node is selected
return;
}
let pluginData = { ...this.submodelElementData }; // Get the SubmodelElement from the AAS
let pluginSubmodelElements = pluginData.submodelElements;
// add pathes and id's to the SubmodelElements
Expand Down
82 changes: 75 additions & 7 deletions aas-web-ui/src/components/ComponentVisualization.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,47 @@
</v-card-title>
<v-divider></v-divider>
<v-card-text
v-if="submodelElementData && Object.keys(submodelElementData).length > 0"
v-if="
SelectedNode && Object.keys(SelectedNode).length > 0 && Object.keys(submodelElementData).length > 0
"
style="overflow-y: auto; height: calc(100svh - 170px)">
<!-- Add Plugins matched on SemanticId's inside the SubmodelEntrypoint -->
<SubmodelEntrypoint
:submodel-element-data="submodelElementData"
:selected-node="SelectedNodeToTransfer"></SubmodelEntrypoint>
<template v-if="submodelElementData.modelType == 'File' || submodelElementData.modelType == 'Blob'">
<ImagePreview
v-if="submodelElementData.contentType && submodelElementData.contentType.includes('image')"
:submodel-element-data="submodelElementData"></ImagePreview>
<PDFPreview
v-if="submodelElementData.contentType && submodelElementData.contentType.includes('pdf')"
:submodel-element-data="submodelElementData"></PDFPreview>
<CADPreview
v-if="
submodelElementData.contentType &&
(submodelElementData.contentType.includes('sla') ||
submodelElementData.contentType.includes('stl') ||
submodelElementData.contentType.includes('model') ||
submodelElementData.contentType.includes('obj') ||
submodelElementData.contentType.includes('gltf'))
"
:submodel-element-data="submodelElementData"></CADPreview>
</template>
<template v-else>
<template
v-if="
submodelElementData.semanticId &&
submodelElementData.semanticId.keys &&
submodelElementData.semanticId.keys.length > 0
">
<component
:is="plugin.name"
v-for="(plugin, index) in filteredPlugins"
:key="index"
:submodel-element-data="submodelElementData"
>{{ plugin.name }}</component
>
</template>
<GenericDataVisu
v-if="viewerMode && filteredPlugins.length === 0"
:submodel-element-data="submodelElementData.submodelElements"></GenericDataVisu>
</template>
</v-card-text>
</v-card>
</v-container>
Expand All @@ -37,7 +72,10 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import SubmodelEntrypoint from '@/components/SubmodelPlugins/_SubmodelEntrypoint.vue';
import CADPreview from '@/components/Plugins/CADPreview.vue';
import ImagePreview from '@/components/Plugins/ImagePreview.vue';
import PDFPreview from '@/components/Plugins/PDFPreview.vue';
import GenericDataVisu from '@/components/UIComponents/GenericDataVisu.vue';
import RequestHandling from '@/mixins/RequestHandling';
import SubmodelElementHandling from '@/mixins/SubmodelElementHandling';
import { useAASStore } from '@/store/AASDataStore';
Expand All @@ -46,7 +84,10 @@
export default defineComponent({
name: 'ComponentVisualization',
components: {
SubmodelEntrypoint, // Submodel Plugin Entrypoint Component
GenericDataVisu,
ImagePreview,
PDFPreview,
CADPreview,
},
mixins: [RequestHandling, SubmodelElementHandling],
Expand Down Expand Up @@ -113,6 +154,33 @@
isMobile() {
return this.navigationStore.getIsMobile;
},
importedPlugins() {
return this.navigationStore.getPlugins;
},
// Filtered Plugins
filteredPlugins() {
return this.importedPlugins.filter((plugin: any) => {
if (!plugin.semanticId) return false;
if (typeof plugin.semanticId === 'string') {
return this.checkSemanticId(this.submodelElementData, plugin.semanticId);
} else if (plugin.semanticId.constructor === Array) {
for (const pluginSemanticId of plugin.semanticId) {
if (this.checkSemanticId(this.submodelElementData, pluginSemanticId)) return true;
}
return false;
}
return false;
});
},
// return if in viewer mode
viewerMode() {
// check if the route name is aasviewer
return this.route.name === 'AASViewer' || this.route.name === 'ComponentVisualization';
},
},
watch: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import TimeSeriesData from '@/components/SubmodelPlugins/TimeSeriesData.vue';
import TimeSeriesData from '@/components/Plugins/Submodels/TimeSeriesData.vue';
import DashboardHandling from '@/mixins/DashboardHandling';
import SubmodelElementHandling from '@/mixins/SubmodelElementHandling';
import { useEnvStore } from '@/store/EnvironmentStore';
Expand Down
2 changes: 1 addition & 1 deletion aas-web-ui/src/components/Dashboard/DashboardElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

<script lang="ts">
import { defineComponent } from 'vue';
import TimeSeriesData from '@/components/SubmodelPlugins/TimeSeriesData.vue';
import TimeSeriesData from '@/components/Plugins/Submodels/TimeSeriesData.vue';
import DashboardHandling from '@/mixins/DashboardHandling';
import SubmodelElementHandling from '@/mixins/SubmodelElementHandling';
import { useEnvStore } from '@/store/EnvironmentStore';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,7 @@
export default defineComponent({
name: 'HTWFuehrungskomponente',
semanticId: 'http://htw-berlin.de/smc_statemachine',
mixins: [RequestHandling, SubmodelElementHandling],
props: ['submodelElementData', 'selectedNode'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
export default defineComponent({
name: 'JSONArrayProperty',
semanticId: 'http://iese.fraunhofer.de/prop_jsonarray',
props: ['submodelElementData'],
setup() {
Expand Down Expand Up @@ -112,10 +113,10 @@
methods: {
initChart() {
// Check if a Node is selected
if (Object.keys(this.submodelElementData).length == 0) {
return;
}
let chartData = JSON.parse(this.submodelElementData.value); // parse the value of the SubmodelElement
let seriesName = this.submodelElementData.idShort; // get the idShort of the SubmodelElement
// check if the value is an array or an object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
export default defineComponent({
name: 'BillsOfMaterial',
semanticId: [
'https://admin-shell.io/idta/HierarchicalStructures/1/0/Submodel',
'https://admin-shell.io/idta/HierarchicalStructures/1/1/Submodel',
],
mixins: [RequestHandling, SubmodelElementHandling],
props: ['submodelElementData'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
export default defineComponent({
name: 'ContactInformation',
semanticId: 'https://admin-shell.io/zvei/nameplate/1/0/ContactInformations',
mixins: [RequestHandling, SubmodelElementHandling],
props: ['submodelElementData'],
Expand Down Expand Up @@ -131,13 +132,19 @@
methods: {
async initContactInformation() {
this.loading = true;
// console.log('Initialize Contact Information Plugin: ', this.submodelElementData);
if (Object.keys(this.submodelElementData).length == 0) {
this.contactInformationData = {};
this.loading = false;
return;
}
let submodelElementData = { ...this.submodelElementData };
submodelElementData = await this.calculateSubmodelElementPathes(
this.contactInformationData = await this.calculateSubmodelElementPathes(
submodelElementData,
this.SelectedNode.path
);
this.contactInformationData = submodelElementData;
// create array of contacts
let contacts = this.contactInformationData.submodelElements.filter((element: any) => {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
export default defineComponent({
name: 'DigitalNameplate',
semanticId: 'https://admin-shell.io/zvei/nameplate/2/0/Nameplate',
components: {
GenericDataVisu,
Expand Down Expand Up @@ -270,16 +271,19 @@
// Function to initialize the Digital Nameplate
async initializeDigitalNameplate() {
this.loading = true;
// Check if a Node is selected
if (Object.keys(this.submodelElementData).length == 0) {
this.digitalNameplateData = {}; // Reset the DigitalNameplate Data when no Node is selected
this.digitalNameplateData = {};
this.loading = false;
return;
}
let digitalNameplateData = { ...this.submodelElementData }; // create local copy of the Nameplate Object
this.digitalNameplateData = await this.calculateSubmodelElementPathes(
digitalNameplateData,
this.SelectedNode.path
); // Set the DigitalNameplate Data
);
// console.log('Digital Nameplate Data:', this.digitalNameplateData);
this.extractProductProperties(digitalNameplateData); // Extract the Product Properties
this.extractManufacturerProperties(digitalNameplateData); // Extract the Manufacturer Properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,16 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { useTheme } from 'vuetify';
import CADPreview from '@/components/Plugins/CADPreview.vue';
import ImagePreview from '@/components/Plugins/ImagePreview.vue';
import PDFPreview from '@/components/Plugins/PDFPreview.vue';
import RequestHandling from '@/mixins/RequestHandling';
import SubmodelElementHandling from '@/mixins/SubmodelElementHandling';
import { useAASStore } from '@/store/AASDataStore';
import CADPreview from './CADPreview.vue';
import ImagePreview from './ImagePreview.vue';
import PDFPreview from './PDFPreview.vue';
export default defineComponent({
name: 'HandoverDocumentation',
semanticId: '0173-1#01-AHF578#001',
components: {
ImagePreview,
PDFPreview,
Expand Down Expand Up @@ -321,13 +322,18 @@
methods: {
async initHandoverDocumentation() {
this.loading = true;
// console.log('Initialize Handover Documentation Plugin: ', this.submodelElementData);
if (Object.keys(this.submodelElementData).length == 0) {
this.handoverDocuData = {};
this.loading = false;
return;
}
let submodelElementData = { ...this.submodelElementData };
submodelElementData = await this.calculateSubmodelElementPathes(
this.handoverDocuData = await this.calculateSubmodelElementPathes(
submodelElementData,
this.SelectedNode.path
);
this.handoverDocuData = submodelElementData;
// create array of documents
let documents = this.handoverDocuData.submodelElements.filter((element: any) => {
return this.checkSemanticId(element, '0173-1#02-ABI500#001/0173-1#01-AHF579#001');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@
export default defineComponent({
name: 'TechnicalData',
semanticId: 'https://admin-shell.io/ZVEI/TechnicalData/Submodel/1/2',
components: {
GenericDataVisu,
GenericDataTableView,
Expand Down Expand Up @@ -307,11 +308,13 @@
methods: {
async initTechnicalData() {
this.loading = true;
// Check if a Node is selected
if (Object.keys(this.submodelElementData).length == 0) {
this.technicalData = {}; // Reset the DigitalNameplate Data when no Node is selected
this.technicalData = {};
this.loading = false;
return;
}
let technicalData = { ...this.submodelElementData }; // create local copy of the Nameplate Object
this.technicalData = await this.calculateSubmodelElementPathes(technicalData, this.SelectedNode.path); // Set the DigitalNameplate Data
this.extractGeneralProperties(technicalData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@
export default defineComponent({
name: 'TimeSeriesData',
semanticId: 'https://admin-shell.io/idta/TimeSeries/1/1',
components: {
LineChart,
AreaChart,
Expand Down Expand Up @@ -329,11 +330,11 @@
methods: {
// Function to initialize the TimeSeriesData Plugin
initializeTimeSeriesData() {
// Check if a Node is selected
if (Object.keys(this.submodelElementData).length == 0) {
this.timeSeriesData = {}; // Reset the TimeSeriesData when no Node is selected
this.timeSeriesData = {};
return;
}
let timeSeriesData = { ...this.submodelElementData }; // create local copy of the TimeSeriesData
this.timeSeriesData = timeSeriesData; // set the local copy to the data object
// get the collection for segments
Expand Down
Loading

0 comments on commit 29f913d

Please sign in to comment.