Skip to content

Commit

Permalink
Cleanup AASStore, #2 (eclipse-basyx#230)
Browse files Browse the repository at this point in the history
* Preserve query parameter for switching between AAS Viewer/Editor, Submodel Viewer, About page, ...
Fixes eclipse-basyx#166

* Using anchor tag `to` for declarative navigation

* Undo changes in AppNavigation/MainMenu

* Adds state for url query

* Adds route guard to preserver query parameter

* Implements fetchSme() and fetchAndDispatchSme() in SMRepositoryClient

* Usage of fetchAndDispathSme() in App.vue

* Usage of fetchAndDispatchSme() in route guard

* Adds env variable EDITOR_ID_PREFIX

* Fix type of <TextInput> for GlobalAssetId

* Extends generateIri()

* Adds tests for generateIri()

* Strip idShortPath of path URL query in case of switching to SubmodelViewer

* Updates router guard

* Updates IDUtils tests for generateIri()

* Udpates jsdoc of generateCustomId()

* Adds tests for generateCustomId()

* Get idPrefix from EnvStore --> pinia error

* Transfers IDUtils to composable

* Fixes usage of IDUtils composable in AAS.vue

* Avoids AASStore actions within Clients

* Removes updatedNode from AASStore

* Harmonizes router.push() calls

* Removes realTimeObject from AASStore

* Simplifies SubmodelElementView.vue

* Fix SMEView

* Fixes fetchAndDispatchAasById() in AAS.vue

* Fixes composables use function names

* Harmonizes call of composable use functions

* Fix "AutoSync"

* Improves SM/SMEHandling

* Improves AAS/SM/SMEHandling

* Fixes timestamps

* Fixes showing of SME id in SMEView

* Fixes fetching of CDs
  • Loading branch information
