Skip to content

Commit 50dce28

Browse files
authored
Adds JSON insert to editor (#683)
* Adds JSON insert to editor * Improves code based on remarks
1 parent b146475 commit 50dce28

File tree

4 files changed

+276
-2
lines changed

4 files changed

+276
-2
lines changed

aas-web-ui/src/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ declare module 'vue' {
5858
ImagePreview: typeof import('./components/Plugins/ImagePreview.vue')['default']
5959
InvalidElement: typeof import('./components/SubmodelElements/InvalidElement.vue')['default']
6060
JSONArrayProperty: typeof import('./components/Plugins/SubmodelElements/JSONArrayProperty.vue')['default']
61+
JsonInsert: typeof import('./components/EditorComponents/JsonInsert.vue')['default']
6162
JSONPreview: typeof import('./components/Plugins/JSONPreview.vue')['default']
6263
LastSync: typeof import('./components/UIComponents/LastSync.vue')['default']
6364
LineChart: typeof import('./components/Widgets/LineChart.vue')['default']
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
<template>
2+
<v-dialog v-model="jsonInsertDialog" width="860" persistent>
3+
<v-card>
4+
<v-card-title>Insert {{ type }} from JSON</v-card-title>
5+
<v-divider></v-divider>
6+
<v-card-text class="bg-card pa-3">
7+
<v-card>
8+
<v-textarea
9+
v-model="jsonInput"
10+
:error-messages="jsonInputErrors"
11+
variant="outlined"
12+
rows="10"
13+
hide-details
14+
@update:model-value="clearErrorMessages" />
15+
</v-card>
16+
</v-card-text>
17+
<v-divider></v-divider>
18+
<v-card-actions>
19+
<v-spacer></v-spacer>
20+
<v-btn @click="closeDialog">Cancel</v-btn>
21+
<v-btn color="primary" @click="insertJson">Save</v-btn>
22+
</v-card-actions>
23+
</v-card>
24+
</v-dialog>
25+
</template>
26+
27+
<script lang="ts" setup>
28+
import type { JsonValue } from '@aas-core-works/aas-core3.0-typescript/jsonization';
29+
import { jsonization, types as aasTypes } from '@aas-core-works/aas-core3.0-typescript';
30+
import { computed, ref, watch } from 'vue';
31+
import { useRouter } from 'vue-router';
32+
import { useAASRepositoryClient } from '@/composables/Client/AASRepositoryClient';
33+
import { useSMRepositoryClient } from '@/composables/Client/SMRepositoryClient';
34+
import { useAASStore } from '@/store/AASDataStore';
35+
import { useNavigationStore } from '@/store/NavigationStore';
36+
import { extractEndpointHref } from '@/utils/AAS/DescriptorUtils';
37+
import { base64Decode, base64Encode } from '@/utils/EncodeDecodeUtils';
38+
39+
const props = defineProps<{
40+
modelValue: boolean;
41+
type: 'Submodel' | 'SubmodelElement';
42+
parentElement?: any;
43+
}>();
44+
45+
const emit = defineEmits<{
46+
(event: 'update:modelValue', value: boolean): void;
47+
}>();
48+
49+
// Vue Router
50+
const router = useRouter();
51+
52+
// Stores
53+
const aasStore = useAASStore();
54+
const navigationStore = useNavigationStore();
55+
56+
// Composables
57+
const { postSubmodel, postSubmodelElement } = useSMRepositoryClient();
58+
const { putAas } = useAASRepositoryClient();
59+
60+
// Data
61+
const jsonInsertDialog = ref(false);
62+
const jsonInput = ref<string | null>(null);
63+
const jsonInputErrors = ref<string[]>([]);
64+
65+
// Computed Properties
66+
const selectedAAS = computed(() => aasStore.getSelectedAAS); // Get the selected AAS from Store
67+
const submodelRepoUrl = computed(() => navigationStore.getSubmodelRepoURL);
68+
69+
watch(
70+
() => props.modelValue,
71+
(value) => {
72+
jsonInsertDialog.value = value;
73+
}
74+
);
75+
76+
watch(
77+
() => jsonInsertDialog.value,
78+
(value) => {
79+
emit('update:modelValue', value);
80+
}
81+
);
82+
83+
function insertJson(): void {
84+
if (!jsonInput.value || !isValidJson(jsonInput.value)) {
85+
jsonInputErrors.value = ['Invalid JSON input'];
86+
return;
87+
}
88+
89+
// Parse JSON to Submodel/SubmodelElement
90+
if (props.type === 'Submodel') {
91+
insertSubmodel(JSON.parse(jsonInput.value));
92+
} else {
93+
insertSubmodelElement(JSON.parse(jsonInput.value));
94+
}
95+
}
96+
97+
async function insertSubmodel(json: JsonValue): Promise<void> {
98+
// Parse JSON to Submodel
99+
const instanceOrError = jsonization.submodelFromJsonable(json);
100+
if (instanceOrError.error !== null) {
101+
navigationStore.dispatchSnackbar({
102+
status: true,
103+
timeout: 4000,
104+
color: 'error',
105+
btnColor: 'buttonText',
106+
text: 'Error parsing Submodel: ' + instanceOrError.error,
107+
});
108+
return;
109+
}
110+
const submodel = instanceOrError.mustValue();
111+
112+
// Create Submodel
113+
await postSubmodel(submodel);
114+
// Add Submodel Reference to AAS
115+
await addSubmodelReferenceToAas(submodel);
116+
// Fetch and dispatch Submodel
117+
const path = submodelRepoUrl.value + '/' + base64Encode(submodel.id);
118+
const aasEndpoint = extractEndpointHref(selectedAAS.value, 'AAS-3.0');
119+
router.push({ query: { aas: aasEndpoint, path: path } });
120+
121+
closeDialog();
122+
navigationStore.dispatchTriggerTreeviewReload();
123+
}
124+
125+
async function insertSubmodelElement(json: JsonValue): Promise<void> {
126+
const instanceOrError = jsonization.submodelElementFromJsonable(json);
127+
if (instanceOrError.error !== null) {
128+
navigationStore.dispatchSnackbar({
129+
status: true,
130+
timeout: 4000,
131+
color: 'error',
132+
btnColor: 'buttonText',
133+
text: 'Error parsing SubmodelElement: ' + instanceOrError.error,
134+
});
135+
return;
136+
}
137+
const submodelElement = instanceOrError.mustValue();
138+
139+
if (props.parentElement.modelType === 'Submodel') {
140+
// Create the property on the parent Submodel
141+
await postSubmodelElement(submodelElement, props.parentElement.id);
142+
143+
const aasEndpoint = extractEndpointHref(selectedAAS.value, 'AAS-3.0');
144+
145+
// Navigate to the new property
146+
router.push({
147+
query: {
148+
aas: aasEndpoint,
149+
path: props.parentElement.path + '/submodel-elements/' + submodelElement.idShort,
150+
},
151+
});
152+
} else {
153+
// Extract the submodel ID and the idShortPath from the parentElement path
154+
const splitted = props.parentElement.path.split('/submodel-elements/');
155+
const submodelId = base64Decode(splitted[0].split('/submodels/')[1]);
156+
const idShortPath = splitted[1];
157+
158+
// Create the property on the parent element
159+
await postSubmodelElement(submodelElement, submodelId, idShortPath);
160+
161+
const aasEndpoint = extractEndpointHref(selectedAAS.value, 'AAS-3.0');
162+
163+
// Navigate to the new property
164+
if (props.parentElement.modelType === 'SubmodelElementCollection') {
165+
router.push({
166+
query: {
167+
aas: aasEndpoint,
168+
path: props.parentElement.path + '.' + submodelElement.idShort,
169+
},
170+
});
171+
}
172+
}
173+
174+
closeDialog();
175+
navigationStore.dispatchTriggerTreeviewReload();
176+
}
177+
178+
async function addSubmodelReferenceToAas(submodel: aasTypes.Submodel): Promise<void> {
179+
if (selectedAAS.value === null) return;
180+
const localAAS = { ...selectedAAS.value };
181+
const instanceOrError = jsonization.assetAdministrationShellFromJsonable(localAAS);
182+
if (instanceOrError.error !== null) {
183+
console.error('Error parsing AAS: ', instanceOrError.error);
184+
return;
185+
}
186+
const aas = instanceOrError.mustValue();
187+
// Create new SubmodelReference
188+
const submodelReference = new aasTypes.Reference(aasTypes.ReferenceTypes.ExternalReference, [
189+
new aasTypes.Key(aasTypes.KeyTypes.Submodel, submodel.id),
190+
]);
191+
// Check if Submodels are null
192+
if (aas.submodels === null || aas.submodels === undefined) {
193+
aas.submodels = [submodelReference];
194+
localAAS.submodels = [jsonization.toJsonable(submodelReference)];
195+
} else {
196+
aas.submodels.push(submodelReference);
197+
localAAS.submodels.push(jsonization.toJsonable(submodelReference));
198+
}
199+
await putAas(aas);
200+
201+
// Update AAS in Store
202+
aasStore.dispatchSelectedAAS(localAAS);
203+
}
204+
205+
function isValidJson(jsonString: string): boolean {
206+
try {
207+
JSON.parse(jsonString);
208+
return true;
209+
} catch {
210+
return false;
211+
}
212+
}
213+
214+
function closeDialog(): void {
215+
clearForm();
216+
jsonInsertDialog.value = false;
217+
}
218+
219+
function clearForm(): void {
220+
jsonInput.value = null;
221+
}
222+
223+
function clearErrorMessages(): void {
224+
jsonInputErrors.value = [];
225+
}
226+
</script>

aas-web-ui/src/components/SubmodelTree.vue

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,20 @@
9393
</v-list-item>
9494
</template>
9595
<span>Create a new Submodel</span>
96-
</v-tooltip></v-list
97-
>
96+
</v-tooltip>
97+
<!-- Open JSON insert dialog -->
98+
<v-tooltip open-delay="600" location="end">
99+
<template #activator="{ props }">
100+
<v-list-item slim v-bind="props" @click="openJsonInsertDialog('Submodel')">
101+
<template #prepend>
102+
<v-icon size="small">mdi-code-json</v-icon>
103+
</template>
104+
Submodel from JSON
105+
</v-list-item>
106+
</template>
107+
<span>Create a new Submodel from JSON</span>
108+
</v-tooltip>
109+
</v-list>
98110
</v-sheet>
99111
</v-menu>
100112
</template>
@@ -139,6 +151,7 @@
139151
@open-edit-submodel-element-dialog="openEditSubmodelElementDialogForElement"
140152
@open-add-submodel-element-dialog="openAddSubmodelElementDialog"
141153
@open-edit-dialog="openEditDialog(false, $event)"
154+
@open-json-insert-dialog="openJsonInsertDialog('SubmodelElement', $event)"
142155
@show-delete-dialog="openDeleteDialog"></Treeview>
143156
</template>
144157
<v-empty-state
@@ -217,6 +230,8 @@
217230
:sml="submodelElementToEdit"></ListForm>
218231
<!-- Dialog for creating/editing Submodel -->
219232
<SubmodelForm v-model="editDialog" :new-sm="newSubmodel" :submodel="submodelToEdit"></SubmodelForm>
233+
<!-- Dialog for inserting JSON -->
234+
<JsonInsert v-model="jsonInsertDialog" :type="jsonInsertType" :parent-element="elementToAddSME"></JsonInsert>
220235
<!-- Dialog for deleting SM/SME -->
221236
<DeleteDialog v-model="deleteDialog" :element="elementToDelete"></DeleteDialog>
222237
</template>
@@ -274,6 +289,8 @@
274289
const elementToAddSME = ref<any | undefined>(undefined); // Variable to store the Element where the new SME is added inside
275290
const submodelElementPath = ref<string | undefined>(undefined); // Variable to store the Element where the new SME is added inside
276291
const submodelElementToEdit = ref<any | undefined>(undefined); // Variable to store the Element where the new SME is added inside
292+
const jsonInsertDialog = ref(false); // Variable to store if the JSON Insert Dialog should be shown
293+
const jsonInsertType = ref<'Submodel' | 'SubmodelElement'>('Submodel'); // Variable to store the ModelType of the JSON to be inserted
277294
278295
// Computed Properties
279296
const isMobile = computed(() => navigationStore.getIsMobile); // Check if the current Device is a Mobile Device
@@ -489,6 +506,14 @@
489506
}
490507
}
491508
509+
function openJsonInsertDialog(type: 'Submodel' | 'SubmodelElement', element?: any): void {
510+
jsonInsertDialog.value = true;
511+
jsonInsertType.value = type;
512+
if (type === 'SubmodelElement' && element) {
513+
elementToAddSME.value = element;
514+
}
515+
}
516+
492517
function openDeleteDialog(element: any): void {
493518
deleteDialog.value = true;
494519
elementToDelete.value = element;

aas-web-ui/src/components/UIComponents/Treeview.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@
161161
</template>
162162
<v-list-item-subtitle>Add Submodel Element</v-list-item-subtitle>
163163
</v-list-item>
164+
<!-- Open Insert SubmodelElement from JSON dialog -->
165+
<v-list-item @click="$emit('openJsonInsertDialog', item)">
166+
<template #prepend>
167+
<v-icon size="x-small">mdi-code-json</v-icon>
168+
</template>
169+
<v-list-item-subtitle>Submodel Element from JSON</v-list-item-subtitle>
170+
</v-list-item>
164171
<v-divider></v-divider>
165172
<!-- Open Submodel edit dialog -->
166173
<v-list-item @click="$emit('openEditDialog', item)">
@@ -213,6 +220,19 @@
213220
</template>
214221
<v-list-item-subtitle>Add Submodel Element</v-list-item-subtitle>
215222
</v-list-item>
223+
<!-- Open Insert SubmodelElement from JSON dialog -->
224+
<v-list-item
225+
v-if="
226+
item.modelType === 'SubmodelElementCollection' ||
227+
item.modelType === 'SubmodelElementList' ||
228+
item.modelType === 'Entity'
229+
"
230+
@click="$emit('openJsonInsertDialog', item)">
231+
<template #prepend>
232+
<v-icon size="x-small">mdi-code-json</v-icon>
233+
</template>
234+
<v-list-item-subtitle>Submodel Element from JSON</v-list-item-subtitle>
235+
</v-list-item>
216236
<v-divider
217237
v-if="
218238
item.modelType === 'SubmodelElementCollection' ||
@@ -261,6 +281,7 @@
261281
:depth="depth + 1"
262282
@open-add-submodel-element-dialog="$emit('openAddSubmodelElementDialog', $event)"
263283
@open-edit-submodel-element-dialog="$emit('openEditSubmodelElementDialog', $event)"
284+
@open-json-insert-dialog="$emit('openJsonInsertDialog', $event)"
264285
@show-delete-dialog="$emit('showDeleteDialog', $event)"></Treeview>
265286
</template>
266287
</div>
@@ -303,6 +324,7 @@
303324
(event: 'openEditDialog', item: any): void;
304325
(event: 'showDeleteDialog', item: any): void;
305326
(event: 'openAddSubmodelElementDialog', item: any): void;
327+
(event: 'openJsonInsertDialog', item: any): void;
306328
(event: 'openEditSubmodelElementDialog', item: any): void;
307329
}>();
308330

0 commit comments

Comments
 (0)