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.
@@ -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
@@ -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
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL2NvbmNlcHRvLXVzZXItY29udHJvbHMvc3JjL3B1YmxpYy1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsd0NBQXdDLENBQUM7QUFDdkQsY0FBYyxtREFBbUQsQ0FBQztBQUNsRSxjQUFjLDZEQUE2RCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLypcclxuICogUHVibGljIEFQSSBTdXJmYWNlIG9mIGNvbmNlcHRvLW1lc3NhZ2VcclxuICovXHJcblxyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb25jZXB0by11c2VyLWNvbnRyb2xzLnNlcnZpY2UnO1xyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb25jZXB0by11c2VyLWNvbnRyb2xzLmNvbXBvbmVudCc7XHJcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbmNlcHRvLW1lc3NhZ2UvY29uY2VwdG8tbWVzc2FnZS5jb21wb25lbnQnO1xyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb25jZXB0by1jb250ZXh0LW1lbnUvY29uY2VwdG8tY29udGV4dC1tZW51LmNvbXBvbmVudCc7Il19
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" }, outputs: { itemSelected: "itemSelected" }, 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"] }] });
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