Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visualization component loading #106

Merged
merged 11 commits into from
Dec 7, 2024
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
Loading