concepto-user-controls 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/concepto-context-menu/concepto-context-menu.component.mjs +3 -7
- package/esm2022/lib/concepto-tree/concepto-tree.component.mjs +19 -0
- package/esm2022/lib/entity-comparison/components/entity-comparison.component.mjs +218 -0
- package/esm2022/lib/entity-comparison/core/services/entity-comparison.service.mjs +111 -0
- package/esm2022/public-api.mjs +4 -1
- package/fesm2022/concepto-user-controls.mjs +340 -6
- package/fesm2022/concepto-user-controls.mjs.map +1 -1
- package/lib/concepto-context-menu/concepto-context-menu.component.d.ts +2 -3
- package/lib/concepto-tree/concepto-tree.component.d.ts +5 -0
- package/lib/entity-comparison/components/entity-comparison.component.d.ts +49 -0
- package/lib/entity-comparison/core/services/entity-comparison.service.d.ts +10 -0
- package/package.json +1 -1
- package/public-api.d.ts +3 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export class EntityComparisonService {
|
|
4
|
+
parseEntities(xmlString) {
|
|
5
|
+
const parser = new DOMParser();
|
|
6
|
+
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
|
|
7
|
+
const exportItems = xmlDoc.querySelectorAll('ExportItem[Type="Entity"]');
|
|
8
|
+
const entities = [];
|
|
9
|
+
exportItems.forEach((item) => {
|
|
10
|
+
const entity = item.querySelector('Entity_WithoutRedundancies');
|
|
11
|
+
if (!entity)
|
|
12
|
+
return;
|
|
13
|
+
const structureJson = entity.querySelector('EntityStructureJson')?.textContent;
|
|
14
|
+
if (!structureJson)
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
const structure = JSON.parse(structureJson);
|
|
18
|
+
entities.push({
|
|
19
|
+
id: entity.querySelector('EntityId')?.textContent || '',
|
|
20
|
+
name: entity.querySelector('EntityName')?.textContent || '',
|
|
21
|
+
structure: structure
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
console.error('Error parsing EntityStructureJson:', e);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return entities;
|
|
29
|
+
}
|
|
30
|
+
compareStructures(leftStructure, rightStructure) {
|
|
31
|
+
const leftOnly = [];
|
|
32
|
+
const rightOnly = [];
|
|
33
|
+
const modified = [];
|
|
34
|
+
const unchanged = [];
|
|
35
|
+
// Create maps for quick lookup
|
|
36
|
+
const leftMap = this.createNodeMap(leftStructure);
|
|
37
|
+
const rightMap = this.createNodeMap(rightStructure);
|
|
38
|
+
// Find nodes only in left or modified
|
|
39
|
+
for (const [key, leftNode] of leftMap.entries()) {
|
|
40
|
+
if (!rightMap.has(key)) {
|
|
41
|
+
leftOnly.push(leftNode);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const rightNode = rightMap.get(key);
|
|
45
|
+
const diff = this.compareNodes(leftNode, rightNode);
|
|
46
|
+
if (diff.length > 0) {
|
|
47
|
+
modified.push({ ...leftNode, modifiedProperties: diff });
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
unchanged.push(leftNode);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Find nodes only in right
|
|
55
|
+
for (const [key, rightNode] of rightMap.entries()) {
|
|
56
|
+
if (!leftMap.has(key)) {
|
|
57
|
+
rightOnly.push(rightNode);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { leftOnly, rightOnly, modified, unchanged };
|
|
61
|
+
}
|
|
62
|
+
createNodeMap(structure, parentPath = '') {
|
|
63
|
+
const map = new Map();
|
|
64
|
+
for (const node of structure) {
|
|
65
|
+
const nodePath = parentPath ? `${parentPath}/${node.Id}-${node.name}` : `${node.Id}-${node.name}`;
|
|
66
|
+
map.set(nodePath, node);
|
|
67
|
+
if (node.Children) {
|
|
68
|
+
try {
|
|
69
|
+
const children = JSON.parse(node.Children);
|
|
70
|
+
const childMap = this.createNodeMap(children, nodePath);
|
|
71
|
+
for (const [key, value] of childMap.entries()) {
|
|
72
|
+
map.set(key, value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
// Ignore parsing errors
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return map;
|
|
81
|
+
}
|
|
82
|
+
compareNodes(left, right) {
|
|
83
|
+
const modifiedProps = [];
|
|
84
|
+
try {
|
|
85
|
+
const leftProps = JSON.parse(left.Properties || '{}');
|
|
86
|
+
const rightProps = JSON.parse(right.Properties || '{}');
|
|
87
|
+
const allKeys = new Set([
|
|
88
|
+
...Object.keys(leftProps),
|
|
89
|
+
...Object.keys(rightProps)
|
|
90
|
+
]);
|
|
91
|
+
for (const key of allKeys) {
|
|
92
|
+
if (leftProps[key] !== rightProps[key]) {
|
|
93
|
+
modifiedProps.push(key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
// Ignore parsing errors
|
|
99
|
+
}
|
|
100
|
+
return modifiedProps;
|
|
101
|
+
}
|
|
102
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
103
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonService, providedIn: 'root' });
|
|
104
|
+
}
|
|
105
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonService, decorators: [{
|
|
106
|
+
type: Injectable,
|
|
107
|
+
args: [{
|
|
108
|
+
providedIn: 'root'
|
|
109
|
+
}]
|
|
110
|
+
}] });
|
|
111
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXR5LWNvbXBhcmlzb24uc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2NvbmNlcHRvLXVzZXItY29udHJvbHMvc3JjL2xpYi9lbnRpdHktY29tcGFyaXNvbi9jb3JlL3NlcnZpY2VzL2VudGl0eS1jb21wYXJpc29uLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQzs7QUFNM0MsTUFBTSxPQUFPLHVCQUF1QjtJQUVsQyxhQUFhLENBQUMsU0FBaUI7UUFDN0IsTUFBTSxNQUFNLEdBQUcsSUFBSSxTQUFTLEVBQUUsQ0FBQztRQUMvQixNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsZUFBZSxDQUFDLFNBQVMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUU3RCxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUN6RSxNQUFNLFFBQVEsR0FBVSxFQUFFLENBQUM7UUFFM0IsV0FBVyxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFO1lBQzNCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsNEJBQTRCLENBQUMsQ0FBQztZQUNoRSxJQUFJLENBQUMsTUFBTTtnQkFBRSxPQUFPO1lBRXBCLE1BQU0sYUFBYSxHQUFHLE1BQU0sQ0FBQyxhQUFhLENBQUMscUJBQXFCLENBQUMsRUFBRSxXQUFXLENBQUM7WUFDL0UsSUFBSSxDQUFDLGFBQWE7Z0JBQUUsT0FBTztZQUUzQixJQUFJLENBQUM7Z0JBQ0gsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxhQUFhLENBQUMsQ0FBQztnQkFDNUMsUUFBUSxDQUFDLElBQUksQ0FBQztvQkFDWixFQUFFLEVBQUUsTUFBTSxDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUMsRUFBRSxXQUFX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QyxPQUFPLENBQUMsRUFBRSxDQUFDO1lBQ1gsd0JBQXdCO1FBQzFCLENBQUM7UUFFRCxPQUFPLGFBQWEsQ0FBQztJQUN2QixDQUFDO3dHQS9HVSx1QkFBdUI7NEdBQXZCLHVCQUF1QixjQUZ0QixNQUFNOzs0RkFFUCx1QkFBdUI7a0JBSG5DLFVBQVU7bUJBQUM7b0JBQ1YsVUFBVSxFQUFFLE1BQU07aUJBQ25CIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBDb21wYXJpc29uUmVzdWx0IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9lbnRpdHktY29tcGFyaXNvbi5jb21wb25lbnQnO1xyXG5cclxuQEluamVjdGFibGUoe1xyXG4gIHByb3ZpZGVkSW46ICdyb290J1xyXG59KVxyXG5leHBvcnQgY2xhc3MgRW50aXR5Q29tcGFyaXNvblNlcnZpY2Uge1xyXG5cclxuICBwYXJzZUVudGl0aWVzKHhtbFN0cmluZzogc3RyaW5nKTogYW55W10ge1xyXG4gICAgY29uc3QgcGFyc2VyID0gbmV3IERPTVBhcnNlcigpO1xyXG4gICAgY29uc3QgeG1sRG9jID0gcGFyc2VyLnBhcnNlRnJvbVN0cmluZyh4bWxTdHJpbmcsICd0ZXh0L3htbCcpO1xyXG4gICAgXHJcbiAgICBjb25zdCBleHBvcnRJdGVtcyA9IHhtbERvYy5xdWVyeVNlbGVjdG9yQWxsKCdFeHBvcnRJdGVtW1R5cGU9XCJFbnRpdHlcIl0nKTtcclxuICAgIGNvbnN0IGVudGl0aWVzOiBhbnlbXSA9IFtdO1xyXG4gICAgXHJcbiAgICBleHBvcnRJdGVtcy5mb3JFYWNoKChpdGVtKSA9PiB7XHJcbiAgICAgIGNvbnN0IGVudGl0eSA9IGl0ZW0ucXVlcnlTZWxlY3RvcignRW50aXR5X1dpdGhvdXRSZWR1bmRhbmNpZXMnKTtcclxuICAgICAgaWYgKCFlbnRpdHkpIHJldHVybjtcclxuICAgICAgXHJcbiAgICAgIGNvbnN0IHN0cnVjdHVyZUpzb24gPSBlbnRpdHkucXVlcnlTZWxlY3RvcignRW50aXR5U3RydWN0dXJlSnNvbicpPy50ZXh0Q29udGVudDtcclxuICAgICAgaWYgKCFzdHJ1Y3R1cmVKc29uKSByZXR1cm47XHJcbiAgICAgIFxyXG4gICAgICB0cnkge1xyXG4gICAgICAgIGNvbnN0IHN0cnVjdHVyZSA9IEpTT04ucGFyc2Uoc3RydWN0dXJlSnNvbik7XHJcbiAgICAgICAgZW50aXRpZXMucHVzaCh7XHJcbiAgICAgICAgICBpZDogZW50aXR5LnF1ZXJ5U2VsZWN0b3IoJ0VudGl0eUlkJyk/LnRleHRDb250ZW50IHx8ICcnLFxyXG4gICAgICAgICAgbmFtZTogZW50aXR5LnF1ZXJ5U2VsZWN0b3IoJ0VudGl0eU5hbWUnKT8udGV4dENvbnRlbnQgfHwgJycsXHJcbiAgICAgICAgICBzdHJ1Y3R1cmU6IHN0cnVjdHVyZVxyXG4gICAgICAgIH0pO1xyXG4gICAgICB9IGNhdGNoIChlKSB7XHJcbiAgICAgICAgY29uc29sZS5lcnJvcignRXJyb3IgcGFyc2luZyBFbnRpdHlTdHJ1Y3R1cmVKc29uOicsIGUpO1xyXG4gICAgICB9XHJcbiAgICB9KTtcclxuICAgIFxyXG4gICAgcmV0dXJuIGVudGl0aWVzO1xyXG4gIH1cclxuXHJcbiAgY29tcGFyZVN0cnVjdHVyZXMobGVmdFN0cnVjdHVyZTogYW55W10sIHJpZ2h0U3RydWN0dXJlOiBhbnlbXSk6IENvbXBhcmlzb25SZXN1bHQge1xyXG4gICAgY29uc3QgbGVmdE9ubHk6IGFueVtdID0gW107XHJcbiAgICBjb25zdCByaWdodE9ubHk6IGFueVtdID0gW107XHJcbiAgICBjb25zdCBtb2RpZmllZDogYW55W10gPSBbXTtcclxuICAgIGNvbnN0IHVuY2hhbmdlZDogYW55W10gPSBbXTtcclxuICAgIFxyXG4gICAgLy8gQ3JlYXRlIG1hcHMgZm9yIHF1aWNrIGxvb2t1cFxyXG4gICAgY29uc3QgbGVmdE1hcCA9IHRoaXMuY3JlYXRlTm9kZU1hcChsZWZ0U3RydWN0dXJlKTtcclxuICAgIGNvbnN0IHJpZ2h0TWFwID0gdGhpcy5jcmVhdGVOb2RlTWFwKHJpZ2h0U3RydWN0dXJlKTtcclxuICAgIFxyXG4gICAgLy8gRmluZCBub2RlcyBvbmx5IGluIGxlZnQgb3IgbW9kaWZpZWRcclxuICAgIGZvciAoY29uc3QgW2tleSwgbGVmdE5vZGVdIG9mIGxlZnRNYXAuZW50cmllcygpKSB7XHJcbiAgICAgIGlmICghcmlnaHRNYXAuaGFzKGtleSkpIHtcclxuICAgICAgICBsZWZ0T25seS5wdXNoKGxlZnROb2RlKTtcclxuICAgICAgfSBlbHNlIHtcclxuICAgICAgICBjb25zdCByaWdodE5vZGUgPSByaWdodE1hcC5nZXQoa2V5KTtcclxuICAgICAgICBjb25zdCBkaWZmID0gdGhpcy5jb21wYXJlTm9kZXMobGVmdE5vZGUsIHJpZ2h0Tm9kZSEpO1xyXG4gICAgICAgIGlmIChkaWZmLmxlbmd0aCA+IDApIHtcclxuICAgICAgICAgIG1vZGlmaWVkLnB1c2goeyAuLi5sZWZ0Tm9kZSwgbW9kaWZpZWRQcm9wZXJ0aWVzOiBkaWZmIH0pO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICB1bmNoYW5nZWQucHVzaChsZWZ0Tm9kZSk7XHJcbiAgICAgICAgfVxyXG4gICAgICB9XHJcbiAgICB9XHJcbiAgICBcclxuICAgIC8vIEZpbmQgbm9kZXMgb25seSBpbiByaWdodFxyXG4gICAgZm9yIChjb25zdCBba2V5LCByaWdodE5vZGVdIG9mIHJpZ2h0TWFwLmVudHJpZXMoKSkge1xyXG4gICAgICBpZiAoIWxlZnRNYXAuaGFzKGtleSkpIHtcclxuICAgICAgICByaWdodE9ubHkucHVzaChyaWdodE5vZGUpO1xyXG4gICAgICB9XHJcbiAgICB9XHJcbiAgICBcclxuICAgIHJldHVybiB7IGxlZnRPbmx5LCByaWdodE9ubHksIG1vZGlmaWVkLCB1bmNoYW5nZWQgfTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgY3JlYXRlTm9kZU1hcChzdHJ1Y3R1cmU6IGFueVtdLCBwYXJlbnRQYXRoOiBzdHJpbmcgPSAnJyk6IE1hcDxzdHJpbmcsIGFueT4ge1xyXG4gICAgY29uc3QgbWFwID0gbmV3IE1hcDxzdHJpbmcsIGFueT4oKTtcclxuICAgIFxyXG4gICAgZm9yIChjb25zdCBub2RlIG9mIHN0cnVjdHVyZSkge1xyXG4gICAgICBjb25zdCBub2RlUGF0aCA9IHBhcmVudFBhdGggPyBgJHtwYXJlbnRQYXRofS8ke25vZGUuSWR9LSR7bm9kZS5uYW1lfWAgOiBgJHtub2RlLklkfS0ke25vZGUubmFtZX1gO1xyXG4gICAgICBtYXAuc2V0KG5vZGVQYXRoLCBub2RlKTtcclxuICAgICAgXHJcbiAgICAgIGlmIChub2RlLkNoaWxkcmVuKSB7XHJcbiAgICAgICAgdHJ5IHtcclxuICAgICAgICAgIGNvbnN0IGNoaWxkcmVuID0gSlNPTi5wYXJzZShub2RlLkNoaWxkcmVuKTtcclxuICAgICAgICAgIGNvbnN0IGNoaWxkTWFwID0gdGhpcy5jcmVhdGVOb2RlTWFwKGNoaWxkcmVuLCBub2RlUGF0aCk7XHJcbiAgICAgICAgICBmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBjaGlsZE1hcC5lbnRyaWVzKCkpIHtcclxuICAgICAgICAgICAgbWFwLnNldChrZXksIHZhbHVlKTtcclxuICAgICAgICAgIH1cclxuICAgICAgICB9IGNhdGNoIChlKSB7XHJcbiAgICAgICAgICAvLyBJZ25vcmUgcGFyc2luZyBlcnJvcnNcclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIH1cclxuICAgIFxyXG4gICAgcmV0dXJuIG1hcDtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgY29tcGFyZU5vZGVzKGxlZnQ6IGFueSwgcmlnaHQ6IGFueSk6IHN0cmluZ1tdIHtcclxuICAgIGNvbnN0IG1vZGlmaWVkUHJvcHM6IHN0cmluZ1tdID0gW107XHJcbiAgICBcclxuICAgIHRyeSB7XHJcbiAgICAgIGNvbnN0IGxlZnRQcm9wcyA9IEpTT04ucGFyc2UobGVmdC5Qcm9wZXJ0aWVzIHx8ICd7fScpO1xyXG4gICAgICBjb25zdCByaWdodFByb3BzID0gSlNPTi5wYXJzZShyaWdodC5Qcm9wZXJ0aWVzIHx8ICd7fScpO1xyXG4gICAgICBcclxuICAgICAgY29uc3QgYWxsS2V5cyA9IG5ldyBTZXQoW1xyXG4gICAgICAgIC4uLk9iamVjdC5rZXlzKGxlZnRQcm9wcyksXHJcbiAgICAgICAgLi4uT2JqZWN0LmtleXMocmlnaHRQcm9wcylcclxuICAgICAgXSk7XHJcbiAgICAgIFxyXG4gICAgICBmb3IgKGNvbnN0IGtleSBvZiBhbGxLZXlzKSB7XHJcbiAgICAgICAgaWYgKGxlZnRQcm9wc1trZXldICE9PSByaWdodFByb3BzW2tleV0pIHtcclxuICAgICAgICAgIG1vZGlmaWVkUHJvcHMucHVzaChrZXkpO1xyXG4gICAgICAgIH1cclxuICAgICAgfVxyXG4gICAgfSBjYXRjaCAoZSkge1xyXG4gICAgICAvLyBJZ25vcmUgcGFyc2luZyBlcnJvcnNcclxuICAgIH1cclxuICAgIFxyXG4gICAgcmV0dXJuIG1vZGlmaWVkUHJvcHM7XHJcbiAgfVxyXG59Il19
|
package/esm2022/public-api.mjs
CHANGED
|
@@ -5,4 +5,7 @@ export * from './lib/concepto-user-controls.service';
|
|
|
5
5
|
export * from './lib/concepto-user-controls.component';
|
|
6
6
|
export * from './lib/concepto-message/concepto-message.component';
|
|
7
7
|
export * from './lib/concepto-context-menu/concepto-context-menu.component';
|
|
8
|
-
|
|
8
|
+
export * from './lib/concepto-tree/concepto-tree.component';
|
|
9
|
+
export * from './lib/entity-comparison/components/entity-comparison.component';
|
|
10
|
+
export * from './lib/entity-comparison/core/services/entity-comparison.service';
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL2NvbmNlcHRvLXVzZXItY29udHJvbHMvc3JjL3B1YmxpYy1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsd0NBQXdDLENBQUM7QUFDdkQsY0FBYyxtREFBbUQsQ0FBQztBQUNsRSxjQUFjLDZEQUE2RCxDQUFDO0FBQzVFLGNBQWMsNkNBQTZDLENBQUM7QUFDNUQsY0FBYyxnRUFBZ0UsQ0FBQztBQUMvRSxjQUFjLGlFQUFpRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLypcclxuICogUHVibGljIEFQSSBTdXJmYWNlIG9mIGNvbmNlcHRvLW1lc3NhZ2VcclxuICovXHJcblxyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb25jZXB0by11c2VyLWNvbnRyb2xzLnNlcnZpY2UnO1xyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb25jZXB0by11c2VyLWNvbnRyb2xzLmNvbXBvbmVudCc7XHJcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbmNlcHRvLW1lc3NhZ2UvY29uY2VwdG8tbWVzc2FnZS5jb21wb25lbnQnO1xyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb25jZXB0by1jb250ZXh0LW1lbnUvY29uY2VwdG8tY29udGV4dC1tZW51LmNvbXBvbmVudCc7XHJcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbmNlcHRvLXRyZWUvY29uY2VwdG8tdHJlZS5jb21wb25lbnQnO1xyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9lbnRpdHktY29tcGFyaXNvbi9jb21wb25lbnRzL2VudGl0eS1jb21wYXJpc29uLmNvbXBvbmVudCc7XHJcbmV4cG9ydCAqIGZyb20gJy4vbGliL2VudGl0eS1jb21wYXJpc29uL2NvcmUvc2VydmljZXMvZW50aXR5LWNvbXBhcmlzb24uc2VydmljZSc7Il19
|
|
@@ -68,7 +68,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
68
68
|
class ConceptoContextMenuComponent {
|
|
69
69
|
elRef;
|
|
70
70
|
nodes = [];
|
|
71
|
-
itemSelected = new EventEmitter();
|
|
72
71
|
menuTree = [];
|
|
73
72
|
visible = false;
|
|
74
73
|
pos = { x: 0, y: 0 };
|
|
@@ -112,7 +111,6 @@ class ConceptoContextMenuComponent {
|
|
|
112
111
|
onOptionClick(option) {
|
|
113
112
|
if (!option.children || option.children.length === 0) {
|
|
114
113
|
console.log('Selected:', option);
|
|
115
|
-
this.itemSelected.emit(option.NodesId);
|
|
116
114
|
this.hide();
|
|
117
115
|
}
|
|
118
116
|
}
|
|
@@ -120,20 +118,356 @@ class ConceptoContextMenuComponent {
|
|
|
120
118
|
return !!option.children && option.children.length > 0;
|
|
121
119
|
}
|
|
122
120
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ConceptoContextMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
123
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ConceptoContextMenuComponent, isStandalone: true, selector: "concepto-context-menu", inputs: { nodes: "nodes" },
|
|
121
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ConceptoContextMenuComponent, isStandalone: true, selector: "concepto-context-menu", inputs: { nodes: "nodes" }, host: { listeners: { "document:click": "onOutsideClick($event)" } }, usesOnChanges: true, ngImport: i0, template: "<div class=\"context-menu\" *ngIf=\"visible\" [ngStyle]=\"{ top: pos.y + 'px', left: pos.x + 'px' }\">\r\n <ul class=\"menu-root\">\r\n <ng-container *ngFor=\"let item of menuTree\">\r\n <ng-container *ngTemplateOutlet=\"renderNode; context: { $implicit: item }\"></ng-container>\r\n </ng-container>\r\n </ul>\r\n</div>\r\n\r\n<ng-template #renderNode let-node>\r\n <li class=\"menu-item\" [class.has-children]=\"hasChildren(node)\">\r\n <div (click)=\"onOptionClick(node)\">\r\n @if (node.NodesImg) {\r\n <img [src]=\"node.NodesImg\" class=\"icon\" />\r\n }\r\n @if (!node.NodesImg) {\r\n <div class=\"icon\"></div>\r\n }\r\n {{ node.NodesText }}\r\n </div>\r\n <ul class=\"submenu\" *ngIf=\"hasChildren(node)\">\r\n <ng-container *ngFor=\"let child of node.children\">\r\n <ng-container *ngTemplateOutlet=\"renderNode; context: { $implicit: child }\"></ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n</ng-template>", styles: [".context-menu{position:fixed;z-index:9999;background-color:#fff;border:1px solid #ccc;box-shadow:2px 2px 8px #00000026;min-width:200px;font-family:sans-serif}.menu-root,.submenu{list-style:none;margin:0;padding:0}.menu-item{position:relative}.menu-item>div{padding:8px 12px;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.menu-item>div:hover{background-color:#f0f0f0}.menu-item.has-children>div:after{content:\"\\25b6\";margin-left:auto;font-size:10px}.submenu{display:none;position:absolute;top:0;left:100%;border:1px solid #ccc;background-color:#fff;min-width:180px;box-shadow:2px 2px 6px #0000001a}.menu-item:hover>.submenu{display:block}.icon{width:16px;height:16px;margin-right:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
|
|
124
122
|
}
|
|
125
123
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ConceptoContextMenuComponent, decorators: [{
|
|
126
124
|
type: Component,
|
|
127
125
|
args: [{ selector: 'concepto-context-menu', standalone: true, imports: [CommonModule], template: "<div class=\"context-menu\" *ngIf=\"visible\" [ngStyle]=\"{ top: pos.y + 'px', left: pos.x + 'px' }\">\r\n <ul class=\"menu-root\">\r\n <ng-container *ngFor=\"let item of menuTree\">\r\n <ng-container *ngTemplateOutlet=\"renderNode; context: { $implicit: item }\"></ng-container>\r\n </ng-container>\r\n </ul>\r\n</div>\r\n\r\n<ng-template #renderNode let-node>\r\n <li class=\"menu-item\" [class.has-children]=\"hasChildren(node)\">\r\n <div (click)=\"onOptionClick(node)\">\r\n @if (node.NodesImg) {\r\n <img [src]=\"node.NodesImg\" class=\"icon\" />\r\n }\r\n @if (!node.NodesImg) {\r\n <div class=\"icon\"></div>\r\n }\r\n {{ node.NodesText }}\r\n </div>\r\n <ul class=\"submenu\" *ngIf=\"hasChildren(node)\">\r\n <ng-container *ngFor=\"let child of node.children\">\r\n <ng-container *ngTemplateOutlet=\"renderNode; context: { $implicit: child }\"></ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n</ng-template>", styles: [".context-menu{position:fixed;z-index:9999;background-color:#fff;border:1px solid #ccc;box-shadow:2px 2px 8px #00000026;min-width:200px;font-family:sans-serif}.menu-root,.submenu{list-style:none;margin:0;padding:0}.menu-item{position:relative}.menu-item>div{padding:8px 12px;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.menu-item>div:hover{background-color:#f0f0f0}.menu-item.has-children>div:after{content:\"\\25b6\";margin-left:auto;font-size:10px}.submenu{display:none;position:absolute;top:0;left:100%;border:1px solid #ccc;background-color:#fff;min-width:180px;box-shadow:2px 2px 6px #0000001a}.menu-item:hover>.submenu{display:block}.icon{width:16px;height:16px;margin-right:8px}\n"] }]
|
|
128
126
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { nodes: [{
|
|
129
127
|
type: Input
|
|
130
|
-
}], itemSelected: [{
|
|
131
|
-
type: Output
|
|
132
128
|
}], onOutsideClick: [{
|
|
133
129
|
type: HostListener,
|
|
134
130
|
args: ['document:click', ['$event']]
|
|
135
131
|
}] } });
|
|
136
132
|
|
|
133
|
+
class ConceptoTreeComponent {
|
|
134
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ConceptoTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
135
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: ConceptoTreeComponent, isStandalone: true, selector: "lib-concepto-tree", ngImport: i0, template: `
|
|
136
|
+
<p>
|
|
137
|
+
concepto-tree works!
|
|
138
|
+
</p>
|
|
139
|
+
`, isInline: true, styles: [""] });
|
|
140
|
+
}
|
|
141
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ConceptoTreeComponent, decorators: [{
|
|
142
|
+
type: Component,
|
|
143
|
+
args: [{ selector: 'lib-concepto-tree', standalone: true, imports: [], template: `
|
|
144
|
+
<p>
|
|
145
|
+
concepto-tree works!
|
|
146
|
+
</p>
|
|
147
|
+
` }]
|
|
148
|
+
}] });
|
|
149
|
+
|
|
150
|
+
class EntityComparisonService {
|
|
151
|
+
parseEntities(xmlString) {
|
|
152
|
+
const parser = new DOMParser();
|
|
153
|
+
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
|
|
154
|
+
const exportItems = xmlDoc.querySelectorAll('ExportItem[Type="Entity"]');
|
|
155
|
+
const entities = [];
|
|
156
|
+
exportItems.forEach((item) => {
|
|
157
|
+
const entity = item.querySelector('Entity_WithoutRedundancies');
|
|
158
|
+
if (!entity)
|
|
159
|
+
return;
|
|
160
|
+
const structureJson = entity.querySelector('EntityStructureJson')?.textContent;
|
|
161
|
+
if (!structureJson)
|
|
162
|
+
return;
|
|
163
|
+
try {
|
|
164
|
+
const structure = JSON.parse(structureJson);
|
|
165
|
+
entities.push({
|
|
166
|
+
id: entity.querySelector('EntityId')?.textContent || '',
|
|
167
|
+
name: entity.querySelector('EntityName')?.textContent || '',
|
|
168
|
+
structure: structure
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
console.error('Error parsing EntityStructureJson:', e);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return entities;
|
|
176
|
+
}
|
|
177
|
+
compareStructures(leftStructure, rightStructure) {
|
|
178
|
+
const leftOnly = [];
|
|
179
|
+
const rightOnly = [];
|
|
180
|
+
const modified = [];
|
|
181
|
+
const unchanged = [];
|
|
182
|
+
// Create maps for quick lookup
|
|
183
|
+
const leftMap = this.createNodeMap(leftStructure);
|
|
184
|
+
const rightMap = this.createNodeMap(rightStructure);
|
|
185
|
+
// Find nodes only in left or modified
|
|
186
|
+
for (const [key, leftNode] of leftMap.entries()) {
|
|
187
|
+
if (!rightMap.has(key)) {
|
|
188
|
+
leftOnly.push(leftNode);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const rightNode = rightMap.get(key);
|
|
192
|
+
const diff = this.compareNodes(leftNode, rightNode);
|
|
193
|
+
if (diff.length > 0) {
|
|
194
|
+
modified.push({ ...leftNode, modifiedProperties: diff });
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
unchanged.push(leftNode);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Find nodes only in right
|
|
202
|
+
for (const [key, rightNode] of rightMap.entries()) {
|
|
203
|
+
if (!leftMap.has(key)) {
|
|
204
|
+
rightOnly.push(rightNode);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { leftOnly, rightOnly, modified, unchanged };
|
|
208
|
+
}
|
|
209
|
+
createNodeMap(structure, parentPath = '') {
|
|
210
|
+
const map = new Map();
|
|
211
|
+
for (const node of structure) {
|
|
212
|
+
const nodePath = parentPath ? `${parentPath}/${node.Id}-${node.name}` : `${node.Id}-${node.name}`;
|
|
213
|
+
map.set(nodePath, node);
|
|
214
|
+
if (node.Children) {
|
|
215
|
+
try {
|
|
216
|
+
const children = JSON.parse(node.Children);
|
|
217
|
+
const childMap = this.createNodeMap(children, nodePath);
|
|
218
|
+
for (const [key, value] of childMap.entries()) {
|
|
219
|
+
map.set(key, value);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
// Ignore parsing errors
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return map;
|
|
228
|
+
}
|
|
229
|
+
compareNodes(left, right) {
|
|
230
|
+
const modifiedProps = [];
|
|
231
|
+
try {
|
|
232
|
+
const leftProps = JSON.parse(left.Properties || '{}');
|
|
233
|
+
const rightProps = JSON.parse(right.Properties || '{}');
|
|
234
|
+
const allKeys = new Set([
|
|
235
|
+
...Object.keys(leftProps),
|
|
236
|
+
...Object.keys(rightProps)
|
|
237
|
+
]);
|
|
238
|
+
for (const key of allKeys) {
|
|
239
|
+
if (leftProps[key] !== rightProps[key]) {
|
|
240
|
+
modifiedProps.push(key);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
// Ignore parsing errors
|
|
246
|
+
}
|
|
247
|
+
return modifiedProps;
|
|
248
|
+
}
|
|
249
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
250
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonService, providedIn: 'root' });
|
|
251
|
+
}
|
|
252
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonService, decorators: [{
|
|
253
|
+
type: Injectable,
|
|
254
|
+
args: [{
|
|
255
|
+
providedIn: 'root'
|
|
256
|
+
}]
|
|
257
|
+
}] });
|
|
258
|
+
|
|
259
|
+
class EntityComparisonComponent {
|
|
260
|
+
comparisonService;
|
|
261
|
+
leftFile = '';
|
|
262
|
+
rightFile = '';
|
|
263
|
+
leftEntities = [];
|
|
264
|
+
rightEntities = [];
|
|
265
|
+
selectedLeftEntity = null;
|
|
266
|
+
selectedRightEntity = null;
|
|
267
|
+
comparisonResult = null;
|
|
268
|
+
expandedNodes = new Set();
|
|
269
|
+
isFullScreen = false;
|
|
270
|
+
currentDifferenceIndex = 0;
|
|
271
|
+
differences = [];
|
|
272
|
+
constructor(comparisonService) {
|
|
273
|
+
this.comparisonService = comparisonService;
|
|
274
|
+
}
|
|
275
|
+
onLeftFileSelected(event) {
|
|
276
|
+
const file = event.target.files[0];
|
|
277
|
+
if (!file)
|
|
278
|
+
return;
|
|
279
|
+
this.leftFile = file.name;
|
|
280
|
+
const reader = new FileReader();
|
|
281
|
+
reader.onload = (e) => {
|
|
282
|
+
const xmlContent = e.target.result;
|
|
283
|
+
this.leftEntities = this.comparisonService.parseEntities(xmlContent);
|
|
284
|
+
this.selectedLeftEntity = null;
|
|
285
|
+
this.comparisonResult = null;
|
|
286
|
+
};
|
|
287
|
+
reader.readAsText(file);
|
|
288
|
+
}
|
|
289
|
+
onRightFileSelected(event) {
|
|
290
|
+
const file = event.target.files[0];
|
|
291
|
+
if (!file)
|
|
292
|
+
return;
|
|
293
|
+
this.rightFile = file.name;
|
|
294
|
+
const reader = new FileReader();
|
|
295
|
+
reader.onload = (e) => {
|
|
296
|
+
const xmlContent = e.target.result;
|
|
297
|
+
this.rightEntities = this.comparisonService.parseEntities(xmlContent);
|
|
298
|
+
this.selectedRightEntity = null;
|
|
299
|
+
this.comparisonResult = null;
|
|
300
|
+
};
|
|
301
|
+
reader.readAsText(file);
|
|
302
|
+
}
|
|
303
|
+
selectLeftEntity(entity) {
|
|
304
|
+
this.selectedLeftEntity = entity;
|
|
305
|
+
this.compareIfBothSelected();
|
|
306
|
+
}
|
|
307
|
+
selectRightEntity(entity) {
|
|
308
|
+
this.selectedRightEntity = entity;
|
|
309
|
+
this.compareIfBothSelected();
|
|
310
|
+
}
|
|
311
|
+
compareIfBothSelected() {
|
|
312
|
+
if (this.selectedLeftEntity && this.selectedRightEntity) {
|
|
313
|
+
this.comparisonResult = this.comparisonService.compareStructures(this.selectedLeftEntity.structure, this.selectedRightEntity.structure);
|
|
314
|
+
this.buildDifferencesList();
|
|
315
|
+
this.currentDifferenceIndex = 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
buildDifferencesList() {
|
|
319
|
+
if (!this.comparisonResult) {
|
|
320
|
+
this.differences = [];
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.differences = [
|
|
324
|
+
...this.comparisonResult.leftOnly.map(node => ({ node, type: 'removed' })),
|
|
325
|
+
...this.comparisonResult.rightOnly.map(node => ({ node, type: 'added' })),
|
|
326
|
+
...this.comparisonResult.modified.map(node => ({ node, type: 'modified' }))
|
|
327
|
+
];
|
|
328
|
+
}
|
|
329
|
+
navigateToNextDifference() {
|
|
330
|
+
if (this.differences.length === 0)
|
|
331
|
+
return;
|
|
332
|
+
this.currentDifferenceIndex = (this.currentDifferenceIndex + 1) % this.differences.length;
|
|
333
|
+
this.scrollToDifference();
|
|
334
|
+
}
|
|
335
|
+
navigateToPreviousDifference() {
|
|
336
|
+
if (this.differences.length === 0)
|
|
337
|
+
return;
|
|
338
|
+
this.currentDifferenceIndex = this.currentDifferenceIndex === 0
|
|
339
|
+
? this.differences.length - 1
|
|
340
|
+
: this.currentDifferenceIndex - 1;
|
|
341
|
+
this.scrollToDifference();
|
|
342
|
+
}
|
|
343
|
+
scrollToDifference() {
|
|
344
|
+
if (this.differences.length === 0)
|
|
345
|
+
return;
|
|
346
|
+
const currentDiff = this.differences[this.currentDifferenceIndex];
|
|
347
|
+
const nodeId = `${currentDiff.node.Id}-${currentDiff.node.name}`;
|
|
348
|
+
// Expand parent nodes FIRST to make the difference visible
|
|
349
|
+
this.expandPathToNode(currentDiff.node);
|
|
350
|
+
// Wait for DOM to update after expansion, then scroll
|
|
351
|
+
setTimeout(() => {
|
|
352
|
+
const leftElement = document.querySelector(`[data-node-id="left-${nodeId}"]`);
|
|
353
|
+
const rightElement = document.querySelector(`[data-node-id="right-${nodeId}"]`);
|
|
354
|
+
if (leftElement) {
|
|
355
|
+
leftElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
356
|
+
}
|
|
357
|
+
if (rightElement) {
|
|
358
|
+
rightElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
359
|
+
}
|
|
360
|
+
}, 100);
|
|
361
|
+
}
|
|
362
|
+
expandPathToNode(node) {
|
|
363
|
+
// Find and expand all parent nodes in both structures
|
|
364
|
+
this.expandParentsInStructure(this.selectedLeftEntity.structure, node, 'left');
|
|
365
|
+
this.expandParentsInStructure(this.selectedRightEntity.structure, node, 'right');
|
|
366
|
+
}
|
|
367
|
+
expandParentsInStructure(structure, targetNode, side) {
|
|
368
|
+
for (const node of structure) {
|
|
369
|
+
const nodeId = `${side}-${node.Id}-${node.name}`;
|
|
370
|
+
const targetId = `${targetNode.Id}-${targetNode.name}`;
|
|
371
|
+
const currentId = `${node.Id}-${node.name}`;
|
|
372
|
+
// If this is the target node, we found it
|
|
373
|
+
if (currentId === targetId) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
// Check children if they exist
|
|
377
|
+
if (this.hasChildren(node)) {
|
|
378
|
+
const children = this.getChildren(node);
|
|
379
|
+
if (this.expandParentsInStructure(children, targetNode, side)) {
|
|
380
|
+
// If the target is in this branch, expand this node
|
|
381
|
+
this.expandedNodes.add(nodeId);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
get hasDifferences() {
|
|
389
|
+
return this.differences.length > 0;
|
|
390
|
+
}
|
|
391
|
+
get currentDifferenceNumber() {
|
|
392
|
+
return this.currentDifferenceIndex + 1;
|
|
393
|
+
}
|
|
394
|
+
get totalDifferences() {
|
|
395
|
+
return this.differences.length;
|
|
396
|
+
}
|
|
397
|
+
toggleNode(nodeId) {
|
|
398
|
+
if (this.expandedNodes.has(nodeId)) {
|
|
399
|
+
this.expandedNodes.delete(nodeId);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
this.expandedNodes.add(nodeId);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
isExpanded(nodeId) {
|
|
406
|
+
return this.expandedNodes.has(nodeId);
|
|
407
|
+
}
|
|
408
|
+
getNodeId(node, prefix) {
|
|
409
|
+
return `${prefix}-${node.Id}-${node.name}`;
|
|
410
|
+
}
|
|
411
|
+
hasChildren(node) {
|
|
412
|
+
return node.Children && JSON.parse(node.Children).length > 0;
|
|
413
|
+
}
|
|
414
|
+
getChildren(node) {
|
|
415
|
+
if (!node.Children)
|
|
416
|
+
return [];
|
|
417
|
+
try {
|
|
418
|
+
return JSON.parse(node.Children);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
getProperties(node) {
|
|
425
|
+
if (!node.Properties)
|
|
426
|
+
return {};
|
|
427
|
+
try {
|
|
428
|
+
return JSON.parse(node.Properties);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return {};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
getPropertyKeys(node) {
|
|
435
|
+
const props = this.getProperties(node);
|
|
436
|
+
return Object.keys(props);
|
|
437
|
+
}
|
|
438
|
+
getChangeType(node) {
|
|
439
|
+
if (!this.comparisonResult)
|
|
440
|
+
return '';
|
|
441
|
+
const nodeId = `${node.Id}-${node.name}`;
|
|
442
|
+
if (this.comparisonResult.leftOnly.some(n => `${n.Id}-${n.name}` === nodeId)) {
|
|
443
|
+
return 'removed';
|
|
444
|
+
}
|
|
445
|
+
if (this.comparisonResult.rightOnly.some(n => `${n.Id}-${n.name}` === nodeId)) {
|
|
446
|
+
return 'added';
|
|
447
|
+
}
|
|
448
|
+
if (this.comparisonResult.modified.some(n => `${n.Id}-${n.name}` === nodeId)) {
|
|
449
|
+
return 'modified';
|
|
450
|
+
}
|
|
451
|
+
return 'unchanged';
|
|
452
|
+
}
|
|
453
|
+
getModifiedProperties(node) {
|
|
454
|
+
if (!this.comparisonResult)
|
|
455
|
+
return [];
|
|
456
|
+
const nodeId = `${node.Id}-${node.name}`;
|
|
457
|
+
const modified = this.comparisonResult.modified.find(n => `${n.Id}-${n.name}` === nodeId);
|
|
458
|
+
return modified?.modifiedProperties || [];
|
|
459
|
+
}
|
|
460
|
+
toggleFullScreen() {
|
|
461
|
+
this.isFullScreen = !this.isFullScreen;
|
|
462
|
+
}
|
|
463
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonComponent, deps: [{ token: EntityComparisonService }], target: i0.ɵɵFactoryTarget.Component });
|
|
464
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: EntityComparisonComponent, isStandalone: true, selector: "app-entity-comparison", providers: [EntityComparisonService], ngImport: i0, template: "<div class=\"comparison-container\" [class.fullscreen]=\"isFullScreen\">\r\n <header class=\"header\">\r\n <h1>Entity Structure Comparison</h1>\r\n <p class=\"subtitle\">Compare EntityStructureJson between two entity exports</p>\r\n </header>\r\n\r\n <div class=\"file-selection\">\r\n <div class=\"file-input-group\">\r\n <label class=\"file-label left\">\r\n <input type=\"file\" accept=\".xml\" (change)=\"onLeftFileSelected($event)\" hidden>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\r\n <polyline points=\"17 8 12 3 7 8\" />\r\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" />\r\n </svg>\r\n Load Left Entity\r\n </label>\r\n <span *ngIf=\"leftFile\" class=\"file-name\">{{ leftFile }}</span>\r\n </div>\r\n\r\n <div class=\"vs-separator\">VS</div>\r\n\r\n <div class=\"file-input-group\">\r\n <label class=\"file-label right\">\r\n <input type=\"file\" accept=\".xml\" (change)=\"onRightFileSelected($event)\" hidden>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\r\n <polyline points=\"17 8 12 3 7 8\" />\r\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" />\r\n </svg>\r\n Load Right Entity\r\n </label>\r\n <span *ngIf=\"rightFile\" class=\"file-name\">{{ rightFile }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"entity-selection\" *ngIf=\"leftEntities.length > 0 || rightEntities.length > 0\">\r\n <div class=\"entity-list\">\r\n <h3>Left Entities</h3>\r\n <div class=\"entities\">\r\n <div *ngFor=\"let entity of leftEntities\" class=\"entity-card\"\r\n [class.selected]=\"selectedLeftEntity === entity\" (click)=\"selectLeftEntity(entity)\">\r\n <div class=\"entity-icon\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\">\r\n <path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\" />\r\n </svg>\r\n </div>\r\n <div class=\"entity-info\">\r\n <div class=\"entity-name\">{{ entity.name }}</div>\r\n <div class=\"entity-id\">{{ entity.id }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"entity-list\">\r\n <h3>Right Entities</h3>\r\n <div class=\"entities\">\r\n <div *ngFor=\"let entity of rightEntities\" class=\"entity-card\"\r\n [class.selected]=\"selectedRightEntity === entity\" (click)=\"selectRightEntity(entity)\">\r\n <div class=\"entity-icon\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\">\r\n <path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\" />\r\n </svg>\r\n </div>\r\n <div class=\"entity-info\">\r\n <div class=\"entity-name\">{{ entity.name }}</div>\r\n <div class=\"entity-id\">{{ entity.id }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"summary-section\" *ngIf=\"comparisonResult\">\r\n <div class=\"summary\">\r\n <div class=\"summary-item added\">\r\n <span class=\"count\">{{ comparisonResult.rightOnly.length }}</span>\r\n <span class=\"label\">Added</span>\r\n </div>\r\n <div class=\"summary-item removed\">\r\n <span class=\"count\">{{ comparisonResult.leftOnly.length }}</span>\r\n <span class=\"label\">Removed</span>\r\n </div>\r\n <div class=\"summary-item modified\">\r\n <span class=\"count\">{{ comparisonResult.modified.length }}</span>\r\n <span class=\"label\">Modified</span>\r\n </div>\r\n <div class=\"summary-item unchanged\">\r\n <span class=\"count\">{{ comparisonResult.unchanged.length }}</span>\r\n <span class=\"label\">Unchanged</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"comparison-results\" *ngIf=\"comparisonResult\">\r\n <div class=\"results-header\">\r\n <div class=\"header-title\">\r\n <h2>Comparison Results</h2>\r\n <div class=\"header-actions\">\r\n <div class=\"diff-navigation\" *ngIf=\"hasDifferences\">\r\n <button class=\"nav-btn\" (click)=\"navigateToPreviousDifference()\" title=\"Previous Difference\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline points=\"18 15 12 9 6 15\" />\r\n </svg>\r\n </button>\r\n <span class=\"diff-counter\">{{ currentDifferenceNumber }} / {{ totalDifferences }}</span>\r\n <button class=\"nav-btn\" (click)=\"navigateToNextDifference()\" title=\"Next Difference\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n </div>\r\n <button class=\"fullscreen-btn\" (click)=\"toggleFullScreen()\" [title]=\"isFullScreen ? 'Exit Full Screen' : 'Enter Full Screen'\">\r\n <svg *ngIf=\"!isFullScreen\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3\" />\r\n </svg>\r\n <svg *ngIf=\"isFullScreen\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3\" />\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"structure-comparison\">\r\n <div class=\"comparison-section\">\r\n <h3>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M9 11l3 3L22 4\" />\r\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\" />\r\n </svg>\r\n {{ selectedLeftEntity.name }}\r\n </h3>\r\n <div class=\"structure-tree\">\r\n <ng-container *ngFor=\"let node of selectedLeftEntity.structure\">\r\n <ng-container\r\n *ngTemplateOutlet=\"nodeTemplate; context: { $implicit: node, side: 'left' }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <div class=\"comparison-section\">\r\n <h3>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M9 11l3 3L22 4\" />\r\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\" />\r\n </svg>\r\n {{ selectedRightEntity.name }}\r\n </h3>\r\n <div class=\"structure-tree\">\r\n <ng-container *ngFor=\"let node of selectedRightEntity.structure\">\r\n <ng-container\r\n *ngTemplateOutlet=\"nodeTemplate; context: { $implicit: node, side: 'right' }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<ng-template #nodeTemplate let-node let-side=\"side\">\r\n <div class=\"tree-node\" [class]=\"getChangeType(node)\" [attr.data-node-id]=\"getNodeId(node, side)\">\r\n <div class=\"node-header\">\r\n <button *ngIf=\"hasChildren(node)\" class=\"expand-btn\" (click)=\"toggleNode(getNodeId(node, side))\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline *ngIf=\"!isExpanded(getNodeId(node, side))\" points=\"9 18 15 12 9 6\" />\r\n <polyline *ngIf=\"isExpanded(getNodeId(node, side))\" points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n <div *ngIf=\"!hasChildren(node)\" class=\"spacer\"></div>\r\n\r\n <div class=\"node-icon\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path *ngIf=\"node.nodeType === 'Group'\"\r\n d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\" />\r\n <circle *ngIf=\"node.nodeType === 'Attribute'\" cx=\"12\" cy=\"12\" r=\"10\" />\r\n </svg>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <span class=\"node-name\">{{ node.name }}</span>\r\n <span class=\"node-type\">{{ node.nodeType }}</span>\r\n <span class=\"change-badge\" *ngIf=\"getChangeType(node) !== 'unchanged'\">\r\n {{ getChangeType(node) }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-properties\" *ngIf=\"getPropertyKeys(node).length > 0\">\r\n <div *ngFor=\"let key of getPropertyKeys(node)\" class=\"property\"\r\n [class.modified]=\"getModifiedProperties(node).includes(key)\">\r\n <span class=\"property-key\">{{ key }}:</span>\r\n <span class=\"property-value\">{{ getProperties(node)[key] }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-children\" *ngIf=\"hasChildren(node) && isExpanded(getNodeId(node, side))\">\r\n <ng-container *ngFor=\"let child of getChildren(node)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"nodeTemplate; context: { $implicit: child, side: side }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n</ng-template>", styles: [".comparison-container{display:flex;flex-direction:column;height:100%;width:100%;background:#f9fafb}.header{background:#fff;padding:1.5rem;border-bottom:1px solid #e5e7eb;box-shadow:0 1px 3px #0000001a}.header h1{margin:0;font-size:1.5rem;font-weight:700;color:#1f2937}.subtitle{margin:.5rem 0 0;font-size:.875rem;color:#6b7280}.file-selection{display:flex;align-items:center;gap:2rem;padding:1.5rem;background:#fff;border-bottom:1px solid #e5e7eb}.file-input-group{flex:1;display:flex;flex-direction:column;gap:.5rem}.file-label{display:inline-flex;align-items:center;gap:.5rem;padding:.75rem 1.25rem;border:2px solid #2563eb;border-radius:.5rem;cursor:pointer;font-weight:500;transition:all .2s;justify-content:center}.file-label.left{background:#fff;color:#2563eb}.file-label.left:hover{background:#eff6ff}.file-label.right{background:#fff;color:#7c3aed;border-color:#7c3aed}.file-label.right:hover{background:#f5f3ff}.file-name{font-size:.875rem;color:#6b7280;text-align:center}.vs-separator{padding:.75rem 1.5rem;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;font-weight:700;border-radius:2rem;font-size:1.25rem;box-shadow:0 4px 6px #0000001a}.entity-selection{display:flex;gap:1rem;padding:1rem;background:#fff;border-bottom:1px solid #e5e7eb}.entity-list{flex:1}.entity-list h3{margin:0 0 .75rem;font-size:.875rem;font-weight:600;color:#6b7280;text-transform:uppercase}.entities{display:flex;flex-direction:column;gap:.5rem}.entity-card{display:flex;align-items:center;gap:.75rem;padding:.75rem;border:1px solid #e5e7eb;border-radius:.5rem;cursor:pointer;transition:all .2s}.entity-card:hover{background:#f9fafb;border-color:#d1d5db}.entity-card.selected{background:#eff6ff;border-color:#2563eb}.entity-icon{color:#6b7280}.entity-card.selected .entity-icon{color:#2563eb}.entity-info{flex:1}.entity-name{font-size:.875rem;font-weight:500;color:#1f2937}.entity-id{font-size:.75rem;color:#6b7280}.summary-section{padding:1.5rem 1.5rem 0;background:#f9fafb}.comparison-results{flex:1;overflow:auto;padding:1.5rem}.results-header{margin-bottom:1.5rem;position:sticky;top:0;background:#f9fafb;z-index:100;padding-top:1rem;padding-bottom:1rem;margin-top:-1rem}.header-title{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.results-header h2{margin:0;font-size:1.25rem;font-weight:700;color:#1f2937}.header-actions{display:flex;align-items:center;gap:1rem}.diff-navigation{display:flex;align-items:center;gap:.5rem;background:#fff;border:1px solid #e5e7eb;border-radius:.5rem;padding:.25rem}.nav-btn{background:#fff;border:1px solid #e5e7eb;border-radius:.375rem;padding:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;color:#6b7280}.nav-btn:hover{background:#f9fafb;border-color:#2563eb;color:#2563eb}.diff-counter{font-size:.875rem;font-weight:600;color:#1f2937;padding:0 .5rem;white-space:nowrap;min-width:60px;text-align:center}.fullscreen-btn{background:#fff;border:1px solid #e5e7eb;border-radius:.5rem;padding:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;color:#6b7280}.fullscreen-btn:hover{background:#f9fafb;border-color:#2563eb;color:#2563eb}.summary{display:flex;gap:1rem}.summary-item{flex:1;display:flex;flex-direction:column;align-items:center;padding:1rem;border-radius:.5rem;background:#fff;border:2px solid}.summary-item.added{border-color:#10b981;background:#ecfdf5}.summary-item.removed{border-color:#ef4444;background:#fef2f2}.summary-item.modified{border-color:#f59e0b;background:#fffbeb}.summary-item.unchanged{border-color:#6b7280;background:#f9fafb}.summary-item .count{font-size:1.5rem;font-weight:700}.summary-item.added .count{color:#10b981}.summary-item.removed .count{color:#ef4444}.summary-item.modified .count{color:#f59e0b}.summary-item.unchanged .count{color:#6b7280}.summary-item .label{font-size:.75rem;font-weight:600;text-transform:uppercase;margin-top:.25rem}.structure-comparison{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;height:100%}.comparison-section{background:#fff;border-radius:.5rem;padding:1rem;border:1px solid #e5e7eb;min-width:0;width:100%;overflow:auto}.comparison-section h3{display:flex;align-items:center;gap:.5rem;margin:0 0 1rem;font-size:1rem;font-weight:600;color:#1f2937;padding-bottom:.75rem;border-bottom:1px solid #e5e7eb}.structure-tree{display:flex;flex-direction:column;gap:.5rem}.tree-node{border-left:3px solid transparent;padding-left:.5rem;transition:all .2s;max-width:100%;overflow:hidden}.tree-node.added{background:#ecfdf5;border-left-color:#10b981}.tree-node.removed{background:#fef2f2;border-left-color:#ef4444}.tree-node.modified{background:#fffbeb;border-left-color:#f59e0b}.node-header{display:flex;align-items:center;gap:.5rem;padding:.5rem;border-radius:.375rem}.expand-btn{background:none;border:none;cursor:pointer;padding:.25rem;display:flex;align-items:center;justify-content:center;border-radius:.25rem;transition:background .2s}.expand-btn:hover{background:#f3f4f6}.spacer{width:24px}.node-icon{color:#6b7280}.node-content{display:flex;align-items:center;gap:.5rem;flex:1}.node-name{font-size:.875rem;font-weight:500;color:#1f2937}.node-type{font-size:.75rem;color:#6b7280;background:#f3f4f6;padding:.125rem .5rem;border-radius:.25rem}.change-badge{font-size:.625rem;font-weight:600;text-transform:uppercase;padding:.125rem .5rem;border-radius:.25rem;margin-left:auto}.tree-node.added .change-badge{background:#10b981;color:#fff}.tree-node.removed .change-badge{background:#ef4444;color:#fff}.tree-node.modified .change-badge{background:#f59e0b;color:#fff}.node-properties{margin-left:2.5rem;margin-top:.5rem;padding:.5rem;background:#f9fafb;border-radius:.375rem;font-size:.75rem;max-width:100%;overflow:hidden}.property{display:flex;flex-wrap:wrap;gap:.5rem;padding:.25rem 0;max-width:100%;overflow:hidden}.property.modified{background:#fef3c7;padding:.25rem .5rem;border-radius:.25rem;margin:.125rem 0}.property-key{font-weight:600;color:#374151;flex-shrink:0}.property-value{color:#6b7280;word-break:break-word;overflow:auto;overflow-wrap:break-word;flex:1;min-width:0}.node-children{margin-left:1.5rem;margin-top:.5rem;display:flex;flex-direction:column;gap:.5rem}.comparison-container.fullscreen{position:fixed;inset:0;z-index:9999;background:#f9fafb}.comparison-container.fullscreen .header,.comparison-container.fullscreen .file-selection,.comparison-container.fullscreen .entity-selection,.comparison-container.fullscreen .summary-section{display:none}.comparison-container.fullscreen .comparison-results{height:100vh;padding:2rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
465
|
+
}
|
|
466
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EntityComparisonComponent, decorators: [{
|
|
467
|
+
type: Component,
|
|
468
|
+
args: [{ selector: 'app-entity-comparison', standalone: true, imports: [CommonModule], providers: [EntityComparisonService], template: "<div class=\"comparison-container\" [class.fullscreen]=\"isFullScreen\">\r\n <header class=\"header\">\r\n <h1>Entity Structure Comparison</h1>\r\n <p class=\"subtitle\">Compare EntityStructureJson between two entity exports</p>\r\n </header>\r\n\r\n <div class=\"file-selection\">\r\n <div class=\"file-input-group\">\r\n <label class=\"file-label left\">\r\n <input type=\"file\" accept=\".xml\" (change)=\"onLeftFileSelected($event)\" hidden>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\r\n <polyline points=\"17 8 12 3 7 8\" />\r\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" />\r\n </svg>\r\n Load Left Entity\r\n </label>\r\n <span *ngIf=\"leftFile\" class=\"file-name\">{{ leftFile }}</span>\r\n </div>\r\n\r\n <div class=\"vs-separator\">VS</div>\r\n\r\n <div class=\"file-input-group\">\r\n <label class=\"file-label right\">\r\n <input type=\"file\" accept=\".xml\" (change)=\"onRightFileSelected($event)\" hidden>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\r\n <polyline points=\"17 8 12 3 7 8\" />\r\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" />\r\n </svg>\r\n Load Right Entity\r\n </label>\r\n <span *ngIf=\"rightFile\" class=\"file-name\">{{ rightFile }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"entity-selection\" *ngIf=\"leftEntities.length > 0 || rightEntities.length > 0\">\r\n <div class=\"entity-list\">\r\n <h3>Left Entities</h3>\r\n <div class=\"entities\">\r\n <div *ngFor=\"let entity of leftEntities\" class=\"entity-card\"\r\n [class.selected]=\"selectedLeftEntity === entity\" (click)=\"selectLeftEntity(entity)\">\r\n <div class=\"entity-icon\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\">\r\n <path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\" />\r\n </svg>\r\n </div>\r\n <div class=\"entity-info\">\r\n <div class=\"entity-name\">{{ entity.name }}</div>\r\n <div class=\"entity-id\">{{ entity.id }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"entity-list\">\r\n <h3>Right Entities</h3>\r\n <div class=\"entities\">\r\n <div *ngFor=\"let entity of rightEntities\" class=\"entity-card\"\r\n [class.selected]=\"selectedRightEntity === entity\" (click)=\"selectRightEntity(entity)\">\r\n <div class=\"entity-icon\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\">\r\n <path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\" />\r\n </svg>\r\n </div>\r\n <div class=\"entity-info\">\r\n <div class=\"entity-name\">{{ entity.name }}</div>\r\n <div class=\"entity-id\">{{ entity.id }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"summary-section\" *ngIf=\"comparisonResult\">\r\n <div class=\"summary\">\r\n <div class=\"summary-item added\">\r\n <span class=\"count\">{{ comparisonResult.rightOnly.length }}</span>\r\n <span class=\"label\">Added</span>\r\n </div>\r\n <div class=\"summary-item removed\">\r\n <span class=\"count\">{{ comparisonResult.leftOnly.length }}</span>\r\n <span class=\"label\">Removed</span>\r\n </div>\r\n <div class=\"summary-item modified\">\r\n <span class=\"count\">{{ comparisonResult.modified.length }}</span>\r\n <span class=\"label\">Modified</span>\r\n </div>\r\n <div class=\"summary-item unchanged\">\r\n <span class=\"count\">{{ comparisonResult.unchanged.length }}</span>\r\n <span class=\"label\">Unchanged</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"comparison-results\" *ngIf=\"comparisonResult\">\r\n <div class=\"results-header\">\r\n <div class=\"header-title\">\r\n <h2>Comparison Results</h2>\r\n <div class=\"header-actions\">\r\n <div class=\"diff-navigation\" *ngIf=\"hasDifferences\">\r\n <button class=\"nav-btn\" (click)=\"navigateToPreviousDifference()\" title=\"Previous Difference\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline points=\"18 15 12 9 6 15\" />\r\n </svg>\r\n </button>\r\n <span class=\"diff-counter\">{{ currentDifferenceNumber }} / {{ totalDifferences }}</span>\r\n <button class=\"nav-btn\" (click)=\"navigateToNextDifference()\" title=\"Next Difference\">\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n </div>\r\n <button class=\"fullscreen-btn\" (click)=\"toggleFullScreen()\" [title]=\"isFullScreen ? 'Exit Full Screen' : 'Enter Full Screen'\">\r\n <svg *ngIf=\"!isFullScreen\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3\" />\r\n </svg>\r\n <svg *ngIf=\"isFullScreen\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3\" />\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"structure-comparison\">\r\n <div class=\"comparison-section\">\r\n <h3>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M9 11l3 3L22 4\" />\r\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\" />\r\n </svg>\r\n {{ selectedLeftEntity.name }}\r\n </h3>\r\n <div class=\"structure-tree\">\r\n <ng-container *ngFor=\"let node of selectedLeftEntity.structure\">\r\n <ng-container\r\n *ngTemplateOutlet=\"nodeTemplate; context: { $implicit: node, side: 'left' }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <div class=\"comparison-section\">\r\n <h3>\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M9 11l3 3L22 4\" />\r\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\" />\r\n </svg>\r\n {{ selectedRightEntity.name }}\r\n </h3>\r\n <div class=\"structure-tree\">\r\n <ng-container *ngFor=\"let node of selectedRightEntity.structure\">\r\n <ng-container\r\n *ngTemplateOutlet=\"nodeTemplate; context: { $implicit: node, side: 'right' }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<ng-template #nodeTemplate let-node let-side=\"side\">\r\n <div class=\"tree-node\" [class]=\"getChangeType(node)\" [attr.data-node-id]=\"getNodeId(node, side)\">\r\n <div class=\"node-header\">\r\n <button *ngIf=\"hasChildren(node)\" class=\"expand-btn\" (click)=\"toggleNode(getNodeId(node, side))\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline *ngIf=\"!isExpanded(getNodeId(node, side))\" points=\"9 18 15 12 9 6\" />\r\n <polyline *ngIf=\"isExpanded(getNodeId(node, side))\" points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n <div *ngIf=\"!hasChildren(node)\" class=\"spacer\"></div>\r\n\r\n <div class=\"node-icon\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path *ngIf=\"node.nodeType === 'Group'\"\r\n d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\" />\r\n <circle *ngIf=\"node.nodeType === 'Attribute'\" cx=\"12\" cy=\"12\" r=\"10\" />\r\n </svg>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <span class=\"node-name\">{{ node.name }}</span>\r\n <span class=\"node-type\">{{ node.nodeType }}</span>\r\n <span class=\"change-badge\" *ngIf=\"getChangeType(node) !== 'unchanged'\">\r\n {{ getChangeType(node) }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-properties\" *ngIf=\"getPropertyKeys(node).length > 0\">\r\n <div *ngFor=\"let key of getPropertyKeys(node)\" class=\"property\"\r\n [class.modified]=\"getModifiedProperties(node).includes(key)\">\r\n <span class=\"property-key\">{{ key }}:</span>\r\n <span class=\"property-value\">{{ getProperties(node)[key] }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-children\" *ngIf=\"hasChildren(node) && isExpanded(getNodeId(node, side))\">\r\n <ng-container *ngFor=\"let child of getChildren(node)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"nodeTemplate; context: { $implicit: child, side: side }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n</ng-template>", styles: [".comparison-container{display:flex;flex-direction:column;height:100%;width:100%;background:#f9fafb}.header{background:#fff;padding:1.5rem;border-bottom:1px solid #e5e7eb;box-shadow:0 1px 3px #0000001a}.header h1{margin:0;font-size:1.5rem;font-weight:700;color:#1f2937}.subtitle{margin:.5rem 0 0;font-size:.875rem;color:#6b7280}.file-selection{display:flex;align-items:center;gap:2rem;padding:1.5rem;background:#fff;border-bottom:1px solid #e5e7eb}.file-input-group{flex:1;display:flex;flex-direction:column;gap:.5rem}.file-label{display:inline-flex;align-items:center;gap:.5rem;padding:.75rem 1.25rem;border:2px solid #2563eb;border-radius:.5rem;cursor:pointer;font-weight:500;transition:all .2s;justify-content:center}.file-label.left{background:#fff;color:#2563eb}.file-label.left:hover{background:#eff6ff}.file-label.right{background:#fff;color:#7c3aed;border-color:#7c3aed}.file-label.right:hover{background:#f5f3ff}.file-name{font-size:.875rem;color:#6b7280;text-align:center}.vs-separator{padding:.75rem 1.5rem;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;font-weight:700;border-radius:2rem;font-size:1.25rem;box-shadow:0 4px 6px #0000001a}.entity-selection{display:flex;gap:1rem;padding:1rem;background:#fff;border-bottom:1px solid #e5e7eb}.entity-list{flex:1}.entity-list h3{margin:0 0 .75rem;font-size:.875rem;font-weight:600;color:#6b7280;text-transform:uppercase}.entities{display:flex;flex-direction:column;gap:.5rem}.entity-card{display:flex;align-items:center;gap:.75rem;padding:.75rem;border:1px solid #e5e7eb;border-radius:.5rem;cursor:pointer;transition:all .2s}.entity-card:hover{background:#f9fafb;border-color:#d1d5db}.entity-card.selected{background:#eff6ff;border-color:#2563eb}.entity-icon{color:#6b7280}.entity-card.selected .entity-icon{color:#2563eb}.entity-info{flex:1}.entity-name{font-size:.875rem;font-weight:500;color:#1f2937}.entity-id{font-size:.75rem;color:#6b7280}.summary-section{padding:1.5rem 1.5rem 0;background:#f9fafb}.comparison-results{flex:1;overflow:auto;padding:1.5rem}.results-header{margin-bottom:1.5rem;position:sticky;top:0;background:#f9fafb;z-index:100;padding-top:1rem;padding-bottom:1rem;margin-top:-1rem}.header-title{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.results-header h2{margin:0;font-size:1.25rem;font-weight:700;color:#1f2937}.header-actions{display:flex;align-items:center;gap:1rem}.diff-navigation{display:flex;align-items:center;gap:.5rem;background:#fff;border:1px solid #e5e7eb;border-radius:.5rem;padding:.25rem}.nav-btn{background:#fff;border:1px solid #e5e7eb;border-radius:.375rem;padding:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;color:#6b7280}.nav-btn:hover{background:#f9fafb;border-color:#2563eb;color:#2563eb}.diff-counter{font-size:.875rem;font-weight:600;color:#1f2937;padding:0 .5rem;white-space:nowrap;min-width:60px;text-align:center}.fullscreen-btn{background:#fff;border:1px solid #e5e7eb;border-radius:.5rem;padding:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;color:#6b7280}.fullscreen-btn:hover{background:#f9fafb;border-color:#2563eb;color:#2563eb}.summary{display:flex;gap:1rem}.summary-item{flex:1;display:flex;flex-direction:column;align-items:center;padding:1rem;border-radius:.5rem;background:#fff;border:2px solid}.summary-item.added{border-color:#10b981;background:#ecfdf5}.summary-item.removed{border-color:#ef4444;background:#fef2f2}.summary-item.modified{border-color:#f59e0b;background:#fffbeb}.summary-item.unchanged{border-color:#6b7280;background:#f9fafb}.summary-item .count{font-size:1.5rem;font-weight:700}.summary-item.added .count{color:#10b981}.summary-item.removed .count{color:#ef4444}.summary-item.modified .count{color:#f59e0b}.summary-item.unchanged .count{color:#6b7280}.summary-item .label{font-size:.75rem;font-weight:600;text-transform:uppercase;margin-top:.25rem}.structure-comparison{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;height:100%}.comparison-section{background:#fff;border-radius:.5rem;padding:1rem;border:1px solid #e5e7eb;min-width:0;width:100%;overflow:auto}.comparison-section h3{display:flex;align-items:center;gap:.5rem;margin:0 0 1rem;font-size:1rem;font-weight:600;color:#1f2937;padding-bottom:.75rem;border-bottom:1px solid #e5e7eb}.structure-tree{display:flex;flex-direction:column;gap:.5rem}.tree-node{border-left:3px solid transparent;padding-left:.5rem;transition:all .2s;max-width:100%;overflow:hidden}.tree-node.added{background:#ecfdf5;border-left-color:#10b981}.tree-node.removed{background:#fef2f2;border-left-color:#ef4444}.tree-node.modified{background:#fffbeb;border-left-color:#f59e0b}.node-header{display:flex;align-items:center;gap:.5rem;padding:.5rem;border-radius:.375rem}.expand-btn{background:none;border:none;cursor:pointer;padding:.25rem;display:flex;align-items:center;justify-content:center;border-radius:.25rem;transition:background .2s}.expand-btn:hover{background:#f3f4f6}.spacer{width:24px}.node-icon{color:#6b7280}.node-content{display:flex;align-items:center;gap:.5rem;flex:1}.node-name{font-size:.875rem;font-weight:500;color:#1f2937}.node-type{font-size:.75rem;color:#6b7280;background:#f3f4f6;padding:.125rem .5rem;border-radius:.25rem}.change-badge{font-size:.625rem;font-weight:600;text-transform:uppercase;padding:.125rem .5rem;border-radius:.25rem;margin-left:auto}.tree-node.added .change-badge{background:#10b981;color:#fff}.tree-node.removed .change-badge{background:#ef4444;color:#fff}.tree-node.modified .change-badge{background:#f59e0b;color:#fff}.node-properties{margin-left:2.5rem;margin-top:.5rem;padding:.5rem;background:#f9fafb;border-radius:.375rem;font-size:.75rem;max-width:100%;overflow:hidden}.property{display:flex;flex-wrap:wrap;gap:.5rem;padding:.25rem 0;max-width:100%;overflow:hidden}.property.modified{background:#fef3c7;padding:.25rem .5rem;border-radius:.25rem;margin:.125rem 0}.property-key{font-weight:600;color:#374151;flex-shrink:0}.property-value{color:#6b7280;word-break:break-word;overflow:auto;overflow-wrap:break-word;flex:1;min-width:0}.node-children{margin-left:1.5rem;margin-top:.5rem;display:flex;flex-direction:column;gap:.5rem}.comparison-container.fullscreen{position:fixed;inset:0;z-index:9999;background:#f9fafb}.comparison-container.fullscreen .header,.comparison-container.fullscreen .file-selection,.comparison-container.fullscreen .entity-selection,.comparison-container.fullscreen .summary-section{display:none}.comparison-container.fullscreen .comparison-results{height:100vh;padding:2rem}\n"] }]
|
|
469
|
+
}], ctorParameters: () => [{ type: EntityComparisonService }] });
|
|
470
|
+
|
|
137
471
|
/*
|
|
138
472
|
* Public API Surface of concepto-message
|
|
139
473
|
*/
|
|
@@ -142,5 +476,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
142
476
|
* Generated bundle index. Do not edit.
|
|
143
477
|
*/
|
|
144
478
|
|
|
145
|
-
export { ConceptoContextMenuComponent, ConceptoMessageComponent, ConceptoUserControls, ConceptoUserControlsService };
|
|
479
|
+
export { ConceptoContextMenuComponent, ConceptoMessageComponent, ConceptoTreeComponent, ConceptoUserControls, ConceptoUserControlsService, EntityComparisonComponent, EntityComparisonService };
|
|
146
480
|
//# sourceMappingURL=concepto-user-controls.mjs.map
|