Skip to content

Commit 677f9af

Browse files
committed
Adds pasting functionality for Submodels/SubmodelElements
1 parent e08f1e6 commit 677f9af

File tree

4 files changed

+252
-7
lines changed

4 files changed

+252
-7
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,32 @@
106106
</template>
107107
<span>Create a new Submodel from JSON</span>
108108
</v-tooltip>
109+
<v-divider></v-divider>
110+
<!-- Paste Submodel from internal clipboard -->
111+
<v-tooltip open-delay="600" location="end">
112+
<template #activator="{ props }">
113+
<v-list-item
114+
:disabled="
115+
!clipboardElementContentType ||
116+
clipboardElementContentType !== 'Submodel'
117+
"
118+
slim
119+
v-bind="props"
120+
@click="pasteElement">
121+
<template #prepend>
122+
<v-icon size="small">mdi-file-document-multiple-outline</v-icon>
123+
</template>
124+
{{
125+
`Paste ${!clipboardElementContentType || clipboardElementContentType !== 'Submodel' ? '' : clipboardElementContentType}`
126+
}}
127+
</v-list-item>
128+
</template>
129+
<span>
130+
{{
131+
`Paste ${!clipboardElementContentType || clipboardElementContentType !== 'Submodel' ? '' : 'copied ' + clipboardElementContentType}`
132+
}}
133+
</span>
134+
</v-tooltip>
109135
</v-list>
110136
</v-sheet>
111137
</v-menu>
@@ -243,7 +269,9 @@
243269
import { useAASHandling } from '@/composables/AAS/AASHandling';
244270
import { useReferableUtils } from '@/composables/AAS/ReferableUtils';
245271
import { useSMHandling } from '@/composables/AAS/SMHandling';
272+
import { useClipboardUtil } from '@/composables/ClipboardUtil';
246273
import { useAASStore } from '@/store/AASDataStore';
274+
import { useClipboardStore } from '@/store/ClipboardStore';
247275
import { useEnvStore } from '@/store/EnvironmentStore';
248276
import { useNavigationStore } from '@/store/NavigationStore';
249277
import { isEmptyString } from '@/utils/StringUtils';
@@ -255,11 +283,13 @@
255283
const { fetchAasSmListById } = useAASHandling();
256284
const { fetchSmList } = useSMHandling();
257285
const { nameToDisplay, descriptionToDisplay } = useReferableUtils();
286+
const { pasteElement } = useClipboardUtil();
258287
259288
// Stores
260289
const navigationStore = useNavigationStore();
261290
const aasStore = useAASStore();
262291
const envStore = useEnvStore();
292+
const clipboardStore = useClipboardStore();
263293
264294
// Data
265295
const submodelTree = ref([] as Array<any>) as Ref<Array<any>>; // Submodel Treeview Data
@@ -301,6 +331,7 @@
301331
const singleAas = computed(() => envStore.getSingleAas); // Get the single AAS state from the Store
302332
const editorMode = computed(() => ['AASEditor', 'SMEditor'].includes(route.name as string));
303333
const triggerTreeviewReload = computed(() => navigationStore.getTriggerTreeviewReload); // Reload the Treeview
334+
const clipboardElementContentType = computed(() => clipboardStore.getClipboardElementModelType()); // Get the Clipboard Element Content Type
304335
305336
// Watchers
306337
watch(

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

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,13 @@
102102
text="Add Submodel Element"
103103
:open-delay="600"
104104
location="bottom">
105-
<template #activator="{ props }">
105+
<template #activator="{ props: addSubmodelElementTooltipProps }">
106106
<v-btn
107107
icon="mdi-plus"
108108
size="small"
109109
variant="plain"
110110
color="subtitleText"
111-
v-bind="props"
111+
v-bind="addSubmodelElementTooltipProps"
112112
class="ml-1"
113113
:style="{
114114
display: isHovering ? 'block' : 'none',
@@ -124,13 +124,13 @@
124124
text="Copy Path to Clipboard"
125125
:open-delay="600"
126126
location="bottom">
127-
<template #activator="{ props }">
127+
<template #activator="{ props: copyPathTooltipProps }">
128128
<v-btn
129129
:icon="copyIcon"
130130
size="small"
131131
variant="plain"
132132
color="subtitleText"
133-
v-bind="props"
133+
v-bind="copyPathTooltipProps"
134134
class="ml-1"
135135
:style="{
136136
display: isHovering ? 'block' : 'none',
@@ -209,6 +209,23 @@
209209
</template>
210210
<v-list-item-subtitle>Copy Submodel as JSON</v-list-item-subtitle>
211211
</v-list-item>
212+
<v-divider></v-divider>
213+
<!-- Paste SubmodelElement from internal clipboard -->
214+
<v-list-item
215+
:disabled="
216+
!clipboardElementContentType ||
217+
clipboardElementContentType === 'Submodel'
218+
"
219+
@click.stop="pasteElement(item)">
220+
<template #prepend>
221+
<v-icon size="x-small">{{ pasteIcon }} </v-icon>
222+
</template>
223+
<v-list-item-subtitle>
224+
{{
225+
`Paste ${!clipboardElementContentType || clipboardElementContentType === 'Submodel' ? '' : clipboardElementContentType}`
226+
}}
227+
</v-list-item-subtitle>
228+
</v-list-item>
212229
</v-list>
213230
</v-sheet>
214231
</template>
@@ -302,6 +319,33 @@
302319
>Copy {{ item.modelType }} as JSON</v-list-item-subtitle
303320
>
304321
</v-list-item>
322+
<v-divider
323+
v-if="
324+
item.modelType === 'SubmodelElementCollection' ||
325+
item.modelType === 'SubmodelElementList' ||
326+
item.modelType === 'Entity'
327+
"></v-divider>
328+
<!-- Paste SubmodelElement from internal clipboard -->
329+
<v-list-item
330+
v-if="
331+
item.modelType === 'SubmodelElementCollection' ||
332+
item.modelType === 'SubmodelElementList' ||
333+
item.modelType === 'Entity'
334+
"
335+
:disabled="
336+
!clipboardElementContentType ||
337+
clipboardElementContentType === 'Submodel'
338+
"
339+
@click="pasteElement(item)">
340+
<template #prepend>
341+
<v-icon size="x-small">{{ pasteIcon }} </v-icon>
342+
</template>
343+
<v-list-item-subtitle>
344+
{{
345+
`Paste ${!clipboardElementContentType || clipboardElementContentType === 'Submodel' ? '' : clipboardElementContentType}`
346+
}}
347+
</v-list-item-subtitle>
348+
</v-list-item>
305349
</v-list>
306350
</v-sheet>
307351
</template>
@@ -342,7 +386,7 @@
342386
343387
// Composables
344388
const { nameToDisplay } = useReferableUtils();
345-
const { copyToClipboard, copyJsonToClipboard } = useClipboardUtil();
389+
const { copyToClipboard, copyJsonToClipboard, pasteElement } = useClipboardUtil();
346390
347391
// Stores
348392
const navigationStore = useNavigationStore();
@@ -374,6 +418,7 @@
374418
const copyInternalIcon = ref<string>('mdi-clipboard-outline');
375419
const copyIcon = ref<string>('mdi-clipboard-file-outline');
376420
const copyJsonIcon = ref<string>('mdi-clipboard-text-outline');
421+
const pasteIcon = ref<string>('mdi-file-document-multiple-outline');
377422
378423
// Computed Properties
379424
const selectedNode = computed(() => aasStore.getSelectedNode);
@@ -382,6 +427,7 @@
382427
const copyInternalIconAsRef = computed(() => copyInternalIcon);
383428
const copyIconAsRef = computed(() => copyIcon);
384429
const copyJsonIconAsRef = computed(() => copyJsonIcon);
430+
const clipboardElementContentType = computed(() => clipboardStore.getClipboardElementModelType());
385431
386432
function toggleTree(smOrSme: any): void {
387433
smOrSme.showChildren = !smOrSme.showChildren;

aas-web-ui/src/composables/ClipboardUtil.ts

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
1+
import type { JsonValue } from '@aas-core-works/aas-core3.0-typescript/jsonization';
2+
import { jsonization, types as aasTypes } from '@aas-core-works/aas-core3.0-typescript';
3+
import { useRouter } from 'vue-router';
4+
import { useAASRepositoryClient } from '@/composables/Client/AASRepositoryClient';
5+
import { useSMRepositoryClient } from '@/composables/Client/SMRepositoryClient';
6+
import { useIDUtils } from '@/composables/IDUtils';
7+
import { useAASStore } from '@/store/AASDataStore';
8+
import { useClipboardStore } from '@/store/ClipboardStore';
19
import { useNavigationStore } from '@/store/NavigationStore';
10+
import { extractEndpointHref } from '@/utils/AAS/DescriptorUtils';
11+
import { base64Decode, base64Encode } from '@/utils/EncodeDecodeUtils';
212

313
export function useClipboardUtil() {
14+
// Vue Router
15+
const router = useRouter();
16+
417
// Store
18+
const aasStore = useAASStore();
19+
const clipboardStore = useClipboardStore();
520
const navigationStore = useNavigationStore();
621

22+
// composables
23+
const { postSubmodel, postSubmodelElement } = useSMRepositoryClient();
24+
const { putAas } = useAASRepositoryClient();
25+
const { generateIri } = useIDUtils();
26+
27+
// Computed properties
28+
const selectedAAS = computed(() => aasStore.getSelectedAAS);
29+
const submodelRepoUrl = computed(() => navigationStore.getSubmodelRepoURL);
30+
731
function copyToClipboard(value: string, valueName: string, iconReference: { value: string }): void {
832
if (!value) return;
933

@@ -78,6 +102,135 @@ export function useClipboardUtil() {
78102
});
79103
}
80104

105+
function pasteElement(item?: unknown): void {
106+
const clipboardElement = clipboardStore.getClipboardContent() as any;
107+
console.log('Paste element on item:', item, '. Element:', clipboardElement);
108+
109+
if (!clipboardElement) return;
110+
111+
if (clipboardElement.modelType === 'Submodel') {
112+
insertSubmodel(clipboardElement);
113+
} else {
114+
insertSubmodelElement(clipboardElement, item);
115+
}
116+
}
117+
118+
async function insertSubmodel(json: JsonValue): Promise<void> {
119+
// Parse JSON to Submodel
120+
const instanceOrError = jsonization.submodelFromJsonable(json);
121+
if (instanceOrError.error !== null) {
122+
navigationStore.dispatchSnackbar({
123+
status: true,
124+
timeout: 4000,
125+
color: 'error',
126+
btnColor: 'buttonText',
127+
text: 'Error parsing Submodel: ' + instanceOrError.error,
128+
});
129+
return;
130+
}
131+
const submodel = instanceOrError.mustValue();
132+
133+
// Create new unique ID for the Submodel
134+
submodel.id = generateIri('Submodel');
135+
136+
// Create Submodel
137+
await postSubmodel(submodel);
138+
// Add Submodel Reference to AAS
139+
await addSubmodelReferenceToAas(submodel);
140+
// Fetch and dispatch Submodel
141+
const path = submodelRepoUrl.value + '/' + base64Encode(submodel.id);
142+
const aasEndpoint = extractEndpointHref(selectedAAS.value, 'AAS-3.0');
143+
router.push({ query: { aas: aasEndpoint, path: path } });
144+
145+
navigationStore.dispatchTriggerTreeviewReload();
146+
}
147+
148+
async function insertSubmodelElement(json: JsonValue, parentElement: any): Promise<void> {
149+
if (!parentElement) return;
150+
151+
const instanceOrError = jsonization.submodelElementFromJsonable(json);
152+
if (instanceOrError.error !== null) {
153+
navigationStore.dispatchSnackbar({
154+
status: true,
155+
timeout: 4000,
156+
color: 'error',
157+
btnColor: 'buttonText',
158+
text: 'Error parsing SubmodelElement: ' + instanceOrError.error,
159+
});
160+
return;
161+
}
162+
const submodelElement = instanceOrError.mustValue();
163+
164+
// In case the SubmodelElement has an idShort, add "_copy" to the end
165+
if (submodelElement.idShort) {
166+
submodelElement.idShort += '_copy';
167+
}
168+
169+
if (parentElement.modelType === 'Submodel') {
170+
// Create the property on the parent Submodel
171+
await postSubmodelElement(submodelElement, parentElement.id);
172+
173+
const aasEndpoint = extractEndpointHref(selectedAAS.value, 'AAS-3.0');
174+
175+
// Navigate to the new property
176+
router.push({
177+
query: {
178+
aas: aasEndpoint,
179+
path: parentElement.path + '/submodel-elements/' + submodelElement.idShort,
180+
},
181+
});
182+
} else {
183+
// Extract the submodel ID and the idShortPath from the parentElement path
184+
const splitted = parentElement.path.split('/submodel-elements/');
185+
const submodelId = base64Decode(splitted[0].split('/submodels/')[1]);
186+
const idShortPath = splitted[1];
187+
188+
// Create the property on the parent element
189+
await postSubmodelElement(submodelElement, submodelId, idShortPath);
190+
191+
const aasEndpoint = extractEndpointHref(selectedAAS.value, 'AAS-3.0');
192+
193+
// Navigate to the new property
194+
if (parentElement.modelType === 'SubmodelElementCollection') {
195+
router.push({
196+
query: {
197+
aas: aasEndpoint,
198+
path: parentElement.path + '.' + submodelElement.idShort,
199+
},
200+
});
201+
}
202+
}
203+
204+
navigationStore.dispatchTriggerTreeviewReload();
205+
}
206+
207+
async function addSubmodelReferenceToAas(submodel: aasTypes.Submodel): Promise<void> {
208+
if (selectedAAS.value === null) return;
209+
const localAAS = { ...selectedAAS.value };
210+
const instanceOrError = jsonization.assetAdministrationShellFromJsonable(localAAS);
211+
if (instanceOrError.error !== null) {
212+
console.error('Error parsing AAS: ', instanceOrError.error);
213+
return;
214+
}
215+
const aas = instanceOrError.mustValue();
216+
// Create new SubmodelReference
217+
const submodelReference = new aasTypes.Reference(aasTypes.ReferenceTypes.ExternalReference, [
218+
new aasTypes.Key(aasTypes.KeyTypes.Submodel, submodel.id),
219+
]);
220+
// Check if Submodels are null
221+
if (aas.submodels === null || aas.submodels === undefined) {
222+
aas.submodels = [submodelReference];
223+
localAAS.submodels = [jsonization.toJsonable(submodelReference)];
224+
} else {
225+
aas.submodels.push(submodelReference);
226+
localAAS.submodels.push(jsonization.toJsonable(submodelReference));
227+
}
228+
await putAas(aas);
229+
230+
// Update AAS in Store
231+
aasStore.dispatchSelectedAAS(localAAS);
232+
}
233+
81234
function cleanObjectRecursively(obj: unknown): unknown {
82235
if (obj === null || obj === undefined) {
83236
return obj;
@@ -144,5 +297,10 @@ export function useClipboardUtil() {
144297
return obj;
145298
}
146299

147-
return { copyToClipboard, copyJsonToClipboard, cleanObjectRecursively };
300+
return {
301+
copyToClipboard,
302+
copyJsonToClipboard,
303+
cleanObjectRecursively,
304+
pasteElement,
305+
};
148306
}

aas-web-ui/src/store/ClipboardStore.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,21 @@ export const useClipboardStore = defineStore('clipboardStore', () => {
1212
function getClipboardContent() {
1313
return clipboardContent.value;
1414
}
15+
function getClipboardElementModelType() {
16+
const content = clipboardContent.value;
17+
return content && typeof content === 'object' && 'modelType' in content
18+
? (content as { modelType?: string }).modelType || null
19+
: null;
20+
}
1521

1622
// Actions
1723
function setClipboardContent(content: unknown) {
1824
clipboardContent.value = cleanObjectRecursively(content);
1925
}
2026

21-
return { getClipboardContent, setClipboardContent };
27+
return {
28+
getClipboardContent,
29+
getClipboardElementModelType,
30+
setClipboardContent,
31+
};
2232
});

0 commit comments

Comments
 (0)