diff --git a/aas-web-ui/src/App.vue b/aas-web-ui/src/App.vue index 8317a29..bc90050 100644 --- a/aas-web-ui/src/App.vue +++ b/aas-web-ui/src/App.vue @@ -13,161 +13,150 @@ - - + diff --git a/aas-web-ui/src/composables/ChartHandling.ts b/aas-web-ui/src/composables/ChartHandling.ts new file mode 100644 index 0000000..d4377df --- /dev/null +++ b/aas-web-ui/src/composables/ChartHandling.ts @@ -0,0 +1,41 @@ +import { useConceptDescriptionHandling } from '@/composables/ConceptDescriptionHandling'; + +export function useChartHandling() { + const { unitSuffix } = useConceptDescriptionHandling(); + + function prepareYValueTooltip(chartData: any, yVariables: any) { + return chartData.map((_series: any, index: number) => { + // Use optional chaining and nullish coalescing to simplify the retrieval of the unit + let unit = ''; + if (yVariables[index]) { + unit = unitSuffix(yVariables[index]); + } + return { + formatter: (value: any) => `${value} ${unit}`, + }; + }); + } + + function prepareLegend(yVariables: any) { + return { + formatter: (seriesName: any, opts: any) => { + let unit = ''; + const index = opts.seriesIndex; + + // check if the yVariable exists + if (yVariables.length > index) { + // check if the yVariable has an unit (embeddedDataSpecification) -> take the first one (TODO: make this more generic in the future) + if (yVariables[index]) { + unit = '[' + unitSuffix(yVariables[index]) + ']'; + } + } + return seriesName + ' ' + unit; + }, + }; + } + + return { + prepareYValueTooltip, + prepareLegend, + }; +} diff --git a/aas-web-ui/src/composables/Client/AASRegistryClient.ts b/aas-web-ui/src/composables/Client/AASRegistryClient.ts new file mode 100644 index 0000000..104bef5 --- /dev/null +++ b/aas-web-ui/src/composables/Client/AASRegistryClient.ts @@ -0,0 +1,76 @@ +import { computed } from 'vue'; +import { useRequestHandling } from '@/composables/RequestHandling'; +import { useNavigationStore } from '@/store/NavigationStore'; +import { URLEncode } from '@/utils/EncodeDecodeUtils'; + +export function useAASRegistryClient() { + const { getRequest } = useRequestHandling(); + + const navigationStore = useNavigationStore(); + + const aasRegistryUrl = computed(() => navigationStore.getAASRegistryURL); + + // Fetch List of all available AAS Descriptors + async function fetchAasDescriptorList(): Promise> { + const failResponse = [] as Array; + + let aasRegUrl = aasRegistryUrl.value; + if (aasRegUrl.trim() === '') return failResponse; + if (!aasRegUrl.includes('/shell-descriptors')) { + aasRegUrl += '/shell-descriptors'; + } + + const aasRegistryPath = aasRegUrl; + const aasRegistryContext = 'retrieving all AAS Descriptors'; + const disableMessage = false; + try { + const aasRegistryResponse = await getRequest(aasRegistryPath, aasRegistryContext, disableMessage); + if ( + aasRegistryResponse.success && + aasRegistryResponse.data.result && + aasRegistryResponse.data.result.length > 0 + ) { + return aasRegistryResponse.data.result; + } + } catch { + // handle error + return failResponse; + } + + return failResponse; + } + + // Fetch AAS Descriptor by AAS ID with AAS Registry + async function fetchAasDescriptorById(aasId: string): Promise { + const failResponse = {} as any; + + let aasRegUrl = aasRegistryUrl.value; + if (aasRegUrl.trim() === '') return failResponse; + if (!aasRegUrl.includes('/shell-descriptors')) { + aasRegUrl += '/shell-descriptors'; + } + + const aasRegistryPath = aasRegUrl + '/' + URLEncode(aasId); + const aasRegistryContext = 'retrieving AAS Descriptor'; + const disableMessage = false; + try { + const aasRegistryResponse = await getRequest(aasRegistryPath, aasRegistryContext, disableMessage); + if ( + aasRegistryResponse?.success && + aasRegistryResponse?.data && + Object.keys(aasRegistryResponse?.data).length > 0 + ) { + return aasRegistryResponse.data; + } + } catch { + // handle error + return failResponse; + } + return failResponse; + } + + return { + fetchAasDescriptorList, + fetchAasDescriptorById, + }; +} diff --git a/aas-web-ui/src/composables/Client/AASRepositoryClient.ts b/aas-web-ui/src/composables/Client/AASRepositoryClient.ts new file mode 100644 index 0000000..e762f9d --- /dev/null +++ b/aas-web-ui/src/composables/Client/AASRepositoryClient.ts @@ -0,0 +1,102 @@ +import { computed } from 'vue'; +import { useAASRegistryClient } from '@/composables/Client/AASRegistryClient'; +import { useRequestHandling } from '@/composables/RequestHandling'; +import { useAASStore } from '@/store/AASDataStore'; +import { useNavigationStore } from '@/store/NavigationStore'; +import { extractEndpointHref } from '@/utils/DescriptorUtils'; + +export function useAASRepositoryClient() { + const { getRequest } = useRequestHandling(); + const { fetchAasDescriptorById } = useAASRegistryClient(); + + const aasStore = useAASStore(); + const navigationStore = useNavigationStore(); + + const aasRepositoryUrl = computed(() => navigationStore.getAASRepoURL); + + // Fetch List of all available AAS + async function fetchAasList(): Promise> { + const failResponse = [] as Array; + + let aasRepoUrl = aasRepositoryUrl.value; + if (aasRepoUrl.trim() === '') return failResponse; + if (!aasRepoUrl.includes('/shells')) { + aasRepoUrl += '/shells'; + } + + const aasRepoPath = aasRepoUrl; + const aasRepoContext = 'retrieving all AAS'; + const disableMessage = false; + try { + const aasRepoResponse = await getRequest(aasRepoPath, aasRepoContext, disableMessage); + if (aasRepoResponse.success && aasRepoResponse.data.result && aasRepoResponse.data.result.length > 0) { + return aasRepoResponse.data.result; + } + } catch { + // handle error + return failResponse; + } + return failResponse; + } + + // Fetch AAS from AAS Repo (with the help of the AAS Registry) + async function fetchAasById(aasId: string): Promise { + const failResponse = {} as any; + + if (aasId.trim() === '') return failResponse; + + const aasDescriptor = await fetchAasDescriptorById(aasId); + + if (aasDescriptor && Object.keys(aasDescriptor).length > 0) { + const aasEndpoint = extractEndpointHref(aasDescriptor, 'AAS-3.0'); + return fetchAas(aasEndpoint); + } + + return failResponse; + } + + // Fetch AAS from (AAS Repo) Endpoint + async function fetchAas(aasEndpoint: string): Promise { + // console.log('fetchAas()', aasEndpoint); + const failResponse = {} as any; + + if (aasEndpoint.trim() === '') return failResponse; + + const aasRepoPath = aasEndpoint; + const aasRepoContext = 'retrieving AAS Data'; + const disableMessage = true; + try { + const aasRepoResponse = await getRequest(aasRepoPath, aasRepoContext, disableMessage); + if (aasRepoResponse?.success && aasRepoResponse?.data && Object.keys(aasRepoResponse?.data).length > 0) { + const aas = aasRepoResponse.data; + // console.log('fetchAas()', aasEndpoint, 'aas', aas); + + // Add endpoint to AAS + aas.endpoints = [{ protocolInformation: { href: aasEndpoint }, interface: 'AAS-3.0' }]; + + return aas; + } + } catch { + return failResponse; + } + + return failResponse; + } + + // Fetch and Dispatch AAS from (AAS Repo) Endpoint + async function fetchAndDispatchAas(aasEndpoint: string) { + if (aasEndpoint.trim() === '') return; + + const aas = await fetchAas(aasEndpoint); + // console.log('fetchAndDispatchAas()', aasEndpoint, 'aas', aas); + + aasStore.dispatchSelectedAAS(aas); + } + + return { + fetchAasList, + fetchAasById, + fetchAas, + fetchAndDispatchAas, + }; +} diff --git a/aas-web-ui/src/composables/Client/SMRegistryClient.ts b/aas-web-ui/src/composables/Client/SMRegistryClient.ts new file mode 100644 index 0000000..4d86b04 --- /dev/null +++ b/aas-web-ui/src/composables/Client/SMRegistryClient.ts @@ -0,0 +1,75 @@ +import { computed } from 'vue'; +import { useRequestHandling } from '@/composables/RequestHandling'; +import { useNavigationStore } from '@/store/NavigationStore'; +import { URLEncode } from '@/utils/EncodeDecodeUtils'; + +export function useSMRegistryClient() { + const { getRequest } = useRequestHandling(); + + const navigationStore = useNavigationStore(); + + const submodelRegistryUrl = computed(() => navigationStore.getSubmodelRegistryURL); + + // Fetch List of all available SM Descriptors + async function fetchSmDescriptorList(): Promise> { + const failResponse = [] as Array; + + let smRegistryUrl = submodelRegistryUrl.value; + if (smRegistryUrl.trim() === '') return failResponse; + if (!smRegistryUrl.includes('/submodel-descriptors')) { + smRegistryUrl += '/submodel-descriptors'; + } + + const smRegistryPath = smRegistryUrl; + const smRegistryContext = 'retrieving all SM Descriptors'; + const disableMessage = false; + try { + const smRegistryResponse = await getRequest(smRegistryPath, smRegistryContext, disableMessage); + if ( + smRegistryResponse.success && + smRegistryResponse.data.result && + smRegistryResponse.data.result.length > 0 + ) { + return smRegistryResponse.data.result; + } + } catch { + // handle error + return failResponse; + } + return failResponse; + } + + // Fetch SM Descriptor by SM ID with SM Registry + async function fetchSmDescriptorById(smId: string): Promise { + const failResponse = {} as any; + + let smRegistryUrl = submodelRegistryUrl.value; + if (smRegistryUrl.trim() === '') return failResponse; + if (!smRegistryUrl.includes('/shell-descriptors')) { + smRegistryUrl += '/shell-descriptors'; + } + + const smRegistryPath = smRegistryUrl + '/' + URLEncode(smId); + const smRegistryContext = 'retrieving SM Descriptor'; + const disableMessage = false; + try { + const smRegistryResponse = await getRequest(smRegistryPath, smRegistryContext, disableMessage); + if ( + smRegistryResponse?.success && + smRegistryResponse?.data && + Object.keys(smRegistryResponse?.data).length > 0 + ) { + return smRegistryResponse.data; + } + } catch { + // handle error + return failResponse; + } + return failResponse; + } + + return { + fetchSmDescriptorList, + fetchSmDescriptorById, + }; +} diff --git a/aas-web-ui/src/composables/Client/SMRepositoryClient.ts b/aas-web-ui/src/composables/Client/SMRepositoryClient.ts new file mode 100644 index 0000000..9947031 --- /dev/null +++ b/aas-web-ui/src/composables/Client/SMRepositoryClient.ts @@ -0,0 +1,90 @@ +import { computed } from 'vue'; +import { useSMRegistryClient } from '@/composables/Client/SMRegistryClient'; +import { useRequestHandling } from '@/composables/RequestHandling'; +import { useNavigationStore } from '@/store/NavigationStore'; +import { extractEndpointHref } from '@/utils/DescriptorUtils'; + +export function useSMRepositoryClient() { + const { getRequest } = useRequestHandling(); + const { fetchSmDescriptorById } = useSMRegistryClient(); + + const navigationStore = useNavigationStore(); + + const submodelRepoUrl = computed(() => navigationStore.getSubmodelRepoURL); + + // Fetch List of all available SM + async function fetchSmList(): Promise> { + const failResponse = [] as Array; + + let smRepoUrl = submodelRepoUrl.value; + if (smRepoUrl.trim() === '') return failResponse; + if (!smRepoUrl.includes('/shells')) { + smRepoUrl += '/shells'; + } + + const smRepoPath = smRepoUrl; + const smRepoContext = 'retrieving all SMs'; + const disableMessage = false; + try { + const smRepoResponse = await getRequest(smRepoPath, smRepoContext, disableMessage); + if (smRepoResponse.success && smRepoResponse.data.result && smRepoResponse.data.result.length > 0) { + return smRepoResponse.data.result; + } + } catch { + // handle error + return failResponse; + } + return failResponse; + } + + // Fetch SM from SM Repo (with the help of the SM Registry) + async function fetchSmById(smId: string): Promise { + // console.log('fetchAasById()', aasId); + const failResponse = {} as any; + + if (smId.trim() === '') return failResponse; + + const smDescriptor = await fetchSmDescriptorById(smId); + + if (smDescriptor && Object.keys(smDescriptor).length > 0) { + const smEndpoint = extractEndpointHref(smDescriptor, 'SUBMODEL-3.0'); + return fetchSm(smEndpoint); + } + + return failResponse; + } + + // Fetch SM from (SM Repo) Endpoint + async function fetchSm(smEndpoint: string): Promise { + // console.log('fetchSm()', aasEndpoint); + const failResponse = {} as any; + + if (smEndpoint.trim() === '') return failResponse; + + const smRepoPath = smEndpoint; + const smRepoContext = 'retrieving SM Data'; + const disableMessage = true; + try { + const smRepoResponse = await getRequest(smRepoPath, smRepoContext, disableMessage); + if (smRepoResponse?.success && smRepoResponse?.data && Object.keys(smRepoResponse?.data).length > 0) { + const sm = smRepoResponse.data; + // console.log('fetchSm()', smEndpoint, 'sm', sm); + + // Add endpoint to AAS + sm.endpoints = [{ protocolInformation: { href: smEndpoint }, interface: 'SUBMODEL-3.0' }]; + + return sm; + } + } catch { + return failResponse; + } + + return failResponse; + } + + return { + fetchSmList, + fetchSmById, + fetchSm, + }; +} diff --git a/aas-web-ui/src/composables/ConceptDescriptionHandling.ts b/aas-web-ui/src/composables/ConceptDescriptionHandling.ts new file mode 100644 index 0000000..4b9bba4 --- /dev/null +++ b/aas-web-ui/src/composables/ConceptDescriptionHandling.ts @@ -0,0 +1,149 @@ +import { computed } from 'vue'; +import { useRequestHandling } from '@/composables/RequestHandling'; +import { useNavigationStore } from '@/store/NavigationStore'; +import { URLEncode } from '@/utils/EncodeDecodeUtils'; +import { getEquivalentEclassSemanticIds, getEquivalentIriSemanticIds } from '@/utils/SemanticIdUtils'; + +export function useConceptDescriptionHandling() { + const { getRequest } = useRequestHandling(); + + const navigationStore = useNavigationStore(); + + const CDRepoURL = computed(() => { + return navigationStore.getConceptDescriptionRepoURL; + }); + + // Get the Unit from the EmbeddedDataSpecification of the ConceptDescription of the Property (if available) + function unitSuffix(prop: any) { + if (!prop.conceptDescriptions) { + getConceptDescriptions(prop).then((conceptDescriptions) => { + prop.conceptDescriptions = conceptDescriptions; + }); + } + if (!prop.conceptDescriptions || prop.conceptDescriptions.length == 0) { + return ''; + } + for (const conceptDescription of prop.conceptDescriptions) { + if (!conceptDescription.embeddedDataSpecifications) { + continue; + } + for (const embeddedDataSpecification of conceptDescription.embeddedDataSpecifications) { + if ( + embeddedDataSpecification.dataSpecificationContent && + embeddedDataSpecification.dataSpecificationContent.unit + ) { + return embeddedDataSpecification.dataSpecificationContent.unit; + } + } + } + return ''; + } + + // Get all ConceptDescriptions for the SubmodelElement from the ConceptDescription Repository + async function getConceptDescriptions(SelectedNode: any) { + let conceptDescriptionRepoURL = ''; + if (CDRepoURL.value && CDRepoURL.value != '') { + conceptDescriptionRepoURL = CDRepoURL.value; + } else { + return Promise.resolve([]); // Return an empty object wrapped in a resolved promise + } + + // return if no SemanticID is available + if (!SelectedNode.semanticId || !SelectedNode.semanticId.keys || SelectedNode.semanticId.keys.length == 0) { + return Promise.resolve([]); + } + + const semanticIdsToFetch = SelectedNode.semanticId.keys.map((key: any) => { + return key.value; + }); + + semanticIdsToFetch.forEach((semanticId: string) => { + if ( + semanticId.startsWith('0173-1#') || + semanticId.startsWith('0173/1///') || + semanticId.startsWith('https://api.eclass-cdp.com/0173-1') + ) { + semanticIdsToFetch.push(...getEquivalentEclassSemanticIds(semanticId)); + } else if (semanticId.startsWith('http://') || semanticId.startsWith('https://')) { + semanticIdsToFetch.push(...getEquivalentIriSemanticIds(semanticId)); + } + }); + + const semanticIdsUniqueToFetch = semanticIdsToFetch.filter( + (value: string, index: number, self: string) => self.indexOf(value) === index + ); + + const cdPromises = semanticIdsUniqueToFetch.map((semanticId: string) => { + const path = conceptDescriptionRepoURL + '/' + URLEncode(semanticId); + const context = 'retrieving ConceptDescriptions'; + const disableMessage = true; + + return getRequest(path, context, disableMessage).then((response: any) => { + if (response.success) { + // console.log('ConceptDescription Data: ', response.data); + const conceptDescription = response.data; + conceptDescription.path = path; + // Check if ConceptDescription has data to be displayed + if ( + (conceptDescription.displayName && conceptDescription.displayName.length > 0) || + (conceptDescription.description && conceptDescription.description.length > 0) || + (conceptDescription.embeddedDataSpecifications && + conceptDescription.embeddedDataSpecifications.length > 0) + ) { + return conceptDescription; + } + return {}; + } else { + return {}; + } + }); + }); + + let conceptDescriptions = await Promise.all(cdPromises); + conceptDescriptions = conceptDescriptions.filter( + (conceptDescription: any) => Object.keys(conceptDescription).length !== 0 + ); // Filter empty Objects + return conceptDescriptions; + } + + // Get the Definition from the EmbeddedDataSpecification of the ConceptDescription of the Property (if available) + function cdDefinition(prop: any) { + if (!prop.conceptDescriptions) { + getConceptDescriptions(prop).then((conceptDescriptions) => { + prop.conceptDescriptions = conceptDescriptions; + }); + } + if (!prop.conceptDescriptions || prop.conceptDescriptions.length == 0) { + return ''; + } + for (const conceptDescription of prop.conceptDescriptions) { + if (!conceptDescription.embeddedDataSpecifications) { + continue; + } + for (const embeddedDataSpecification of conceptDescription.embeddedDataSpecifications) { + if ( + embeddedDataSpecification.dataSpecificationContent && + embeddedDataSpecification.dataSpecificationContent.definition + ) { + const definitionEn = embeddedDataSpecification.dataSpecificationContent.definition.find( + (definition: any) => { + return definition.language === 'en' && definition.text !== ''; + } + ); + if (definitionEn && definitionEn.text) { + return definitionEn.text; + } + } else { + return ''; + } + } + } + return ''; + } + + return { + unitSuffix, + getConceptDescriptions, + cdDefinition, + }; +} diff --git a/aas-web-ui/src/composables/DashboardHandling.ts b/aas-web-ui/src/composables/DashboardHandling.ts new file mode 100644 index 0000000..55f7fdd --- /dev/null +++ b/aas-web-ui/src/composables/DashboardHandling.ts @@ -0,0 +1,160 @@ +import { computed } from 'vue'; +import { useRequestHandling } from '@/composables/RequestHandling'; +import { useAASStore } from '@/store/AASDataStore'; +import { useEnvStore } from '@/store/EnvironmentStore'; +import { extractEndpointHref } from '@/utils/DescriptorUtils'; +import { URLEncode } from '@/utils/EncodeDecodeUtils'; +import { UUID } from '@/utils/IDUtils'; + +export function useDashboardHandling() { + const { getRequest, postRequest, putRequest, deleteRequest } = useRequestHandling(); + + const aasStore = useAASStore(); + + const SelectedNode = computed(() => { + return aasStore.getSelectedNode; + }); + + const SelectedAAS = computed(() => { + return aasStore.getSelectedAAS; + }); + + const dashboardServicePath = computed(() => { + return useEnvStore().getEnvDashboardServicePath; + }); + + async function dashboardAdd(item: any) { + // console.log(item) + const group = await getAAS(); + // console.log(group); + const dashboardObj = { + title: item.title, + endpoint: SelectedNode.value.path, + group: { + groupName: group.idShort, + groupId: group.id, + }, + configObject: { + semanticId: SelectedNode.value.semanticId, + chartType: item.chartType, + chartOptions: item.chartOptions, + timeVal: item.timeValue, + yvals: item.yValues, + segment: item.segment, + apiToken: item.apiToken, + }, + visibility: true, + }; + // console.log('Add Element to Dasboard: ', dashboardObj); + // construct the request + const path = dashboardServicePath.value + '/addElement'; + const headers: Headers = new Headers(); + headers.append('Content-Type', 'application/json'); + headers.append('accept', '*/*'); + const content = JSON.stringify(dashboardObj); + const context = 'adding element to dashboard'; + const disableMessage = false; + // send the request + postRequest(path, content, headers, context, disableMessage).then((response: any) => { + if (response.success) { + // console.log('Successfully added Element to Dashboard: ', response.data); + } + }); + } + + async function getGroups() { + const path = dashboardServicePath.value + '/groups/summary'; + const context = 'fetching all groups'; + const disableMessage = false; + const response: any = await getRequest(path, context, disableMessage); + if (response.success) { + // console.log(response.data) + return response.data; + } + } + + async function getElements(group: any) { + const pathGroup = URLEncode(group); + const path = dashboardServicePath.value + '/findGroup/' + pathGroup; + const context = 'fetching all elements of a group'; + const disableMessage = false; + const response: any = await getRequest(path, context, disableMessage); + if (response.success) { + // console.log(response); + return response.data; + } + } + + async function deleteGroup(groups: any, groupId: any): Promise { + // console.log(groups) + const pathGroup = URLEncode(groupId); + const path = dashboardServicePath.value + '/deleteGroup/' + pathGroup; + const context = 'deleting all elements of a group'; + const disableMessage = false; + const response: any = await deleteRequest(path, context, disableMessage); + if (response.success) { + // console.log(response); + const index = groups.findIndex((element: any) => element.groupId === groupId); + // console.log(index) + groups = groups.filter((item: any) => item !== groups[index]); + // console.log(groups) + return groups; + } + return groups; + } + + async function deleteSingle(elementId: any): Promise { + const path = dashboardServicePath.value + '/deleteElement/' + elementId; + const context = 'deleting one element of a group'; + const disableMessage = false; + const response: any = await deleteRequest(path, context, disableMessage); + if (response.success) { + return elementId; + } + return undefined; + } + + async function updateElement(element: any): Promise { + // console.log(element) + const path = dashboardServicePath.value + '/updateElement/' + element.id; + const headers: Headers = new Headers(); + headers.append('Content-Type', 'application/json'); + headers.append('accept', '*/*'); + const content = JSON.stringify(element); + const context = 'adding element to dashboard'; + const disableMessage = false; + // send the request + const response: any = await putRequest(path, content, headers, context, disableMessage); + if (response.success) { + return response.data; + } + return undefined; + } + + async function getAAS(): Promise { + if ((SelectedAAS.value.idShort && SelectedAAS.value.id) != null) { + // console.log(this.SelectedAAS) + return SelectedAAS.value; + } else { + const shellHref = extractEndpointHref(SelectedAAS.value, 'AAS-3.0'); + const path = shellHref; + const context = 'getting aas from endpoint'; + const disableMessage = false; + const response = await getRequest(path, context, disableMessage); + if (response && response.success) { + return response.data; + } + return { idShort: 'new Group', id: UUID() }; + } + } + + return { + dashboardAdd, + getGroups, + getElements, + deleteGroup, + deleteSingle, + updateElement, + getAAS, + }; +} diff --git a/aas-web-ui/src/composables/RequestHandling.ts b/aas-web-ui/src/composables/RequestHandling.ts new file mode 100644 index 0000000..92e28cf --- /dev/null +++ b/aas-web-ui/src/composables/RequestHandling.ts @@ -0,0 +1,332 @@ +import { useAuthStore } from '@/store/AuthStore'; +import { useNavigationStore } from '@/store/NavigationStore'; + +export function useRequestHandling() { + const authStore = useAuthStore(); + const navigationStore = useNavigationStore(); + + function getRequest(path: string, context: string, disableMessage: boolean, headers: Headers = new Headers()): any { + headers = addAuthorizationHeader(headers); // Add the Authorization header + return fetch(path, { method: 'GET', headers: headers }) + .then((response) => { + // Check if the Server responded with content + if ( + response.headers.get('Content-Type')?.split(';')[0] === 'application/json' && + response.headers.get('Content-Length') !== '0' + ) { + return response.json(); // Return the response as JSON + } else if ( + response.headers.get('Content-Type')?.split(';')[0] === + 'application/asset-administration-shell-package+xml' && + response.headers.get('Content-Length') !== '0' + ) { + return response.blob(); // Return the response as Blob} + } else if ( + response.headers.get('Content-Type')?.split(';')[0].includes('image') && + response.headers.get('Content-Length') !== '0' + ) { + return response.blob(); // Return the response as Blob + } else if ( + response.headers.get('Content-Type')?.split(';')[0] === 'text/csv' && + response.headers.get('Content-Length') !== '0' + ) { + return response.text(); // Return the response as text + } else if ( + response.headers.get('Content-Type')?.split(';')[0] === 'text/plain' && + response.headers.get('Content-Length') !== '0' + ) { + return response.text(); // Return the response as text + } else if ( + response.headers.get('Content-Type')?.split(';')[0] === 'application/pdf' && + response.headers.get('Content-Length') !== '0' + ) { + return response.blob(); // Return the response as Blob + } else if (!response.ok) { + // No content but received an HTTP error status + throw new Error('Error status: ' + response.status); + } else if (response.ok && response.status >= 200 && response.status < 300) { + return response.blob(); // Return the response as Blob + } else { + // Unexpected HTTP status + throw new Error('Unexpected HTTP status: ' + response.status); + } + }) + .then((data) => { + // Check if the Server responded with an error + if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) { + // Error response from the server + if (!disableMessage) errorHandler(data, context); // Call the error handler + return { success: false }; + } else if (data) { + // Successful response from the server + return { success: true, data: data }; + } else { + // Unexpected response format + throw new Error('Unexpected response format'); + } + }) + .catch((error) => { + // Catch any errors + // console.error('Error: ', error); // Log the error + if (!disableMessage) + navigationStore.dispatchSnackbar({ + status: true, + timeout: 60000, + color: 'error', + btnColor: 'buttonText', + text: 'Error! Server responded with: ' + error, + }); + return { success: false }; + }); + } + + function postRequest( + path: string, + body: any, + headers: Headers, + context: string, + disableMessage: boolean, + isTSRequest: boolean = false + ): any { + if (!isTSRequest) { + headers = addAuthorizationHeader(headers); // Add the Authorization header + } + return fetch(path, { method: 'POST', body: body, headers: headers }) + .then((response) => { + // Check if the Server responded with content + if ( + response.headers.get('Content-Type')?.split(';')[0] === 'application/json' && + response.headers.get('Content-Length') !== '0' + ) { + return response.json(); // Return the response as JSON + } else if ( + response.headers.get('Content-Type')?.split(';')[0] === 'text/csv' && + response.headers.get('Content-Length') !== '0' + ) { + return response.text(); // Return the response as text + } else if (!response.ok) { + // No content but received an HTTP error status + throw new Error('Error status: ' + response.status); + } else { + return; // Return without content + } + }) + .then((data) => { + // Check if the Server responded with an error + if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) { + // Error response from the server + if (!disableMessage) errorHandler(data, context); // Call the error handler + return { success: false }; + } else if (data) { + // Successful response from the server + return { success: true, data: data }; + } else if (data === null || data === undefined) { + // in this case no content is expected + return { success: true }; + } else { + // Unexpected response format + throw new Error('Unexpected response format'); + } + }) + .catch((error) => { + // Catch any errors + // console.error('Error: ', error); // Log the error + if (!disableMessage) + navigationStore.dispatchSnackbar({ + status: true, + timeout: 60000, + color: 'error', + btnColor: 'buttonText', + text: 'Error! Server responded with: ' + error, + }); + return { success: false }; + }); + } + + function putRequest(path: string, body: any, headers: Headers, context: string, disableMessage: boolean): any { + headers = addAuthorizationHeader(headers); // Add the Authorization header + return fetch(path, { method: 'PUT', body: body, headers: headers }) + .then((response) => { + // Check if the Server responded with content + if ( + response.headers.get('Content-Type')?.split(';')[0] === 'application/json' && + response.headers.get('Content-Length') !== '0' + ) { + return response.json(); // Return the response as JSON + } else if (!response.ok) { + // No content but received an HTTP error status + throw new Error('Error status: ' + response.status); + } else { + return; // Return without content + } + }) + .then((data) => { + // Check if the Server responded with an error + if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) { + // Error response from the server + if (!disableMessage) errorHandler(data, context); // Call the error handler + return { success: false }; + } else if (data) { + // Successful response from the server + return { success: true, data: data }; + } else if (data === null || data === undefined) { + // in this case no content is expected + return { success: true }; + } else { + // Unexpected response format + throw new Error('Unexpected response format'); + } + }) + .catch((error) => { + // Catch any errors + // console.error('Error: ', error); // Log the error + if (!disableMessage) + navigationStore.dispatchSnackbar({ + status: true, + timeout: 60000, + color: 'error', + btnColor: 'buttonText', + text: 'Error! Server responded with: ' + error, + }); + return { success: false }; + }); + } + + function patchRequest(path: string, body: any, headers: Headers, context: string, disableMessage: boolean): any { + headers = addAuthorizationHeader(headers); // Add the Authorization header + return fetch(path, { method: 'PATCH', body: body, headers: headers }) + .then((response) => { + // Check if the Server responded with content + if ( + response.headers.get('Content-Type')?.split(';')[0] === 'application/json' && + response.headers.get('Content-Length') !== '0' + ) { + return response.json(); // Return the response as JSON + } else if (!response.ok) { + // No content but received an HTTP error status + throw new Error('Error status: ' + response.status); + } else { + return; // Return without content + } + }) + .then((data) => { + // Check if the Server responded with an error + if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) { + // Error response from the server + if (!disableMessage) errorHandler(data, context); // Call the error handler + return { success: false }; + } else if (data) { + // Successful response from the server + return { success: true, data: data }; + } else if (data === null || data === undefined) { + // in this case no content is expected + return { success: true }; + } else { + // Unexpected response format + throw new Error('Unexpected response format'); + } + }) + .catch((error) => { + // Catch any errors + // console.error('Error: ', error); // Log the error + if (!disableMessage) + navigationStore.dispatchSnackbar({ + status: true, + timeout: 60000, + color: 'error', + btnColor: 'buttonText', + text: 'Error! Server responded with: ' + error, + }); + return { success: false }; + }); + } + + function deleteRequest(path: string, context: string, disableMessage: boolean): any { + return fetch(path, { method: 'DELETE', headers: addAuthorizationHeader(new Headers()) }) + .then((response) => { + // Check if the Server responded with content + if ( + response.headers.get('Content-Type')?.split(';')[0] === 'application/json' && + response.headers.get('Content-Length') !== '0' + ) { + return response.json(); // Return the response as JSON + } else if (!response.ok) { + // No content but received an HTTP error status + throw new Error('Error status: ' + response.status); + } else { + return; // Return without content + } + }) + .then((data) => { + // Check if the Server responded with an error + if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) { + // Error response from the server + if (!disableMessage) errorHandler(data, context); // Call the error handler + return { success: false }; + } else if (data) { + // Successful response from the server + return { success: true, data: data }; + } else { + // in this case no content is expected + return { success: true }; + } + }) + .catch((error) => { + // Catch any errors + // console.error('Error: ', error); // Log the error + if (!disableMessage) + navigationStore.dispatchSnackbar({ + status: true, + timeout: 60000, + color: 'error', + btnColor: 'buttonText', + text: 'Error! Server responded with: ' + error, + }); + return { success: false }; + }); + } + + function addAuthorizationHeader(headers: Headers): Headers { + if (!authStore.getAuthStatus) return headers; + headers.set('Authorization', 'Bearer ' + authStore.getToken); + return headers; + } + + function errorHandler(errorData: any, context: string) { + // console.log('Error: ', errorData, 'Context: ', context) + const initialErrorMessage = 'Error ' + context + '!'; + let errorMessage = ''; + + // Building error message based on the new error response structure + if (errorData.status) { + errorMessage += '\nStatus: ' + errorData.status; + } + if (errorData.error) { + errorMessage += '\nError: ' + errorData.error; + } + if (errorData.timestamp) { + const errorDate = new Date(errorData.timestamp).toLocaleString(); + errorMessage += '\nTimestamp: ' + errorDate; + } + if (errorData.path) { + errorMessage += '\nPath: ' + errorData.path; + } + + navigationStore.dispatchSnackbar({ + status: true, + timeout: 60000, + color: 'error', + btnColor: 'buttonText', + baseError: initialErrorMessage, + extendedError: errorMessage, + }); + } + + return { + getRequest, + postRequest, + putRequest, + patchRequest, + deleteRequest, + }; +} diff --git a/aas-web-ui/src/utils/ChartUtils.ts b/aas-web-ui/src/utils/ChartUtils.ts new file mode 100644 index 0000000..768476f --- /dev/null +++ b/aas-web-ui/src/utils/ChartUtils.ts @@ -0,0 +1,55 @@ +export function prepareSeriesValues(chartData: any, yVariables: any) { + const newSeries = chartData.map((series: any, index: number) => { + const chartValues = series.map((element: any) => ({ + x: new Date(element.time), + y: Number(element.value).toFixed(2), + })); + let name = 'Value ' + Number(index + 1); + // check if the yVariable exists + if (yVariables.length > index) { + // check if the yVariable has an idShort + if (yVariables[index] && yVariables[index].idShort) { + name = yVariables[index].idShort; + } + } + return { + name: name, + data: chartValues, + }; + }); + return newSeries; +} + +export function prepareHistogramData(chartData: any, numberOfCategories: any) { + const bins = Number(numberOfCategories); + // Flatten the array and sort values + const allValues = chartData.flat().map((item: any) => Number(item.value)); + allValues.sort((a: any, b: any) => a - b); + + // Determine range and interval + const minValue = allValues[0]; + const maxValue = allValues[allValues.length - 1]; + const range = maxValue - minValue; + const interval = range / bins; + + // Create bins for histogram for each series + const histograms = chartData.map((series: any) => { + const histogram = new Array(bins).fill(0); + series.forEach((item: any) => { + const value = Number(item.value); + const index = Math.min(Math.floor((value - minValue) / interval), bins - 1); + histogram[index]++; + }); + return histogram; + }); + + // Prepare categories array + const categories = new Array(bins); + for (let i = 0; i < bins; i++) { + const rangeStart = minValue + i * interval; + const rangeEnd = rangeStart + interval; + categories[i] = `${rangeStart.toFixed(2)} - ${rangeEnd.toFixed(2)}`; + } + + return { histograms, categories }; +} diff --git a/aas-web-ui/src/utils/DateUtils.ts b/aas-web-ui/src/utils/DateUtils.ts new file mode 100644 index 0000000..e2bb2ac --- /dev/null +++ b/aas-web-ui/src/utils/DateUtils.ts @@ -0,0 +1,13 @@ +// convert js date object to string (format: yyyy-MM-dd HH:mm:ss) +export function formatDate(date: Date) { + return ( + [date.getFullYear(), padTo2Digits(date.getMonth() + 1), padTo2Digits(date.getDate())].join('-') + + ' ' + + [padTo2Digits(date.getHours()), padTo2Digits(date.getMinutes()), padTo2Digits(date.getSeconds())].join(':') + ); +} + +// convert date element to digits +function padTo2Digits(num: number) { + return num.toString().padStart(2, '0'); +} diff --git a/aas-web-ui/src/utils/DescriptorUtils.ts b/aas-web-ui/src/utils/DescriptorUtils.ts new file mode 100644 index 0000000..05fc062 --- /dev/null +++ b/aas-web-ui/src/utils/DescriptorUtils.ts @@ -0,0 +1,28 @@ +// Extract the right endpoints href from a descriptor +export function extractEndpointHref(descriptor: any, interfaceShortName: string): string { + const interfaceShortNames = [ + 'AAS', + 'SUBMODEL', + 'SERIALIZE', + 'DESCRIPTION', + 'AASX-FILE', + 'AAS-REGISTRY', + 'SUBMODEL-REGISTRY', + 'AAS-REPOSITORY', + 'SUBMODEL-REPOSITORY', + 'CD-REPOSITORY', + 'AAS-DISCOVERY', + ]; + if (!interfaceShortNames.some((iShortName) => interfaceShortName.startsWith(`${iShortName}-`))) { + return ''; + } + if (!Array.isArray(descriptor?.endpoints) || descriptor?.endpoints.length === 0 || interfaceShortName === '') { + return ''; + } + const endpoints = descriptor.endpoints; + // find the right endpoint based on the interfaceShortName (has to match endpoint.interface) + const endpoint = endpoints.find((endpoint: any) => { + return endpoint?.interface === interfaceShortName; + }); + return endpoint?.protocolInformation?.href ? endpoint.protocolInformation.href : ''; +} diff --git a/aas-web-ui/src/utils/EncodeDecodeUtils.ts b/aas-web-ui/src/utils/EncodeDecodeUtils.ts new file mode 100644 index 0000000..efa934c --- /dev/null +++ b/aas-web-ui/src/utils/EncodeDecodeUtils.ts @@ -0,0 +1,5 @@ +export function URLEncode(aasId: string) { + const base64Id = btoa(unescape(encodeURIComponent(aasId))); + const urlSafeBase64Id = base64Id.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '%3D'); + return urlSafeBase64Id; +} diff --git a/aas-web-ui/src/utils/IDUtils.ts b/aas-web-ui/src/utils/IDUtils.ts new file mode 100644 index 0000000..ca91a17 --- /dev/null +++ b/aas-web-ui/src/utils/IDUtils.ts @@ -0,0 +1,50 @@ +import md5 from 'md5'; +import { v4 as uuidv4 } from 'uuid'; + +export function UUID(): string { + return uuidv4(); +} + +export function generateUUIDFromString(str: any): string { + // create md5 hash from string + const hash = md5(str); + // create UUID from hash + const guid = + hash.substring(0, 8) + + '-' + + hash.substring(8, 12) + + '-' + + hash.substring(12, 16) + + '-' + + hash.substring(16, 20) + + '-' + + hash.substring(20, 32); + return guid; +} + +// Function to check if the idShort of a SubmodelElement matches the given idShort +export function checkIdShort( + referable: any, + idShort: string, + startsWith: boolean = false, + strict: boolean = false +): boolean { + if (idShort.trim() === '') return false; + + if (!referable || !referable.idShort || referable.idShort.length === 0) return false; + + if (startsWith) { + // For matching e.g. ProductImage{00} with idShort ProductImage + if (strict) { + return referable.idShort.startsWith(idShort); + } else { + return referable.idShort.toLowerCase().startsWith(idShort.toLowerCase()); + } + } else { + if (strict) { + return referable.idShort === idShort; + } else { + return referable.idShort.toLowerCase() === idShort.toLowerCase(); + } + } +} diff --git a/aas-web-ui/src/utils/SemanticIdUtils.ts b/aas-web-ui/src/utils/SemanticIdUtils.ts new file mode 100644 index 0000000..be76eb9 --- /dev/null +++ b/aas-web-ui/src/utils/SemanticIdUtils.ts @@ -0,0 +1,179 @@ +// Function to check if the SemanticID of a SubmodelElement matches the given SemanticID +export function checkSemanticId(submodelElement: any, semanticId: string): boolean { + // console.log('checkSemanticId', 'submodelElement', submodelElement, 'semanticId', semanticId); + if (semanticId.trim() == '') return false; + + if (!Array.isArray(submodelElement?.semanticId?.keys) || submodelElement.semanticId.keys.length == 0) return false; + + for (const key of submodelElement.semanticId.keys) { + // console.log('checkSemanticId: ', 'key of submodelElement', key.value, 'semanticId', semanticId); + if (key.value.startsWith('0112/')) { + return checkSemanticIdIecCdd(key.value, semanticId); + } else if (key.value.startsWith('0173-1#') || key.value.startsWith('0173/1///')) { + return checkSemanticIdEclassIrdi(key.value, semanticId); + } else if (key.value.startsWith('https://api.eclass-cdp.com/0173-1')) { + return checkSemanticIdEclassIrdiUrl(key.value, semanticId); + } else if (key.value.startsWith('http://') || key.value.startsWith('https://')) { + return checkSemanticIdIri(key.value, semanticId); + } else { + if (key.value === semanticId) return true; + } + } + + return false; +} + +export function checkSemanticIdEclassIrdi(keyValue: string, semanticId: string): boolean { + if (semanticId.trim() == '') return false; + + if (!keyValue.startsWith('0173-1#') && !keyValue.startsWith('0173/1///')) return false; + + if (keyValue.startsWith('0173-1#')) { + // Eclass IRDI like 0173-1#01-AHF578#001 + if (new RegExp(/\*\d{2}$/).test(keyValue)) { + keyValue = keyValue.slice(0, -3); + semanticId = semanticId.slice(0, -3); + } + if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId) || new RegExp(/[#-]{1}\d{3}\*\d{1,}$/).test(semanticId)) { + return getEquivalentEclassSemanticIds(keyValue).includes(semanticId); + } + + // Eclass IRDI without version; like 0173-1#01-AHF578 + return ( + getEquivalentEclassSemanticIds(keyValue).findIndex((equivalentSemanticId) => { + return equivalentSemanticId.startsWith(semanticId); + }, semanticId) != -1 + ); + } else if (keyValue.startsWith('0173/1///')) { + if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId) || new RegExp(/[#-]{1}\d{3}\*\d{1,}$/).test(semanticId)) { + // Eclass IRDI with version; like 0173/1///01#AHF578#001 + return getEquivalentEclassSemanticIds(keyValue).includes(semanticId); + } + + // Eclass IRDI without version; like 0173/1///01#AHF578 + return ( + getEquivalentEclassSemanticIds(keyValue).findIndex((equivalentSemanticId) => { + return equivalentSemanticId.startsWith(semanticId); + }, semanticId) != -1 + ); + } + + return false; +} + +export function checkSemanticIdEclassIrdiUrl(keyValue: string, semanticId: string): boolean { + if (semanticId.trim() == '') return false; + + if (!keyValue.startsWith('https://api.eclass-cdp.com/0173-1')) return false; + + // Eclass URL like https://api.eclass-cdp.com/0173-1-01-AHF578-001 + if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId) || new RegExp(/[#-]{1}\d{3}~\d{1,}$/).test(semanticId)) { + // Eclass URL with version (like https://api.eclass-cdp.com/0173-1-01-AHF578-001) + return getEquivalentEclassSemanticIds(semanticId).includes(keyValue); + } + + // Eclass URL without version (like https://api.eclass-cdp.com/0173-1-01-AHF578) + return ( + getEquivalentEclassSemanticIds(keyValue).findIndex((equivalentSemanticId) => { + return equivalentSemanticId.startsWith(semanticId); + }, semanticId) != -1 + ); +} + +export function checkSemanticIdIecCdd(keyValue: string, semanticId: string): boolean { + if (semanticId.trim() == '') return false; + + if (!semanticId.startsWith('0112/')) return false; + if (!keyValue.startsWith('0112/')) return false; + + // IEC CDD like 0112/2///61987#ABN590#002 + if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId)) { + // IEC CDD with version; like 0112/2///61987#ABN590#002 + if (keyValue === semanticId) { + return true; + } + } + + // IEC CDD without version; like 0112/2///61987#ABN590 + return keyValue.startsWith(semanticId); +} + +export function checkSemanticIdIri(keyValue: string, semanticId: string): boolean { + // console.log('checkSemanticIdIri: ', 'keyValue', keyValue, 'semanticId', semanticId); + if (semanticId.trim() == '') return false; + + if (!semanticId.startsWith('http://') && !semanticId.startsWith('https://')) return false; + if (!keyValue.startsWith('http://') && !keyValue.startsWith('https://')) return false; + + if (keyValue.endsWith('/')) keyValue = keyValue.substring(0, keyValue.length - 1); + if (semanticId.endsWith('/')) semanticId = semanticId.substring(0, semanticId.length - 1); + + if (new RegExp(/\/\d{1,}\/\d{1,}$/).test(semanticId)) { + // IRI with version like https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprint/0/9/ + return getEquivalentIriSemanticIds(semanticId).includes(keyValue); + } + + // IRI without version like https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprint/ + return ( + getEquivalentIriSemanticIds(keyValue).findIndex((equivalentSemanticId) => { + return equivalentSemanticId.startsWith(semanticId); + }, semanticId) != -1 + ); +} + +export function getEquivalentEclassSemanticIds(semanticId: string): any[] { + if ( + semanticId.trim() === '' || + (!semanticId.startsWith('0173-1#') && + !semanticId.startsWith('0173/1///') && + !semanticId.startsWith('https://api.eclass-cdp.com/0173-1')) + ) + return []; + + const semanticIds: any[] = [semanticId]; + + if (semanticId.startsWith('0173-1#')) { + // e.g. 0173-1#01-AHF578#001 + semanticIds.push(semanticId.replace(/-1#(\d{2})-/, '/1///$1#')); // 0173-1#01-AHF578#001 --> 0173/1///01#AHF578#001 + semanticIds.push('https://api.eclass-cdp.com/' + semanticId.replaceAll('#', '-')); // 0173-1#01-AHF578#001 --> https://api.eclass-cdp.com/0173-1-01-AHF578-001 + } else if (semanticId.startsWith('0173/1///')) { + // e.g. 0173/1///01#AHF578#001 + semanticIds.push(semanticId.replace(/\/1\/\/\/(\d{2})#/, '-1#$1-')); // 0173/1///01#AHF578#001 --> 0173-1#01-AHF578#001 + semanticIds.push( + 'https://api.eclass-cdp.com/' + semanticId.replace(/\/1\/\/\/(\d{2})#/, '-1-$1-').replaceAll('#', '-') // 0173/1///01#AHF578#001 --> https://api.eclass-cdp.com/0173-1-01-AHF578-001 + ); + } else if (semanticId.startsWith('https://api.eclass-cdp.com/0173-1')) { + // e.g. https://api.eclass-cdp.com/0173-1-01-AHF578-001 + semanticIds.push( + semanticId + .replaceAll('https://api.eclass-cdp.com/', '') + .replace(/-1-(\d{2})-/, '-1#$1-') + .replace(/-(\d{3})$/, '#$1') // https://api.eclass-cdp.com/0173-1-01-AHF578-001 --> 0173-1#01-AHF578#001 + ); + semanticIds.push( + semanticId + .replaceAll('https://api.eclass-cdp.com/', '') + .replace(/-1-(\d{2})-/, '/1///$1#') + .replace(/-(\d{3})$/, '#$1') // https://api.eclass-cdp.com/0173-1-01-AHF578-001 --> 0173/1///01#AHF578#001 + ); + } + + // console.log('getEquivalentEclassSemanticIds', 'semanticId', semanticId, 'semanticIds', semanticIds); + return semanticIds; +} + +export function getEquivalentIriSemanticIds(semanticId: string): any[] { + if (semanticId.trim() === '' || !(semanticId.startsWith('http://') || semanticId.startsWith('https://'))) return []; + + const semanticIds: any[] = [semanticId]; + + // e.g. IRI + if (semanticId.endsWith('/')) { + semanticIds.push(semanticId.substring(0, semanticId.length - 1)); + } else { + semanticIds.push(semanticId + '/'); + } + + // console.log('getEquivalentIriSemanticIds', 'semanticId', semanticId, 'semanticIds', semanticIds); + return semanticIds; +} diff --git a/aas-web-ui/src/utils/generalUtils.ts b/aas-web-ui/src/utils/generalUtils.ts new file mode 100644 index 0000000..b951a7d --- /dev/null +++ b/aas-web-ui/src/utils/generalUtils.ts @@ -0,0 +1,62 @@ +// Function to capitalize the first letter of a string +export function capitalizeFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +// Function to check if the valueType is a number +export function isNumber(valueType: string) { + if (!valueType) return false; + // List of all number types + const numberTypes = [ + 'double', + 'float', + 'integer', + 'int', + 'nonNegativeInteger', + 'positiveInteger', + 'unsignedLong', + 'unsignedInt', + 'unsignedShort', + 'unsignedByte', + 'nonPositiveInteger', + 'negativeInteger', + 'long', + 'short', + 'decimal', + 'byte', + ]; + // strip xs: from the property if it exists + if (valueType.includes('xs:')) { + valueType = valueType.replace('xs:', ''); + } + // check if the property is a number + if (numberTypes.includes(valueType)) { + return true; + } else { + return false; + } +} + +// Function to download a JSON File +export function downloadJson(obj: any, fileName: string) { + const jsonStr = JSON.stringify(obj, null, 4); + const blob = new Blob([jsonStr], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +} + +// Function to download a binary File +export function downloadFile(filename: string, fileContent: Blob) { + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(fileContent); + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}