seicke authored Jan 10, 2025
1 parent 85224eb commit a00991f
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 158 deletions.
5 changes: 3 additions & 2 deletions aas-web-ui/src/components/AASTreeview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import { useRequestHandling } from '@/composables/RequestHandling';
import { useAASStore } from '@/store/AASDataStore';
import { useNavigationStore } from '@/store/NavigationStore';
import { formatDate } from '@/utils/DateUtils';
import { extractEndpointHref } from '@/utils/DescriptorUtils';
import { URLEncode } from '@/utils/EncodeDecodeUtils';
import { nameToDisplay } from '@/utils/ReferableUtils';
Expand Down Expand Up @@ -185,6 +186,7 @@
submodel.isActive = false;
// set the Path of the Submodel
submodel.path = path;
submodel.timestamp = formatDate(new Date());
// check if submodel has SubmodelElements
if (submodel.submodelElements && submodel.submodelElements.length > 0) {
// recursively create treestructure for contained submodelElements
Expand Down Expand Up @@ -233,6 +235,7 @@
// set the active State of each Element
element.isActive = false;
// set the Parent of each Element
element.timestamp = formatDate(new Date());
element.parent = parent;
// set the Path of each Element
if (element.parent.modelType == 'Submodel') {
Expand Down Expand Up @@ -324,8 +327,6 @@
if (!foundNode) {
foundNode = true;
element.isActive = true;
aasStore.dispatchSelectedNode(element);
aasStore.dispatchRealTimeObject(element);
}
// if prop showChildren exists, set it to true
if ('showChildren' in element) {
Expand Down
47 changes: 5 additions & 42 deletions aas-web-ui/src/components/ComponentVisualization.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,9 @@
import { computed, onMounted, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAASHandling } from '@/composables/AASHandling';
import { useRequestHandling } from '@/composables/RequestHandling';
import { useSMEHandling } from '@/composables/SMEHandling';
import { useAASStore } from '@/store/AASDataStore';
import { useNavigationStore } from '@/store/NavigationStore';
import { formatDate } from '@/utils/DateUtils';
import { nameToDisplay } from '@/utils/ReferableUtils';
import { checkSemanticId } from '@/utils/SemanticIdUtils';
Expand All @@ -99,8 +98,8 @@
const router = useRouter();
// Composables
const { getRequest } = useRequestHandling();
const { fetchAndDispatchAas } = useAASHandling();
const { fetchAndDispatchSme } = useSMEHandling();
// Stores
const navigationStore = useNavigationStore();
Expand All @@ -114,7 +113,6 @@
const submodelRegistryServerURL = computed(() => navigationStore.getSubmodelRegistryURL);
const selectedAAS = computed(() => aasStore.getSelectedAAS);
const selectedNode = computed(() => aasStore.getSelectedNode);
const realTimeObject = computed(() => aasStore.getRealTimeObject);
const isMobile = computed(() => navigationStore.getIsMobile);
const importedPlugins = computed(() => navigationStore.getPlugins);
const filteredPlugins = computed(() => {
Expand Down Expand Up @@ -201,13 +199,6 @@
initializeView(); // initialize list
});
// Watch for changes in the RealTimeDataObject and (re-)initialize the Component
watch(realTimeObject, () => {
// clear old submodelElementData
submodelElementData.value = {};
initializeView(); // initialize list
});
onMounted(() => {
if (Object.keys(selectedNode.value).length > 0 && isMobile.value) {
// initialize if component got mounted on mobile devices (needed there because it is rendered in a separate view)
Expand All @@ -227,11 +218,11 @@
function initializeView() {
// console.log('Selected Node: ', this.realTimeObject);
// Check if a Node is selected
if (Object.keys(realTimeObject.value).length === 0) {
if (Object.keys(selectedNode.value).length === 0) {
submodelElementData.value = {}; // Reset the SubmodelElement Data when no Node is selected
return;
}
submodelElementData.value = { ...realTimeObject.value }; // create local copy of the SubmodelElement Object
submodelElementData.value = { ...selectedNode.value }; // create local copy of the SubmodelElement Object
// console.log('SubmodelElement Data (ComponentVisualization): ', this.submodelElementData);
}
Expand All @@ -242,35 +233,7 @@
if (aasEndpoint && path) {
await fetchAndDispatchAas(aasEndpoint);
// Request the selected SubmodelElement
let context = 'retrieving SubmodelElement';
let disableMessage = true;
getRequest(path, context, disableMessage).then((response: any) => {
if (response.success) {
// execute if the Request was successful
response.data.timestamp = formatDate(new Date()); // add timestamp to the SubmodelElement Data
response.data.path = path; // add the path to the SubmodelElement Data
response.data.isActive = true; // add the isActive Property to the SubmodelElement Data
// console.log('SubmodelElement Data: ', response.data)
// dispatch the SubmodelElementPath set by the URL to the store
submodelElementData.value = response.data;
aasStore.dispatchRealTimeObject(submodelElementData.value);
} else {
// execute if the Request failed
if (Object.keys(response.data).length === 0) {
// don't copy the static SubmodelElement Data if no Node is selected or Node is invalid
navigationStore.dispatchSnackbar({
status: true,
timeout: 60000,
color: 'error',
btnColor: 'buttonText',
text: 'No valid SubmodelElement under the given Path',
}); // Show Error Snackbar
return;
}
}
});
await fetchAndDispatchSme(path);
}
}
Expand Down
110 changes: 25 additions & 85 deletions aas-web-ui/src/components/SubmodelElementView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,24 +138,13 @@
<InvalidElement v-else :invalid-element-object="submodelElementData"></InvalidElement>
</v-list>
<!-- ConceptDescriptions -->
<v-divider
v-if="
submodelElementData.conceptDescriptions &&
submodelElementData.conceptDescriptions.length > 0
"></v-divider>
<v-list
v-if="
submodelElementData.conceptDescriptions &&
submodelElementData.conceptDescriptions.length > 0
"
nav>
<v-divider v-if="conceptDescriptions && conceptDescriptions.length > 0"></v-divider>
<v-list v-if="conceptDescriptions && conceptDescriptions.length > 0" nav>
<v-list-item
v-for="(conceptDescription, index) in submodelElementData.conceptDescriptions"
v-for="(conceptDescription, index) in conceptDescriptions"
:key="conceptDescription.id">
<ConceptDescription :concept-description-object="conceptDescription"></ConceptDescription>
<v-divider
v-if="index !== submodelElementData.conceptDescriptions.length - 1"
class="mt-2"></v-divider>
<v-divider v-if="index !== conceptDescriptions.length - 1" class="mt-2"></v-divider>
</v-list-item>
</v-list>
<!-- Last Sync -->
Expand Down Expand Up @@ -193,10 +182,9 @@
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useConceptDescriptionHandling } from '@/composables/ConceptDescriptionHandling';
import { useRequestHandling } from '@/composables/RequestHandling';
import { useSMEHandling } from '@/composables/SMEHandling';
import { useAASStore } from '@/store/AASDataStore';
import { useNavigationStore } from '@/store/NavigationStore';
import { formatDate } from '@/utils/DateUtils';
// Vue Router
const route = useRoute();
Expand All @@ -207,10 +195,11 @@
// Composables
const { getConceptDescriptions } = useConceptDescriptionHandling();
const { getRequest } = useRequestHandling();
const { fetchAndDispatchSme } = useSMEHandling();
// Data
const submodelElementData = ref({} as any);
const conceptDescriptions = ref([] as Array<any>);
const requestInterval = ref<number | undefined>(undefined); // interval to send requests to the AAS
// Computed Properties
Expand All @@ -227,8 +216,7 @@
() => aasRegistryServerURL.value,
() => {
if (!aasRegistryServerURL.value) {
submodelElementData.value = {};
aasStore.dispatchRealTimeObject(submodelElementData);
initializeView();
}
}
);
Expand All @@ -238,8 +226,7 @@
() => submodelRegistryServerURL.value,
() => {
if (!submodelRegistryServerURL.value) {
submodelElementData.value = {};
aasStore.dispatchRealTimeObject(submodelElementData);
initializeView();
}
}
);
Expand All @@ -248,18 +235,15 @@
watch(
() => selectedAAS.value,
() => {
submodelElementData.value = {};
aasStore.dispatchRealTimeObject(submodelElementData);
initializeView();
}
);
// Watch for changes in the selected Node and (re-)initialize the Component
watch(
() => selectedNode.value,
() => {
// clear old submodelElementData
submodelElementData.value = {};
initializeView(true);
initializeView();
},
{ deep: true }
);
Expand All @@ -273,7 +257,7 @@
// create new interval
requestInterval.value = window.setInterval(() => {
if (Object.keys(selectedNode.value).length > 0) {
initializeView();
fetchAndDispatchSme(selectedNode.value.path, false);
}
}, autoSync.value.interval);
} else {
Expand All @@ -288,76 +272,32 @@
// create new interval
requestInterval.value = window.setInterval(() => {
if (Object.keys(selectedNode.value).length > 0) {
initializeView();
fetchAndDispatchSme(selectedNode.value.path, false);
}
}, autoSync.value.interval);
} else {
initializeView(true);
initializeView();
}
});
onBeforeUnmount(() => {
window.clearInterval(requestInterval.value); // clear old interval
});
function initializeView(withConceptDescriptions = false) {
// console.log('selected Node: ', selectedNode.value);
// Check if a Node is selected
async function initializeView(): Promise<void> {
if (Object.keys(selectedNode.value).length === 0) {
submodelElementData.value = {}; // Reset the SubmodelElement Data when no Node is selected
submodelElementData.value = {};
return;
}
// Request the selected SubmodelElement
const path = selectedNode.value.path;
const context = 'retrieving SubmodelElement';
const disableMessage = true;
getRequest(path, context, disableMessage).then((response: any) => {
// save Concept Descriptions before overwriting the SubmodelElement Data
let conceptDescriptions = submodelElementData.value.conceptDescriptions;
if (response.success && (response.data?.id || response.data?.idShort)) {
// execute if the Request was successful
response.data.timestamp = formatDate(new Date()); // add timestamp to the SubmodelElement Data
response.data.path = selectedNode.value.path; // add the path to the SubmodelElement Data
// console.log('SubmodelElement Data: ', response.data);
submodelElementData.value = response.data;
} else {
// execute if the Request failed
// show the static SubmodelElement Data from the store if the Request failed (the timestamp should show that the data is outdated)
submodelElementData.value = {}; // Reset the SubmodelElement Data when Node couldn't be retrieved
if (Object.keys(selectedNode.value).length === 0) {
// don't copy the static SubmodelElement Data if no Node is selected or Node is invalid
navigationStore.dispatchSnackbar({
status: true,
timeout: 60000,
color: 'error',
btnColor: 'buttonText',
text: 'No valid SubmodelElement under the given Path',
}); // Show Error Snackbar
return;
}
submodelElementData.value = { ...selectedNode.value }; // copy the static SubmodelElement Data from the store
submodelElementData.value.timestamp = 'no sync';
submodelElementData.value.path = selectedNode.value.path; // add the path to the SubmodelElement Data
}
if (withConceptDescriptions) {
getCD(); // fetch Concept Descriptions for the SubmodelElement
} else {
submodelElementData.value.conceptDescriptions = conceptDescriptions; // add Concept Descriptions to the SubmodelElement Data
}
// console.log('SubmodelElement Data (SubmodelElementView): ', this.submodelElementData)
// add SubmodelElement Data to the store (as RealTimeDataObject)
aasStore.dispatchRealTimeObject(submodelElementData);
});
}
// Get Concept Descriptions for the SubmodelElement from the ConceptDescription Repository
function getCD() {
getConceptDescriptions(selectedNode.value).then((response: any) => {
// console.log('ConceptDescription: ', response)
// add ConceptDescription to the SubmodelElement Data
if (response) {
submodelElementData.value.conceptDescriptions = response;
}
});
submodelElementData.value = { ...selectedNode.value }; // create local copy
if (
!conceptDescriptions.value ||
!Array.isArray(conceptDescriptions.value) ||
conceptDescriptions.value.length === 0
) {
conceptDescriptions.value = await getConceptDescriptions(selectedNode.value);
}
}
</script>
11 changes: 5 additions & 6 deletions aas-web-ui/src/components/SubmodelList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import { useRequestHandling } from '@/composables/RequestHandling';
import { useAASStore } from '@/store/AASDataStore';
import { useNavigationStore } from '@/store/NavigationStore';
import { formatDate } from '@/utils/DateUtils';
import { extractEndpointHref } from '@/utils/DescriptorUtils';
import { URLEncode } from '@/utils/EncodeDecodeUtils';
import { nameToDisplay } from '@/utils/ReferableUtils';
Expand Down Expand Up @@ -151,8 +152,8 @@
fetchedSubmodelData.forEach((submodel: any) => {
if (submodel.path === initialNode.value.path) {
submodel.isActive = true;
submodel.timestamp = formatDate(new Date());
aasStore.dispatchSelectedNode(submodel);
aasStore.dispatchRealTimeObject(submodel);
}
});
initialUpdate.value = false;
Expand Down Expand Up @@ -233,7 +234,7 @@
function toggleNode(submodel: any) {
// console.log('Selected Submodel: ', submodel);
// dublicate the selected Node Object
let localSubmodel = submodel;
let localSubmodel = { ...submodel };
localSubmodel.isActive = true;
// set the isActive Property of all other Submodels to false
submodelData.value.forEach((submodel: any) => {
Expand Down Expand Up @@ -262,16 +263,14 @@
},
});
}
aasStore.dispatchSelectedNode(localSubmodel);
} else {
// remove the path query from the Route entirely
let query = { ...route.query };
delete query.path;
router.push({ query: query });
aasStore.dispatchSelectedNode({});
}
// dispatch the selected Node to the store
aasStore.dispatchSelectedNode(localSubmodel);
// add Submodel to the store (as RealTimeDataObject)
aasStore.dispatchRealTimeObject(localSubmodel);
}
// Function to initialize the Submodel List with the Route Parameters
Expand Down
1 change: 1 addition & 0 deletions aas-web-ui/src/components/UIComponents/VTreeview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@
},
});
}
if (localItem.modelType !== 'Submodel') delete localItem.id;
this.aasStore.dispatchSelectedNode(localItem);
} else {
// remove the path query from the Route entirely
Expand Down
4 changes: 3 additions & 1 deletion aas-web-ui/src/composables/AASHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export function useAASHandling() {
async function fetchAndDispatchAas(aasEndpoint: string): Promise<void> {
// console.log('fetchAndDispatchAas()', aasEndpoint);

if (aasEndpoint.trim() === '') return;
aasEndpoint = aasEndpoint.trim();

if (aasEndpoint === '') return;

const aas = await aasRepoClient.fetchAas(aasEndpoint);
// console.log('fetchAndDispatchAas()', aasEndpoint, 'aas', aas);
Expand Down
2 changes: 1 addition & 1 deletion aas-web-ui/src/composables/Client/SMRepositoryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function useSMRepositoryClient() {
if (submodelElementPath.trim() === '') return failResponse;

if (!submodelElementPath.includes('/submodel-elements/')) {
// Not valid SME path, maybe just SM endpoint
// No valid SME path, maybe just SM endpoint
return fetchSm(submodelElementPath);
}

Expand Down
Loading

0 comments on commit a00991f

Please sign in to comment.