@expeed/ngx-data-mapper 1.1.0 → 1.2.1
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.
|
@@ -10,26 +10,28 @@ import * as i7 from '@angular/material/tooltip';
|
|
|
10
10
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
11
11
|
import * as i2$1 from '@angular/forms';
|
|
12
12
|
import { FormsModule } from '@angular/forms';
|
|
13
|
-
import * as
|
|
13
|
+
import * as i4 from '@angular/material/select';
|
|
14
14
|
import { MatSelectModule } from '@angular/material/select';
|
|
15
|
-
import * as i5
|
|
15
|
+
import * as i5 from '@angular/material/input';
|
|
16
16
|
import { MatInputModule } from '@angular/material/input';
|
|
17
17
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
18
18
|
import * as i8 from '@angular/material/checkbox';
|
|
19
19
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
20
|
-
import * as
|
|
20
|
+
import * as i10 from '@angular/cdk/drag-drop';
|
|
21
21
|
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
22
22
|
import * as i6 from '@angular/material/radio';
|
|
23
23
|
import { MatRadioModule } from '@angular/material/radio';
|
|
24
24
|
import * as i7$1 from '@angular/material/slide-toggle';
|
|
25
25
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
26
|
-
import * as i9
|
|
26
|
+
import * as i9 from '@angular/material/divider';
|
|
27
27
|
import { MatDividerModule } from '@angular/material/divider';
|
|
28
28
|
import * as i6$1 from '@angular/material/datepicker';
|
|
29
29
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
30
30
|
import { MatNativeDateModule } from '@angular/material/core';
|
|
31
|
-
import * as
|
|
31
|
+
import * as i8$1 from '@angular/material/menu';
|
|
32
32
|
import { MatMenuModule } from '@angular/material/menu';
|
|
33
|
+
import * as i9$1 from '@angular/material/button-toggle';
|
|
34
|
+
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Standard JSON Schema (draft-07) TypeScript interfaces
|
|
@@ -125,16 +127,21 @@ class MappingService {
|
|
|
125
127
|
arrayToObjectMappings = signal([], ...(ngDevMode ? [{ debugName: "arrayToObjectMappings" }] : []));
|
|
126
128
|
defaultValues = signal([], ...(ngDevMode ? [{ debugName: "defaultValues" }] : []));
|
|
127
129
|
selectedMappingId = signal(null, ...(ngDevMode ? [{ debugName: "selectedMappingId" }] : []));
|
|
130
|
+
_sourceSchemaRef = signal(null, ...(ngDevMode ? [{ debugName: "_sourceSchemaRef" }] : []));
|
|
131
|
+
_targetSchemaRef = signal(null, ...(ngDevMode ? [{ debugName: "_targetSchemaRef" }] : []));
|
|
128
132
|
dragState = signal({
|
|
129
133
|
isDragging: false,
|
|
130
134
|
sourceField: null,
|
|
131
135
|
startPoint: null,
|
|
132
136
|
currentPoint: null,
|
|
137
|
+
dragMode: 'new',
|
|
133
138
|
}, ...(ngDevMode ? [{ debugName: "dragState" }] : []));
|
|
134
139
|
allMappings = computed(() => this.mappings(), ...(ngDevMode ? [{ debugName: "allMappings" }] : []));
|
|
135
140
|
allArrayMappings = computed(() => this.arrayMappings(), ...(ngDevMode ? [{ debugName: "allArrayMappings" }] : []));
|
|
136
141
|
allArrayToObjectMappings = computed(() => this.arrayToObjectMappings(), ...(ngDevMode ? [{ debugName: "allArrayToObjectMappings" }] : []));
|
|
137
142
|
allDefaultValues = computed(() => this.defaultValues(), ...(ngDevMode ? [{ debugName: "allDefaultValues" }] : []));
|
|
143
|
+
sourceSchemaRef = computed(() => this._sourceSchemaRef(), ...(ngDevMode ? [{ debugName: "sourceSchemaRef" }] : []));
|
|
144
|
+
targetSchemaRef = computed(() => this._targetSchemaRef(), ...(ngDevMode ? [{ debugName: "targetSchemaRef" }] : []));
|
|
138
145
|
selectedMapping = computed(() => {
|
|
139
146
|
const id = this.selectedMappingId();
|
|
140
147
|
return this.mappings().find((m) => m.id === id) || null;
|
|
@@ -149,6 +156,26 @@ class MappingService {
|
|
|
149
156
|
sourceField: field,
|
|
150
157
|
startPoint,
|
|
151
158
|
currentPoint: startPoint,
|
|
159
|
+
dragMode: 'new',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
startEndpointDrag(mappingId, endpointType, startPoint, sourceFieldIndex) {
|
|
163
|
+
const mapping = this.mappings().find(m => m.id === mappingId);
|
|
164
|
+
if (!mapping)
|
|
165
|
+
return;
|
|
166
|
+
// For source endpoint, we show the line from target back to cursor
|
|
167
|
+
// For target endpoint, we show the line from source(s) to cursor
|
|
168
|
+
const dragMode = endpointType === 'source' ? 'move-source' : 'move-target';
|
|
169
|
+
this.dragState.set({
|
|
170
|
+
isDragging: true,
|
|
171
|
+
sourceField: endpointType === 'source'
|
|
172
|
+
? mapping.sourceFields[sourceFieldIndex ?? 0]
|
|
173
|
+
: mapping.targetField,
|
|
174
|
+
startPoint,
|
|
175
|
+
currentPoint: startPoint,
|
|
176
|
+
dragMode,
|
|
177
|
+
mappingId,
|
|
178
|
+
sourceFieldIndex,
|
|
152
179
|
});
|
|
153
180
|
}
|
|
154
181
|
updateDragPosition(currentPoint) {
|
|
@@ -163,8 +190,62 @@ class MappingService {
|
|
|
163
190
|
sourceField: null,
|
|
164
191
|
startPoint: null,
|
|
165
192
|
currentPoint: null,
|
|
193
|
+
dragMode: 'new',
|
|
166
194
|
});
|
|
167
195
|
}
|
|
196
|
+
changeSourceField(mappingId, newSourceField, sourceFieldIndex) {
|
|
197
|
+
const mapping = this.mappings().find(m => m.id === mappingId);
|
|
198
|
+
if (!mapping)
|
|
199
|
+
return;
|
|
200
|
+
// Don't allow if new source is the same as target
|
|
201
|
+
if (newSourceField.id === mapping.targetField.id)
|
|
202
|
+
return;
|
|
203
|
+
// Don't allow if source already exists in the mapping
|
|
204
|
+
if (mapping.sourceFields.some(sf => sf.id === newSourceField.id))
|
|
205
|
+
return;
|
|
206
|
+
if (sourceFieldIndex !== undefined && mapping.sourceFields.length > 1) {
|
|
207
|
+
// Replace specific source field in multi-source mapping
|
|
208
|
+
const newSourceFields = [...mapping.sourceFields];
|
|
209
|
+
newSourceFields[sourceFieldIndex] = newSourceField;
|
|
210
|
+
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, sourceFields: newSourceFields } : m));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Single source mapping - replace the source field
|
|
214
|
+
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, sourceFields: [newSourceField] } : m));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
changeTargetField(mappingId, newTargetField) {
|
|
218
|
+
const mapping = this.mappings().find(m => m.id === mappingId);
|
|
219
|
+
if (!mapping)
|
|
220
|
+
return;
|
|
221
|
+
// Don't allow if new target is same as any source
|
|
222
|
+
if (mapping.sourceFields.some(sf => sf.id === newTargetField.id))
|
|
223
|
+
return;
|
|
224
|
+
// Check if another mapping already targets this field
|
|
225
|
+
const existingMapping = this.mappings().find(m => m.targetField.id === newTargetField.id && m.id !== mappingId);
|
|
226
|
+
if (existingMapping) {
|
|
227
|
+
// Merge into existing mapping (add sources)
|
|
228
|
+
const mergedSources = [
|
|
229
|
+
...existingMapping.sourceFields,
|
|
230
|
+
...mapping.sourceFields.filter(sf => !existingMapping.sourceFields.some(esf => esf.id === sf.id)),
|
|
231
|
+
];
|
|
232
|
+
this.mappings.update(mappings => mappings
|
|
233
|
+
.filter(m => m.id !== mappingId) // Remove old mapping
|
|
234
|
+
.map(m => m.id === existingMapping.id
|
|
235
|
+
? {
|
|
236
|
+
...m,
|
|
237
|
+
sourceFields: mergedSources,
|
|
238
|
+
transformations: mergedSources.length > 1
|
|
239
|
+
? [{ type: 'concat', separator: ' ' }]
|
|
240
|
+
: m.transformations,
|
|
241
|
+
}
|
|
242
|
+
: m));
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Simply update the target field
|
|
246
|
+
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, targetField: newTargetField } : m));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
168
249
|
createMapping(sourceFields, targetField, transformation) {
|
|
169
250
|
// Check if this is an array-to-array mapping
|
|
170
251
|
const sourceField = sourceFields[0];
|
|
@@ -375,13 +456,20 @@ class MappingService {
|
|
|
375
456
|
this.arrayToObjectMappings.set([]);
|
|
376
457
|
this.defaultValues.set([]);
|
|
377
458
|
this.selectedMappingId.set(null);
|
|
459
|
+
this._sourceSchemaRef.set(null);
|
|
460
|
+
this._targetSchemaRef.set(null);
|
|
461
|
+
}
|
|
462
|
+
setSourceSchemaRef(ref) {
|
|
463
|
+
this._sourceSchemaRef.set(ref);
|
|
464
|
+
}
|
|
465
|
+
setTargetSchemaRef(ref) {
|
|
466
|
+
this._targetSchemaRef.set(ref);
|
|
378
467
|
}
|
|
379
468
|
// Default value methods
|
|
380
469
|
setDefaultValue(targetField, value) {
|
|
381
|
-
const valueType = this.getValueType(targetField.type);
|
|
382
470
|
const existingDefault = this.defaultValues().find(d => d.targetField.id === targetField.id);
|
|
383
471
|
if (existingDefault) {
|
|
384
|
-
const updated = { ...existingDefault, value
|
|
472
|
+
const updated = { ...existingDefault, value };
|
|
385
473
|
this.defaultValues.update(dv => dv.map(d => d.id === existingDefault.id ? updated : d));
|
|
386
474
|
return updated;
|
|
387
475
|
}
|
|
@@ -389,7 +477,6 @@ class MappingService {
|
|
|
389
477
|
id: this.generateId(),
|
|
390
478
|
targetField,
|
|
391
479
|
value,
|
|
392
|
-
valueType,
|
|
393
480
|
};
|
|
394
481
|
this.defaultValues.update(dv => [...dv, newDefault]);
|
|
395
482
|
return newDefault;
|
|
@@ -403,14 +490,6 @@ class MappingService {
|
|
|
403
490
|
hasDefaultValue(targetFieldId) {
|
|
404
491
|
return this.defaultValues().some(d => d.targetField.id === targetFieldId);
|
|
405
492
|
}
|
|
406
|
-
getValueType(fieldType) {
|
|
407
|
-
switch (fieldType) {
|
|
408
|
-
case 'number': return 'number';
|
|
409
|
-
case 'boolean': return 'boolean';
|
|
410
|
-
case 'date': return 'date';
|
|
411
|
-
default: return 'string';
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
493
|
exportMappings(name, description) {
|
|
415
494
|
const exportData = {
|
|
416
495
|
version: '1.0',
|
|
@@ -421,13 +500,20 @@ class MappingService {
|
|
|
421
500
|
arrayToObjectMappings: this.arrayToObjectMappings(),
|
|
422
501
|
defaultValues: this.defaultValues(),
|
|
423
502
|
};
|
|
503
|
+
// Include schema refs if set
|
|
504
|
+
if (this._sourceSchemaRef()) {
|
|
505
|
+
exportData['sourceSchemaRef'] = this._sourceSchemaRef();
|
|
506
|
+
}
|
|
507
|
+
if (this._targetSchemaRef()) {
|
|
508
|
+
exportData['targetSchemaRef'] = this._targetSchemaRef();
|
|
509
|
+
}
|
|
424
510
|
return JSON.stringify(exportData, null, 2);
|
|
425
511
|
}
|
|
426
512
|
/**
|
|
427
513
|
* Export mappings as a MappingDocument object (not stringified)
|
|
428
514
|
*/
|
|
429
515
|
exportMappingsAsObject(name, description) {
|
|
430
|
-
|
|
516
|
+
const exportData = {
|
|
431
517
|
version: '1.0',
|
|
432
518
|
name: name || 'Mapping Configuration',
|
|
433
519
|
description: description || '',
|
|
@@ -436,6 +522,14 @@ class MappingService {
|
|
|
436
522
|
arrayToObjectMappings: this.arrayToObjectMappings(),
|
|
437
523
|
defaultValues: this.defaultValues(),
|
|
438
524
|
};
|
|
525
|
+
// Include schema refs if set
|
|
526
|
+
if (this._sourceSchemaRef()) {
|
|
527
|
+
exportData['sourceSchemaRef'] = this._sourceSchemaRef();
|
|
528
|
+
}
|
|
529
|
+
if (this._targetSchemaRef()) {
|
|
530
|
+
exportData['targetSchemaRef'] = this._targetSchemaRef();
|
|
531
|
+
}
|
|
532
|
+
return exportData;
|
|
439
533
|
}
|
|
440
534
|
importMappings(json) {
|
|
441
535
|
try {
|
|
@@ -445,6 +539,8 @@ class MappingService {
|
|
|
445
539
|
this.arrayMappings.set(data.arrayMappings || []);
|
|
446
540
|
this.arrayToObjectMappings.set(data.arrayToObjectMappings || []);
|
|
447
541
|
this.defaultValues.set(data.defaultValues || []);
|
|
542
|
+
this._sourceSchemaRef.set(data.sourceSchemaRef || null);
|
|
543
|
+
this._targetSchemaRef.set(data.targetSchemaRef || null);
|
|
448
544
|
}
|
|
449
545
|
else {
|
|
450
546
|
// Legacy format
|
|
@@ -842,15 +938,17 @@ class SvgConnectorService {
|
|
|
842
938
|
}
|
|
843
939
|
calculateConnectionPoint(rect, side, containerRect) {
|
|
844
940
|
const relativeY = rect.top - containerRect.top + rect.height / 2;
|
|
941
|
+
// Offset to position endpoint circles outside the schema panels
|
|
942
|
+
const endpointOffset = 8;
|
|
845
943
|
if (side === 'source') {
|
|
846
944
|
return {
|
|
847
|
-
x: rect.right - containerRect.left,
|
|
945
|
+
x: rect.right - containerRect.left + endpointOffset,
|
|
848
946
|
y: relativeY,
|
|
849
947
|
};
|
|
850
948
|
}
|
|
851
949
|
else {
|
|
852
950
|
return {
|
|
853
|
-
x: rect.left - containerRect.left,
|
|
951
|
+
x: rect.left - containerRect.left - endpointOffset,
|
|
854
952
|
y: relativeY,
|
|
855
953
|
};
|
|
856
954
|
}
|
|
@@ -1116,6 +1214,7 @@ class SchemaTreeComponent {
|
|
|
1116
1214
|
fieldDragStart = new EventEmitter();
|
|
1117
1215
|
fieldDragEnd = new EventEmitter();
|
|
1118
1216
|
fieldDrop = new EventEmitter();
|
|
1217
|
+
sourceDrop = new EventEmitter(); // For endpoint dragging - drop on source field
|
|
1119
1218
|
fieldPositionsChanged = new EventEmitter();
|
|
1120
1219
|
fieldDefaultValueClick = new EventEmitter();
|
|
1121
1220
|
schemaFieldsContainer;
|
|
@@ -1167,21 +1266,45 @@ class SchemaTreeComponent {
|
|
|
1167
1266
|
onDragStart(event, field) {
|
|
1168
1267
|
if (this.side !== 'source')
|
|
1169
1268
|
return;
|
|
1269
|
+
// Don't start new drag if endpoint dragging is in progress
|
|
1270
|
+
const dragState = this.mappingService.currentDragState();
|
|
1271
|
+
if (dragState.isDragging)
|
|
1272
|
+
return;
|
|
1170
1273
|
const element = event.currentTarget;
|
|
1171
1274
|
const rect = element.getBoundingClientRect();
|
|
1172
1275
|
this.fieldDragStart.emit({ field, element, rect });
|
|
1173
1276
|
}
|
|
1277
|
+
isEndpointDragMode() {
|
|
1278
|
+
const dragState = this.mappingService.currentDragState();
|
|
1279
|
+
return dragState.isDragging && (dragState.dragMode === 'move-source' || dragState.dragMode === 'move-target');
|
|
1280
|
+
}
|
|
1281
|
+
isSourceEndpointDragging() {
|
|
1282
|
+
const dragState = this.mappingService.currentDragState();
|
|
1283
|
+
return dragState.isDragging && dragState.dragMode === 'move-source';
|
|
1284
|
+
}
|
|
1285
|
+
isTargetEndpointDragging() {
|
|
1286
|
+
const dragState = this.mappingService.currentDragState();
|
|
1287
|
+
return dragState.isDragging && dragState.dragMode === 'move-target';
|
|
1288
|
+
}
|
|
1174
1289
|
onDragOver(event) {
|
|
1175
1290
|
if (this.side === 'target') {
|
|
1176
1291
|
event.preventDefault();
|
|
1177
1292
|
}
|
|
1178
1293
|
}
|
|
1179
1294
|
onDrop(event, field) {
|
|
1180
|
-
if (this.side !== 'target')
|
|
1181
|
-
return;
|
|
1182
1295
|
const element = event.currentTarget;
|
|
1183
1296
|
const rect = element.getBoundingClientRect();
|
|
1184
|
-
|
|
1297
|
+
// Check if endpoint dragging is in progress (via MappingService drag state)
|
|
1298
|
+
const dragState = this.mappingService.currentDragState();
|
|
1299
|
+
// Handle source drop during endpoint dragging (moving source endpoint)
|
|
1300
|
+
if (this.side === 'source' && dragState.isDragging && dragState.dragMode === 'move-source') {
|
|
1301
|
+
this.sourceDrop.emit({ field, element, rect });
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
// Handle target drop (either endpoint dragging or new mapping)
|
|
1305
|
+
if (this.side === 'target') {
|
|
1306
|
+
this.fieldDrop.emit({ field, element, rect });
|
|
1307
|
+
}
|
|
1185
1308
|
}
|
|
1186
1309
|
getTypeIcon(type) {
|
|
1187
1310
|
const icons = {
|
|
@@ -1218,7 +1341,7 @@ class SchemaTreeComponent {
|
|
|
1218
1341
|
const defaultValue = this.defaultValues.find(d => d.targetField.id === field.id);
|
|
1219
1342
|
if (!defaultValue || defaultValue.value === null)
|
|
1220
1343
|
return '';
|
|
1221
|
-
if (defaultValue.
|
|
1344
|
+
if (defaultValue.targetField.type === 'date' && defaultValue.value) {
|
|
1222
1345
|
return new Date(defaultValue.value).toLocaleDateString();
|
|
1223
1346
|
}
|
|
1224
1347
|
return String(defaultValue.value);
|
|
@@ -1244,11 +1367,11 @@ class SchemaTreeComponent {
|
|
|
1244
1367
|
return field.id;
|
|
1245
1368
|
}
|
|
1246
1369
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1247
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaTreeComponent, isStandalone: true, selector: "schema-tree", inputs: { schema: "schema", side: "side", mappings: "mappings", defaultValues: "defaultValues" }, outputs: { fieldDragStart: "fieldDragStart", fieldDragEnd: "fieldDragEnd", fieldDrop: "fieldDrop", fieldPositionsChanged: "fieldPositionsChanged", fieldDefaultValueClick: "fieldDefaultValueClick" }, viewQueries: [{ propertyName: "schemaFieldsContainer", first: true, predicate: ["schemaFields"], descendants: true }, { propertyName: "fieldItems", predicate: ["fieldItem"], descendants: true }], ngImport: i0, template: "<div class=\"schema-tree\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\">\n <div class=\"schema-header\">\n <span class=\"schema-title\">{{ schema.name }}</span>\n <span class=\"schema-badge\">{{ side === 'source' ? 'Source' : 'Target' }}</span>\n </div>\n\n <div class=\"schema-fields\" #schemaFields>\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: schema.fields, level: 0 }\"></ng-container>\n </div>\n</div>\n\n<ng-template #fieldList let-fields=\"fields\" let-level=\"level\">\n @for (field of fields; track trackByFieldId($index, field)) {\n <div\n #fieldItem\n class=\"field-item\"\n [class.mapped]=\"isFieldMapped(field)\"\n [class.has-default]=\"hasDefaultValue(field)\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.expanded]=\"field.expanded\"\n [class.is-array]=\"field.type === 'array'\"\n [class.draggable]=\"side === 'source' && ((!field.children || field.children.length === 0) || field.type === 'array')\"\n [class.droppable]=\"side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')\"\n [class.clickable]=\"side === 'target' && (!isFieldMapped(field) || hasDefaultValue(field)) && field.type !== 'object' && field.type !== 'array'\"\n [style.padding-left.px]=\"16 + level * 20\"\n [attr.data-field-id]=\"field.id\"\n (mousedown)=\"onDragStart($event, field)\"\n (mouseup)=\"onDrop($event, field)\"\n (click)=\"onFieldClick($event, field)\"\n >\n <!-- Expand/Collapse button for nested objects -->\n @if (field.children && field.children.length > 0) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field, $event)\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Field type icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field name with array indicator -->\n <span class=\"field-name\">{{ field.name }}@if (field.type === 'array') {<span class=\"array-indicator\">[]</span>}</span>\n\n <!-- Mapping indicator -->\n @if (isFieldMapped(field)) {\n <span class=\"mapping-indicator\" [matTooltip]=\"getFieldMappingCount(field) + ' mapping(s)'\">\n <mat-icon>{{ field.type === 'array' ? 'loop' : 'link' }}</mat-icon>\n @if (getFieldMappingCount(field) > 1) {\n <span class=\"mapping-count\">{{ getFieldMappingCount(field) }}</span>\n }\n </span>\n }\n\n <!-- Default value indicator -->\n @if (hasDefaultValue(field)) {\n <span class=\"default-indicator\" [matTooltip]=\"'Default: ' + getDefaultValueDisplay(field)\">\n <mat-icon>edit</mat-icon>\n <span class=\"default-value\">{{ getDefaultValueDisplay(field) }}</span>\n </span>\n }\n\n <!-- Connection point - show for leaf nodes, arrays, and objects (on target for array-to-object) -->\n @if ((!field.children || field.children.length === 0) || field.type === 'array' || (side === 'target' && field.type === 'object')) {\n <div class=\"connection-point\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\" [class.array-point]=\"field.type === 'array'\" [class.object-point]=\"field.type === 'object'\">\n <span class=\"point-dot\"></span>\n </div>\n }\n </div>\n\n <!-- Nested children -->\n @if (field.children && field.children.length > 0 && field.expanded) {\n <div class=\"nested-fields\">\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: field.children, level: level + 1 }\"></ng-container>\n </div>\n }\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.schema-tree{background:var(--surface-card, #ffffff);border-radius:12px;box-shadow:0 2px 8px #00000014;overflow:hidden;height:100%;min-height:0;display:flex;flex-direction:column;flex:1}.schema-tree.source .schema-header{background:linear-gradient(135deg,#6366f1,#8b5cf6)}.schema-tree.source .connection-point{right:8px}.schema-tree.target .schema-header{background:linear-gradient(135deg,#10b981,#059669)}.schema-tree.target .connection-point{left:8px}.schema-header{padding:16px 20px;color:#fff;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-shrink:0}.schema-title{font-size:16px;font-weight:600;letter-spacing:.3px}.schema-badge{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.5px;background:#fff3;padding:4px 10px;border-radius:20px}.schema-fields{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.field-item{display:flex;align-items:center;padding:10px 16px;gap:8px;cursor:default;transition:background-color .15s ease;position:relative;-webkit-user-select:none;user-select:none}.field-item:hover{background-color:var(--surface-hover, #f8fafc)}.field-item.mapped{background-color:var(--surface-mapped, #f0fdf4)}.field-item.mapped:hover{background-color:#dcfce7}.field-item.draggable{cursor:grab}.field-item.draggable:active{cursor:grabbing}.field-item.draggable:hover .connection-point .point-dot{transform:scale(1.3);background:#6366f1}.field-item.droppable{cursor:pointer}.field-item.droppable:hover .connection-point .point-dot{transform:scale(1.3);background:#10b981}.expand-btn{background:none;border:none;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#94a3b8;border-radius:4px;transition:all .15s ease}.expand-btn:hover{background-color:#e2e8f0;color:#475569}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:24px;height:24px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:1;font-size:14px;color:#1e293b;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-item.is-array{background-color:#fffbeb}.field-item.is-array:hover,.field-item.is-array.mapped{background-color:#fef3c7}.field-item.is-array .type-icon{color:#f59e0b}.connection-point.array-point .point-dot{background:#f59e0b;box-shadow:0 0 0 3px #f59e0b4d}.connection-point.object-point .point-dot{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf64d}.mapping-indicator{display:flex;align-items:center;gap:2px;color:#10b981}.mapping-indicator mat-icon{font-size:16px;width:16px;height:16px}.mapping-indicator .mapping-count{font-size:11px;font-weight:600;background:#10b981;color:#fff;padding:1px 5px;border-radius:10px;min-width:16px;text-align:center}.connection-point{position:absolute;top:50%;transform:translateY(-50%);width:20px;height:20px;display:flex;align-items:center;justify-content:center}.connection-point.source{right:8px}.connection-point.target{left:8px}.point-dot{width:10px;height:10px;border-radius:50%;background:#cbd5e1;transition:all .2s ease;box-shadow:0 0 0 3px #cbd5e14d}.mapped .point-dot{background:#10b981;box-shadow:0 0 0 3px #10b9814d}.nested-fields{border-left:2px solid #e2e8f0;margin-left:28px}.field-item.has-default{background-color:#eff6ff}.field-item.has-default:hover{background-color:#dbeafe}.field-item.clickable{cursor:pointer}.field-item.clickable:hover:not(.has-default){background-color:#f1f5f9}.field-item.clickable:hover:not(.has-default):after{content:\"Click to set default\";position:absolute;right:32px;font-size:11px;color:#64748b;font-style:italic}.field-item.clickable.has-default:hover:after{content:\"Click to edit\";position:absolute;right:32px;font-size:11px;color:#3b82f6;font-style:italic}.default-indicator{display:flex;align-items:center;gap:4px;color:#3b82f6;font-size:12px;background:#dbeafe;padding:2px 8px;border-radius:4px;max-width:100px;overflow:hidden}.default-indicator mat-icon{font-size:14px;width:14px;height:14px}.default-indicator .default-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
|
|
1370
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaTreeComponent, isStandalone: true, selector: "schema-tree", inputs: { schema: "schema", side: "side", mappings: "mappings", defaultValues: "defaultValues" }, outputs: { fieldDragStart: "fieldDragStart", fieldDragEnd: "fieldDragEnd", fieldDrop: "fieldDrop", sourceDrop: "sourceDrop", fieldPositionsChanged: "fieldPositionsChanged", fieldDefaultValueClick: "fieldDefaultValueClick" }, viewQueries: [{ propertyName: "schemaFieldsContainer", first: true, predicate: ["schemaFields"], descendants: true }, { propertyName: "fieldItems", predicate: ["fieldItem"], descendants: true }], ngImport: i0, template: "<div class=\"schema-tree\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\">\n <div class=\"schema-header\">\n <span class=\"schema-title\">{{ schema.name }}</span>\n <span class=\"schema-badge\">{{ side === 'source' ? 'Source' : 'Target' }}</span>\n </div>\n\n <div class=\"schema-fields\" #schemaFields>\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: schema.fields, level: 0 }\"></ng-container>\n </div>\n</div>\n\n<ng-template #fieldList let-fields=\"fields\" let-level=\"level\">\n @for (field of fields; track trackByFieldId($index, field)) {\n <div\n #fieldItem\n class=\"field-item\"\n [class.mapped]=\"isFieldMapped(field)\"\n [class.has-default]=\"hasDefaultValue(field)\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.expanded]=\"field.expanded\"\n [class.is-array]=\"field.type === 'array'\"\n [class.draggable]=\"side === 'source' && ((!field.children || field.children.length === 0) || field.type === 'array') && !isEndpointDragMode()\"\n [class.droppable]=\"(side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')) || (side === 'source' && isSourceEndpointDragging() && ((!field.children || field.children.length === 0) || field.type === 'array'))\"\n [class.endpoint-drop-target]=\"(side === 'source' && isSourceEndpointDragging()) || (side === 'target' && isTargetEndpointDragging())\"\n [class.clickable]=\"side === 'target' && (!isFieldMapped(field) || hasDefaultValue(field)) && field.type !== 'object' && field.type !== 'array'\"\n [style.padding-left.px]=\"16 + level * 20\"\n [attr.data-field-id]=\"field.id\"\n (mousedown)=\"onDragStart($event, field)\"\n (mouseup)=\"onDrop($event, field)\"\n (click)=\"onFieldClick($event, field)\"\n >\n <!-- Expand/Collapse button for nested objects -->\n @if (field.children && field.children.length > 0) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field, $event)\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Field type icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field name with array indicator -->\n <span class=\"field-name\">{{ field.name }}@if (field.type === 'array') {<span class=\"array-indicator\">[]</span>}</span>\n\n <!-- Mapping indicator -->\n @if (isFieldMapped(field)) {\n <span class=\"mapping-indicator\" [matTooltip]=\"getFieldMappingCount(field) + ' mapping(s)'\">\n <mat-icon>{{ field.type === 'array' ? 'loop' : 'link' }}</mat-icon>\n @if (getFieldMappingCount(field) > 1) {\n <span class=\"mapping-count\">{{ getFieldMappingCount(field) }}</span>\n }\n </span>\n }\n\n <!-- Default value indicator -->\n @if (hasDefaultValue(field)) {\n <span class=\"default-indicator\" [matTooltip]=\"'Default: ' + getDefaultValueDisplay(field)\">\n <mat-icon>edit</mat-icon>\n <span class=\"default-value\">{{ getDefaultValueDisplay(field) }}</span>\n </span>\n }\n\n <!-- Connection point - show for leaf nodes, arrays, and objects (on target for array-to-object) -->\n @if ((!field.children || field.children.length === 0) || field.type === 'array' || (side === 'target' && field.type === 'object')) {\n <div class=\"connection-point\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\" [class.array-point]=\"field.type === 'array'\" [class.object-point]=\"field.type === 'object'\">\n <span class=\"point-dot\"></span>\n </div>\n }\n </div>\n\n <!-- Nested children -->\n @if (field.children && field.children.length > 0 && field.expanded) {\n <div class=\"nested-fields\">\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: field.children, level: level + 1 }\"></ng-container>\n </div>\n }\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.schema-tree{background:var(--data-mapper-panel-bg, var(--surface-card, #ffffff));border-radius:var(--data-mapper-panel-border-radius, 12px);border:var(--data-mapper-panel-border, none);box-shadow:var(--data-mapper-panel-shadow, 0 2px 8px rgba(0, 0, 0, .08));overflow:hidden;height:100%;min-height:0;display:flex;flex-direction:column;flex:1}.schema-tree.source .schema-header{background:linear-gradient(135deg,#6366f1,#8b5cf6)}.schema-tree.source .connection-point{right:8px}.schema-tree.target .schema-header{background:linear-gradient(135deg,#10b981,#059669)}.schema-tree.target .connection-point{left:8px}.schema-header{padding:16px 20px;color:#fff;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-shrink:0}.schema-title{font-size:16px;font-weight:600;letter-spacing:.3px}.schema-badge{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.5px;background:#fff3;padding:4px 10px;border-radius:20px}.schema-fields{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.field-item{display:flex;align-items:center;padding:10px 16px;gap:8px;cursor:default;transition:background-color .15s ease;position:relative;-webkit-user-select:none;user-select:none}.field-item:hover{background-color:var(--surface-hover, #f8fafc)}.field-item.mapped{background-color:var(--surface-mapped, #f0fdf4)}.field-item.mapped:hover{background-color:#dcfce7}.field-item.draggable{cursor:grab}.field-item.draggable:active{cursor:grabbing}.field-item.draggable:hover .connection-point .point-dot{transform:scale(1.3);background:#6366f1}.field-item.droppable{cursor:pointer}.field-item.droppable:hover .connection-point .point-dot{transform:scale(1.3);background:#10b981}.field-item.endpoint-drop-target{background-color:#fef3c7}.field-item.endpoint-drop-target:hover{background-color:#fde68a}.field-item.endpoint-drop-target .connection-point .point-dot{animation:pulse-drop-target 1s ease-in-out infinite}@keyframes pulse-drop-target{0%,to{transform:scale(1);box-shadow:0 0 0 3px #f59e0b4d}50%{transform:scale(1.2);box-shadow:0 0 0 6px #f59e0b33}}.expand-btn{background:none;border:none;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#94a3b8;border-radius:4px;transition:all .15s ease}.expand-btn:hover{background-color:#e2e8f0;color:#475569}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:24px;height:24px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:1;font-size:14px;color:#1e293b;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-item.is-array{background-color:#fffbeb}.field-item.is-array:hover,.field-item.is-array.mapped{background-color:#fef3c7}.field-item.is-array .type-icon{color:#f59e0b}.connection-point.array-point .point-dot{background:#f59e0b;box-shadow:0 0 0 3px #f59e0b4d}.connection-point.object-point .point-dot{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf64d}.mapping-indicator{display:flex;align-items:center;gap:2px;color:#10b981}.mapping-indicator mat-icon{font-size:16px;width:16px;height:16px}.mapping-indicator .mapping-count{font-size:11px;font-weight:600;background:#10b981;color:#fff;padding:1px 5px;border-radius:10px;min-width:16px;text-align:center}.connection-point{position:absolute;top:50%;transform:translateY(-50%);width:20px;height:20px;display:flex;align-items:center;justify-content:center}.connection-point.source{right:8px}.connection-point.target{left:8px}.point-dot{width:10px;height:10px;border-radius:50%;background:#cbd5e1;transition:all .2s ease;box-shadow:0 0 0 3px #cbd5e14d}.mapped .point-dot{background:#10b981;box-shadow:0 0 0 3px #10b9814d}.nested-fields{border-left:2px solid #e2e8f0;margin-left:28px}.field-item.has-default{background-color:#eff6ff}.field-item.has-default:hover{background-color:#dbeafe}.field-item.clickable{cursor:pointer}.field-item.clickable:hover:not(.has-default){background-color:#f1f5f9}.field-item.clickable:hover:not(.has-default):after{content:\"Click to set default\";position:absolute;right:32px;font-size:11px;color:#64748b;font-style:italic}.field-item.clickable.has-default:hover:after{content:\"Click to edit\";position:absolute;right:32px;font-size:11px;color:#3b82f6;font-style:italic}.default-indicator{display:flex;align-items:center;gap:4px;color:#3b82f6;font-size:12px;background:#dbeafe;padding:2px 8px;border-radius:4px;max-width:100px;overflow:hidden}.default-indicator mat-icon{font-size:14px;width:14px;height:14px}.default-indicator .default-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
|
|
1248
1371
|
}
|
|
1249
1372
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, decorators: [{
|
|
1250
1373
|
type: Component,
|
|
1251
|
-
args: [{ selector: 'schema-tree', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], template: "<div class=\"schema-tree\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\">\n <div class=\"schema-header\">\n <span class=\"schema-title\">{{ schema.name }}</span>\n <span class=\"schema-badge\">{{ side === 'source' ? 'Source' : 'Target' }}</span>\n </div>\n\n <div class=\"schema-fields\" #schemaFields>\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: schema.fields, level: 0 }\"></ng-container>\n </div>\n</div>\n\n<ng-template #fieldList let-fields=\"fields\" let-level=\"level\">\n @for (field of fields; track trackByFieldId($index, field)) {\n <div\n #fieldItem\n class=\"field-item\"\n [class.mapped]=\"isFieldMapped(field)\"\n [class.has-default]=\"hasDefaultValue(field)\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.expanded]=\"field.expanded\"\n [class.is-array]=\"field.type === 'array'\"\n [class.draggable]=\"side === 'source' && ((!field.children || field.children.length === 0) || field.type === 'array')\"\n [class.droppable]=\"side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')\"\n [class.clickable]=\"side === 'target' && (!isFieldMapped(field) || hasDefaultValue(field)) && field.type !== 'object' && field.type !== 'array'\"\n [style.padding-left.px]=\"16 + level * 20\"\n [attr.data-field-id]=\"field.id\"\n (mousedown)=\"onDragStart($event, field)\"\n (mouseup)=\"onDrop($event, field)\"\n (click)=\"onFieldClick($event, field)\"\n >\n <!-- Expand/Collapse button for nested objects -->\n @if (field.children && field.children.length > 0) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field, $event)\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Field type icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field name with array indicator -->\n <span class=\"field-name\">{{ field.name }}@if (field.type === 'array') {<span class=\"array-indicator\">[]</span>}</span>\n\n <!-- Mapping indicator -->\n @if (isFieldMapped(field)) {\n <span class=\"mapping-indicator\" [matTooltip]=\"getFieldMappingCount(field) + ' mapping(s)'\">\n <mat-icon>{{ field.type === 'array' ? 'loop' : 'link' }}</mat-icon>\n @if (getFieldMappingCount(field) > 1) {\n <span class=\"mapping-count\">{{ getFieldMappingCount(field) }}</span>\n }\n </span>\n }\n\n <!-- Default value indicator -->\n @if (hasDefaultValue(field)) {\n <span class=\"default-indicator\" [matTooltip]=\"'Default: ' + getDefaultValueDisplay(field)\">\n <mat-icon>edit</mat-icon>\n <span class=\"default-value\">{{ getDefaultValueDisplay(field) }}</span>\n </span>\n }\n\n <!-- Connection point - show for leaf nodes, arrays, and objects (on target for array-to-object) -->\n @if ((!field.children || field.children.length === 0) || field.type === 'array' || (side === 'target' && field.type === 'object')) {\n <div class=\"connection-point\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\" [class.array-point]=\"field.type === 'array'\" [class.object-point]=\"field.type === 'object'\">\n <span class=\"point-dot\"></span>\n </div>\n }\n </div>\n\n <!-- Nested children -->\n @if (field.children && field.children.length > 0 && field.expanded) {\n <div class=\"nested-fields\">\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: field.children, level: level + 1 }\"></ng-container>\n </div>\n }\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.schema-tree{background:var(--surface-card, #ffffff);border-radius:12px;box-shadow:0 2px 8px
|
|
1374
|
+
args: [{ selector: 'schema-tree', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], template: "<div class=\"schema-tree\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\">\n <div class=\"schema-header\">\n <span class=\"schema-title\">{{ schema.name }}</span>\n <span class=\"schema-badge\">{{ side === 'source' ? 'Source' : 'Target' }}</span>\n </div>\n\n <div class=\"schema-fields\" #schemaFields>\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: schema.fields, level: 0 }\"></ng-container>\n </div>\n</div>\n\n<ng-template #fieldList let-fields=\"fields\" let-level=\"level\">\n @for (field of fields; track trackByFieldId($index, field)) {\n <div\n #fieldItem\n class=\"field-item\"\n [class.mapped]=\"isFieldMapped(field)\"\n [class.has-default]=\"hasDefaultValue(field)\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.expanded]=\"field.expanded\"\n [class.is-array]=\"field.type === 'array'\"\n [class.draggable]=\"side === 'source' && ((!field.children || field.children.length === 0) || field.type === 'array') && !isEndpointDragMode()\"\n [class.droppable]=\"(side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')) || (side === 'source' && isSourceEndpointDragging() && ((!field.children || field.children.length === 0) || field.type === 'array'))\"\n [class.endpoint-drop-target]=\"(side === 'source' && isSourceEndpointDragging()) || (side === 'target' && isTargetEndpointDragging())\"\n [class.clickable]=\"side === 'target' && (!isFieldMapped(field) || hasDefaultValue(field)) && field.type !== 'object' && field.type !== 'array'\"\n [style.padding-left.px]=\"16 + level * 20\"\n [attr.data-field-id]=\"field.id\"\n (mousedown)=\"onDragStart($event, field)\"\n (mouseup)=\"onDrop($event, field)\"\n (click)=\"onFieldClick($event, field)\"\n >\n <!-- Expand/Collapse button for nested objects -->\n @if (field.children && field.children.length > 0) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field, $event)\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Field type icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field name with array indicator -->\n <span class=\"field-name\">{{ field.name }}@if (field.type === 'array') {<span class=\"array-indicator\">[]</span>}</span>\n\n <!-- Mapping indicator -->\n @if (isFieldMapped(field)) {\n <span class=\"mapping-indicator\" [matTooltip]=\"getFieldMappingCount(field) + ' mapping(s)'\">\n <mat-icon>{{ field.type === 'array' ? 'loop' : 'link' }}</mat-icon>\n @if (getFieldMappingCount(field) > 1) {\n <span class=\"mapping-count\">{{ getFieldMappingCount(field) }}</span>\n }\n </span>\n }\n\n <!-- Default value indicator -->\n @if (hasDefaultValue(field)) {\n <span class=\"default-indicator\" [matTooltip]=\"'Default: ' + getDefaultValueDisplay(field)\">\n <mat-icon>edit</mat-icon>\n <span class=\"default-value\">{{ getDefaultValueDisplay(field) }}</span>\n </span>\n }\n\n <!-- Connection point - show for leaf nodes, arrays, and objects (on target for array-to-object) -->\n @if ((!field.children || field.children.length === 0) || field.type === 'array' || (side === 'target' && field.type === 'object')) {\n <div class=\"connection-point\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\" [class.array-point]=\"field.type === 'array'\" [class.object-point]=\"field.type === 'object'\">\n <span class=\"point-dot\"></span>\n </div>\n }\n </div>\n\n <!-- Nested children -->\n @if (field.children && field.children.length > 0 && field.expanded) {\n <div class=\"nested-fields\">\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: field.children, level: level + 1 }\"></ng-container>\n </div>\n }\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.schema-tree{background:var(--data-mapper-panel-bg, var(--surface-card, #ffffff));border-radius:var(--data-mapper-panel-border-radius, 12px);border:var(--data-mapper-panel-border, none);box-shadow:var(--data-mapper-panel-shadow, 0 2px 8px rgba(0, 0, 0, .08));overflow:hidden;height:100%;min-height:0;display:flex;flex-direction:column;flex:1}.schema-tree.source .schema-header{background:linear-gradient(135deg,#6366f1,#8b5cf6)}.schema-tree.source .connection-point{right:8px}.schema-tree.target .schema-header{background:linear-gradient(135deg,#10b981,#059669)}.schema-tree.target .connection-point{left:8px}.schema-header{padding:16px 20px;color:#fff;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-shrink:0}.schema-title{font-size:16px;font-weight:600;letter-spacing:.3px}.schema-badge{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.5px;background:#fff3;padding:4px 10px;border-radius:20px}.schema-fields{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.field-item{display:flex;align-items:center;padding:10px 16px;gap:8px;cursor:default;transition:background-color .15s ease;position:relative;-webkit-user-select:none;user-select:none}.field-item:hover{background-color:var(--surface-hover, #f8fafc)}.field-item.mapped{background-color:var(--surface-mapped, #f0fdf4)}.field-item.mapped:hover{background-color:#dcfce7}.field-item.draggable{cursor:grab}.field-item.draggable:active{cursor:grabbing}.field-item.draggable:hover .connection-point .point-dot{transform:scale(1.3);background:#6366f1}.field-item.droppable{cursor:pointer}.field-item.droppable:hover .connection-point .point-dot{transform:scale(1.3);background:#10b981}.field-item.endpoint-drop-target{background-color:#fef3c7}.field-item.endpoint-drop-target:hover{background-color:#fde68a}.field-item.endpoint-drop-target .connection-point .point-dot{animation:pulse-drop-target 1s ease-in-out infinite}@keyframes pulse-drop-target{0%,to{transform:scale(1);box-shadow:0 0 0 3px #f59e0b4d}50%{transform:scale(1.2);box-shadow:0 0 0 6px #f59e0b33}}.expand-btn{background:none;border:none;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#94a3b8;border-radius:4px;transition:all .15s ease}.expand-btn:hover{background-color:#e2e8f0;color:#475569}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:24px;height:24px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:1;font-size:14px;color:#1e293b;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-item.is-array{background-color:#fffbeb}.field-item.is-array:hover,.field-item.is-array.mapped{background-color:#fef3c7}.field-item.is-array .type-icon{color:#f59e0b}.connection-point.array-point .point-dot{background:#f59e0b;box-shadow:0 0 0 3px #f59e0b4d}.connection-point.object-point .point-dot{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf64d}.mapping-indicator{display:flex;align-items:center;gap:2px;color:#10b981}.mapping-indicator mat-icon{font-size:16px;width:16px;height:16px}.mapping-indicator .mapping-count{font-size:11px;font-weight:600;background:#10b981;color:#fff;padding:1px 5px;border-radius:10px;min-width:16px;text-align:center}.connection-point{position:absolute;top:50%;transform:translateY(-50%);width:20px;height:20px;display:flex;align-items:center;justify-content:center}.connection-point.source{right:8px}.connection-point.target{left:8px}.point-dot{width:10px;height:10px;border-radius:50%;background:#cbd5e1;transition:all .2s ease;box-shadow:0 0 0 3px #cbd5e14d}.mapped .point-dot{background:#10b981;box-shadow:0 0 0 3px #10b9814d}.nested-fields{border-left:2px solid #e2e8f0;margin-left:28px}.field-item.has-default{background-color:#eff6ff}.field-item.has-default:hover{background-color:#dbeafe}.field-item.clickable{cursor:pointer}.field-item.clickable:hover:not(.has-default){background-color:#f1f5f9}.field-item.clickable:hover:not(.has-default):after{content:\"Click to set default\";position:absolute;right:32px;font-size:11px;color:#64748b;font-style:italic}.field-item.clickable.has-default:hover:after{content:\"Click to edit\";position:absolute;right:32px;font-size:11px;color:#3b82f6;font-style:italic}.default-indicator{display:flex;align-items:center;gap:4px;color:#3b82f6;font-size:12px;background:#dbeafe;padding:2px 8px;border-radius:4px;max-width:100px;overflow:hidden}.default-indicator mat-icon{font-size:14px;width:14px;height:14px}.default-indicator .default-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}\n"] }]
|
|
1252
1375
|
}], propDecorators: { schema: [{
|
|
1253
1376
|
type: Input
|
|
1254
1377
|
}], side: [{
|
|
@@ -1263,6 +1386,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
1263
1386
|
type: Output
|
|
1264
1387
|
}], fieldDrop: [{
|
|
1265
1388
|
type: Output
|
|
1389
|
+
}], sourceDrop: [{
|
|
1390
|
+
type: Output
|
|
1266
1391
|
}], fieldPositionsChanged: [{
|
|
1267
1392
|
type: Output
|
|
1268
1393
|
}], fieldDefaultValueClick: [{
|
|
@@ -1458,7 +1583,7 @@ class ConditionBuilderComponent {
|
|
|
1458
1583
|
return op?.label || operator;
|
|
1459
1584
|
}
|
|
1460
1585
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ConditionBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1461
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ConditionBuilderComponent, isStandalone: true, selector: "condition-builder", inputs: { fields: "fields", condition: "condition", compact: "compact" }, outputs: { conditionChange: "conditionChange" }, ngImport: i0, template: "<div class=\"condition-builder\" [class.compact]=\"compact\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup, isRoot: true }\"></ng-container>\r\n</div>\r\n\r\n<!-- Recursive group template -->\r\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\r\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\r\n <!-- Group header with logic toggle (only show if multiple children or nested) -->\r\n @if (group.children.length > 1 || !isRoot) {\r\n <div class=\"group-header\">\r\n <div class=\"logic-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'and'\"\r\n (click)=\"onLogicChange(group, 'and')\"\r\n >\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'or'\"\r\n (click)=\"onLogicChange(group, 'or')\"\r\n >\r\n OR\r\n </button>\r\n </div>\r\n @if (!isRoot) {\r\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group children -->\r\n <div class=\"group-children\">\r\n @for (item of group.children; track item.id; let i = $index) {\r\n <!-- Logic connector between items -->\r\n @if (i > 0) {\r\n <div class=\"logic-connector\">\r\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\r\n {{ group.logic | uppercase }}\r\n </span>\r\n </div>\r\n }\r\n\r\n @if (isCondition(item)) {\r\n <!-- Condition row -->\r\n <div class=\"condition-row\">\r\n <!-- Field selector (only if multiple fields) -->\r\n @if (showFieldSelector) {\r\n <mat-form-field appearance=\"outline\" class=\"field-select\">\r\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\r\n @for (field of availableFields; track field.path) {\r\n <mat-option [value]=\"field.path\">{{ field.name }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- Operator selector -->\r\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\r\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\r\n @for (op of getOperatorsForField(item.field); track op.value) {\r\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Value input (only if operator needs value) -->\r\n @if (operatorNeedsValue(item.operator)) {\r\n @if (item.valueType === 'boolean') {\r\n <mat-slide-toggle\r\n [checked]=\"item.value === true\"\r\n (change)=\"onValueChange(item, $event.checked)\"\r\n class=\"bool-toggle\"\r\n >\r\n </mat-slide-toggle>\r\n } @else if (item.valueType === 'number') {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n } @else {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"text\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- Remove condition button -->\r\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove\" (click)=\"removeItem(group, item.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n } @else if (isGroup(item)) {\r\n <!-- Nested group -->\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\r\n }\r\n }\r\n\r\n <!-- Empty state -->\r\n @if (group.children.length === 0) {\r\n <div class=\"empty-group\">\r\n <span>No conditions defined</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Group actions -->\r\n <div class=\"group-actions\">\r\n <button type=\"button\" class=\"add-btn\" (click)=\"addCondition(group)\">\r\n <mat-icon>add</mat-icon>\r\n @if (!compact) {\r\n <span>Add condition</span>\r\n }\r\n </button>\r\n @if (!compact && group.children.length > 0) {\r\n <button type=\"button\" class=\"add-btn add-group-btn\" (click)=\"addGroup(group)\">\r\n <mat-icon>folder_open</mat-icon>\r\n <span>Add group</span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n</ng-template>\r\n", styles: [".condition-builder.compact .filter-group{padding:8px}.condition-builder.compact .condition-row{gap:6px}.condition-builder.compact .operator-select{min-width:100px}.condition-builder.compact .value-input{min-width:80px}.condition-builder.compact .add-btn{padding:4px 8px;font-size:12px}.filter-group{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px}.filter-group.nested-group{margin-top:8px;background:#f1f5f9;border-color:#cbd5e1}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.logic-toggle{display:flex;gap:4px}.logic-btn{padding:4px 12px;border:1px solid #cbd5e1;background:#fff;border-radius:4px;font-size:11px;font-weight:600;color:#64748b;cursor:pointer;transition:all .15s ease}.logic-btn:hover{border-color:#94a3b8}.logic-btn.active{background:#6366f1;border-color:#6366f1;color:#fff}.group-children{display:flex;flex-direction:column;gap:8px}.logic-connector{display:flex;align-items:center;justify-content:center;padding:4px 0}.logic-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:4px}.logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-badge.or{background:#fef3c7;color:#b45309}.condition-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.field-select{min-width:120px}.operator-select{min-width:130px}.value-input{min-width:100px;flex:1}.bool-toggle{margin:0 8px}.remove-btn,.remove-group-btn{width:28px;height:28px;line-height:28px;flex-shrink:0}.remove-btn mat-icon,.remove-group-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-btn:hover mat-icon,.remove-group-btn:hover mat-icon{color:#ef4444}.empty-group{display:flex;align-items:center;justify-content:center;padding:12px;color:#94a3b8;font-size:13px;font-style:italic}.group-actions{display:flex;gap:8px;margin-top:8px;padding-top:8px;border-top:1px dashed #e2e8f0}.add-btn{display:flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px dashed #cbd5e1;border-radius:6px;font-size:13px;color:#64748b;cursor:pointer;transition:all .15s ease}.add-btn mat-icon{font-size:16px;width:16px;height:16px}.add-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.add-btn.add-group-btn{border-style:solid}::ng-deep .condition-builder .mat-mdc-form-field{font-size:13px}::ng-deep .condition-builder .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .condition-builder .mdc-text-field--outlined{--mdc-outlined-text-field-container-shape: 6px}::ng-deep .condition-builder .mat-mdc-form-field-infix{padding-top:8px!important;padding-bottom:8px!important;min-height:36px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
1586
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ConditionBuilderComponent, isStandalone: true, selector: "condition-builder", inputs: { fields: "fields", condition: "condition", compact: "compact" }, outputs: { conditionChange: "conditionChange" }, ngImport: i0, template: "<div class=\"condition-builder\" [class.compact]=\"compact\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup, isRoot: true }\"></ng-container>\r\n</div>\r\n\r\n<!-- Recursive group template -->\r\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\r\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\r\n <!-- Group header with logic toggle (only show if multiple children or nested) -->\r\n @if (group.children.length > 1 || !isRoot) {\r\n <div class=\"group-header\">\r\n <div class=\"logic-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'and'\"\r\n (click)=\"onLogicChange(group, 'and')\"\r\n >\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'or'\"\r\n (click)=\"onLogicChange(group, 'or')\"\r\n >\r\n OR\r\n </button>\r\n </div>\r\n @if (!isRoot) {\r\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group children -->\r\n <div class=\"group-children\">\r\n @for (item of group.children; track item.id; let i = $index) {\r\n <!-- Logic connector between items -->\r\n @if (i > 0) {\r\n <div class=\"logic-connector\">\r\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\r\n {{ group.logic | uppercase }}\r\n </span>\r\n </div>\r\n }\r\n\r\n @if (isCondition(item)) {\r\n <!-- Condition row -->\r\n <div class=\"condition-row\">\r\n <!-- Field selector (only if multiple fields) -->\r\n @if (showFieldSelector) {\r\n <mat-form-field appearance=\"outline\" class=\"field-select\">\r\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\r\n @for (field of availableFields; track field.path) {\r\n <mat-option [value]=\"field.path\">{{ field.name }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- Operator selector -->\r\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\r\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\r\n @for (op of getOperatorsForField(item.field); track op.value) {\r\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Value input (only if operator needs value) -->\r\n @if (operatorNeedsValue(item.operator)) {\r\n @if (item.valueType === 'boolean') {\r\n <mat-slide-toggle\r\n [checked]=\"item.value === true\"\r\n (change)=\"onValueChange(item, $event.checked)\"\r\n class=\"bool-toggle\"\r\n >\r\n </mat-slide-toggle>\r\n } @else if (item.valueType === 'number') {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n } @else {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"text\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- Remove condition button -->\r\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove\" (click)=\"removeItem(group, item.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n } @else if (isGroup(item)) {\r\n <!-- Nested group -->\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\r\n }\r\n }\r\n\r\n <!-- Empty state -->\r\n @if (group.children.length === 0) {\r\n <div class=\"empty-group\">\r\n <span>No conditions defined</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Group actions -->\r\n <div class=\"group-actions\">\r\n <button type=\"button\" class=\"add-btn\" (click)=\"addCondition(group)\">\r\n <mat-icon>add</mat-icon>\r\n @if (!compact) {\r\n <span>Add condition</span>\r\n }\r\n </button>\r\n @if (!compact && group.children.length > 0) {\r\n <button type=\"button\" class=\"add-btn add-group-btn\" (click)=\"addGroup(group)\">\r\n <mat-icon>folder_open</mat-icon>\r\n <span>Add group</span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n</ng-template>\r\n", styles: [".condition-builder.compact .filter-group{padding:8px}.condition-builder.compact .condition-row{gap:6px}.condition-builder.compact .operator-select{min-width:100px}.condition-builder.compact .value-input{min-width:80px}.condition-builder.compact .add-btn{padding:4px 8px;font-size:12px}.filter-group{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px}.filter-group.nested-group{margin-top:8px;background:#f1f5f9;border-color:#cbd5e1}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.logic-toggle{display:flex;gap:4px}.logic-btn{padding:4px 12px;border:1px solid #cbd5e1;background:#fff;border-radius:4px;font-size:11px;font-weight:600;color:#64748b;cursor:pointer;transition:all .15s ease}.logic-btn:hover{border-color:#94a3b8}.logic-btn.active{background:#6366f1;border-color:#6366f1;color:#fff}.group-children{display:flex;flex-direction:column;gap:8px}.logic-connector{display:flex;align-items:center;justify-content:center;padding:4px 0}.logic-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:4px}.logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-badge.or{background:#fef3c7;color:#b45309}.condition-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.field-select{min-width:120px}.operator-select{min-width:130px}.value-input{min-width:100px;flex:1}.bool-toggle{margin:0 8px}.remove-btn,.remove-group-btn{width:28px;height:28px;line-height:28px;flex-shrink:0}.remove-btn mat-icon,.remove-group-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-btn:hover mat-icon,.remove-group-btn:hover mat-icon{color:#ef4444}.empty-group{display:flex;align-items:center;justify-content:center;padding:12px;color:#94a3b8;font-size:13px;font-style:italic}.group-actions{display:flex;gap:8px;margin-top:8px;padding-top:8px;border-top:1px dashed #e2e8f0}.add-btn{display:flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px dashed #cbd5e1;border-radius:6px;font-size:13px;color:#64748b;cursor:pointer;transition:all .15s ease}.add-btn mat-icon{font-size:16px;width:16px;height:16px}.add-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.add-btn.add-group-btn{border-style:solid}::ng-deep .condition-builder .mat-mdc-form-field{font-size:13px}::ng-deep .condition-builder .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .condition-builder .mdc-text-field--outlined{--mdc-outlined-text-field-container-shape: 6px}::ng-deep .condition-builder .mat-mdc-form-field-infix{padding-top:8px!important;padding-bottom:8px!important;min-height:36px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: i4.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
1462
1587
|
}
|
|
1463
1588
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ConditionBuilderComponent, decorators: [{
|
|
1464
1589
|
type: Component,
|
|
@@ -1759,7 +1884,7 @@ class TransformationPopoverComponent {
|
|
|
1759
1884
|
return labels[operator] || operator;
|
|
1760
1885
|
}
|
|
1761
1886
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1762
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TransformationPopoverComponent, isStandalone: true, selector: "transformation-popover", inputs: { mapping: "mapping", position: "position", sampleData: "sampleData" }, outputs: { save: "save", delete: "delete", close: "close" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"transformation-popover\" [ngStyle]=\"getPopoverStyle()\">\r\n <div class=\"popover-arrow\"></div>\r\n\r\n <div class=\"popover-header\">\r\n <span class=\"popover-title\">Transformation</span>\r\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-content\">\r\n <!-- Source/Target Info -->\r\n <div class=\"mapping-info\">\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Source:</span>\r\n <span class=\"info-value\">{{ getSourceFieldNames() }}</span>\r\n </div>\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Target:</span>\r\n <span class=\"info-value\">{{ mapping.targetField.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Single Step Mode (default, clean UI) -->\r\n @if (!isMultiStep) {\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: steps[0], index: 0, showHeader: false }\"></ng-container>\r\n\r\n <!-- Preview Section -->\r\n <div class=\"preview-section\">\r\n <span class=\"preview-label\">Preview:</span>\r\n <div class=\"preview-value\">{{ stepPreviews[0] || '(empty)' }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Multi-Step Mode -->\r\n @if (isMultiStep) {\r\n <div class=\"steps-container\" cdkDropList (cdkDropListDropped)=\"onStepDrop($event)\">\r\n @for (step of steps; track $index; let i = $index) {\r\n <div class=\"step-card\" [class.expanded]=\"isStepExpanded(i)\" cdkDrag>\r\n <!-- Collapsed View -->\r\n @if (!isStepExpanded(i)) {\r\n <div class=\"step-collapsed\" (click)=\"toggleStep(i)\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle (click)=\"$event.stopPropagation()\">drag_indicator</mat-icon>\r\n <div class=\"step-collapsed-header\">\r\n <div class=\"step-title-row\">\r\n <span class=\"step-number\">Step {{ i + 1 }}: {{ getStepTypeLabel(step.type) }}</span>\r\n @if (hasCondition(step)) {\r\n <span class=\"condition-badge\" matTooltip=\"{{ getConditionSummary(step) }}\">\r\n <mat-icon>filter_alt</mat-icon>\r\n if\r\n </span>\r\n }\r\n </div>\r\n <div class=\"step-collapsed-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"expand-btn\"\r\n (click)=\"toggleStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Edit step\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"step-collapsed-preview\">\r\n <span class=\"step-input\">\"{{ (stepInputs[i] || '') | slice:0:20 }}{{ (stepInputs[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n <mat-icon class=\"arrow-icon\">arrow_forward</mat-icon>\r\n <span class=\"step-output\">\"{{ (stepPreviews[i] || '') | slice:0:20 }}{{ (stepPreviews[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Expanded View -->\r\n @if (isStepExpanded(i)) {\r\n <div class=\"step-expanded\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle>drag_indicator</mat-icon>\r\n <div class=\"step-header\">\r\n <span class=\"step-number\">Step {{ i + 1 }}</span>\r\n <div class=\"step-header-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"collapse-btn\"\r\n (click)=\"toggleStep(i)\"\r\n matTooltip=\"Collapse\"\r\n >\r\n <mat-icon>expand_less</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i)\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"step-content\">\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: step, index: i, showHeader: false }\"></ng-container>\r\n </div>\r\n\r\n <!-- Step Preview -->\r\n <div class=\"step-preview\">\r\n <mat-icon class=\"preview-arrow\">arrow_downward</mat-icon>\r\n <span class=\"step-preview-value\">{{ stepPreviews[i] || '(empty)' }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Final Preview -->\r\n <div class=\"preview-section final-preview\">\r\n <span class=\"preview-label\">Final Result:</span>\r\n <div class=\"preview-value\">{{ finalPreview || '(empty)' }}</div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Add Step Button - Always visible -->\r\n <div class=\"add-step-section\">\r\n <button mat-stroked-button class=\"add-step-btn\" (click)=\"addStep()\">\r\n <mat-icon>add</mat-icon>\r\n @if (isMultiStep) {\r\n Add Step\r\n } @else {\r\n Add Transformation Step\r\n }\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-actions\">\r\n <button mat-button color=\"warn\" (click)=\"onDelete()\" matTooltip=\"Remove this mapping\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n <div class=\"action-spacer\"></div>\r\n <button mat-button (click)=\"onClose()\">Cancel</button>\r\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\r\n </div>\r\n</div>\r\n\r\n<!-- Backdrop -->\r\n<div class=\"popover-backdrop\" (click)=\"onClose()\"></div>\r\n\r\n<!-- Step Configuration Template -->\r\n<ng-template #stepConfig let-step=\"step\" let-index=\"index\" let-showHeader=\"showHeader\">\r\n <!-- Transformation Type -->\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Transformation Type</mat-label>\r\n <mat-select [(ngModel)]=\"step.type\" (selectionChange)=\"onStepTypeChange(index)\">\r\n @for (t of availableTransformations; track t.type) {\r\n <mat-option [value]=\"t.type\">{{ t.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Type-specific options -->\r\n @switch (step.type) {\r\n @case ('concat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Separator</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.separator\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\" \"\r\n />\r\n <mat-hint>Join values with this (default: space)</mat-hint>\r\n </mat-form-field>\r\n <div class=\"or-divider\">or use template for custom format</div>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n [placeholder]=\"'{0} - {1}'\"\r\n />\r\n <mat-hint>Overrides separator if set</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('substring') {\r\n <div class=\"config-section config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Start Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.startIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>End Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.endIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('replace') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Search For</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.searchValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Replace With</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.replaceValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('dateFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Output Format</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.outputFormat\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"YYYY-MM-DD\"\r\n />\r\n <mat-hint>YYYY, MM, DD, HH, mm, ss</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('numberFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Decimal Places</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.decimalPlaces\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <div class=\"config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Prefix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.prefix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"$\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Suffix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.suffix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n }\r\n\r\n @case ('mask') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Pattern</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.pattern\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"(###) ###-####\"\r\n />\r\n <mat-hint># = character from input</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('template') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template Expression</mat-label>\r\n <textarea\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n rows=\"3\"\r\n [placeholder]=\"'Hello {0}, your ID is {1}'\"\r\n ></textarea>\r\n @if (index === 0) {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }}, {{ '{' }}1{{ '}' }}, etc. for source fields</mat-hint>\r\n } @else {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }} for the value from previous step</mat-hint>\r\n }\r\n </mat-form-field>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Condition Section -->\r\n <div class=\"condition-section\">\r\n <mat-checkbox\r\n [checked]=\"hasCondition(step)\"\r\n (change)=\"toggleCondition(step, $event.checked)\"\r\n class=\"condition-checkbox\"\r\n >\r\n Apply conditionally\r\n </mat-checkbox>\r\n\r\n @if (hasCondition(step)) {\r\n <div class=\"condition-content\">\r\n <condition-builder\r\n [condition]=\"step.condition?.root || null\"\r\n [compact]=\"true\"\r\n (conditionChange)=\"onConditionChange(step, $event)\"\r\n ></condition-builder>\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.popover-backdrop{position:fixed;inset:0;background:#0000004d;z-index:999}.transformation-popover{position:fixed;z-index:1000;width:420px;background:#fff;border-radius:12px;box-shadow:0 10px 40px #0003;transform:translate(-50%,-50%);animation:popoverIn .2s ease-out}@keyframes popoverIn{0%{opacity:0;transform:translate(-50%,-50%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#f8fafc;border-radius:12px 12px 0 0}.popover-title{font-size:16px;font-weight:600;color:#1e293b}.close-btn{width:32px;height:32px;line-height:32px}.close-btn mat-icon{font-size:20px;width:20px;height:20px}.popover-content{padding:20px;max-height:500px;overflow-y:auto}.mapping-info{background:#f1f5f9;border-radius:8px;padding:12px 16px;margin-bottom:16px}.info-row{display:flex;align-items:center;gap:8px}.info-row+.info-row{margin-top:8px}.info-label{font-size:12px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;width:60px}.info-value{font-size:14px;color:#1e293b;font-weight:500}.full-width{width:100%}.config-section{margin-top:12px}.or-divider{text-align:center;font-size:11px;color:#94a3b8;margin:8px 0}.condition-section{margin-top:16px;padding-top:12px;border-top:1px dashed #e2e8f0}.condition-checkbox{font-size:13px;color:#64748b}.condition-content{margin-top:12px}.config-row{display:flex;gap:12px}.config-row mat-form-field{flex:1}.preview-section{margin-top:16px;padding:12px 16px;background:linear-gradient(135deg,#eff6ff,#f0fdf4);border-radius:8px;border:1px solid #e0e7ff}.preview-label{font-size:11px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px;display:block;margin-bottom:6px}.preview-value{font-size:14px;color:#1e293b;font-family:Monaco,Menlo,monospace;word-break:break-all;min-height:20px}.steps-container{display:flex;flex-direction:column;gap:12px}.step-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;transition:all .2s ease}.step-card.expanded{border-color:#6366f1;border-width:2px;background:#fff}.step-collapsed{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px;cursor:pointer;transition:background .15s ease}.step-collapsed:hover{background:#f1f5f9}.step-collapsed .drag-handle{grid-row:span 2;align-self:center}.step-collapsed-header{display:flex;align-items:center;flex-wrap:wrap;gap:4px}.step-collapsed-header .step-title-row{display:flex;align-items:center;gap:8px;flex:1}.step-collapsed-header .step-number{flex-shrink:0}.condition-badge{display:inline-flex;align-items:center;gap:2px;padding:2px 6px;background:#fef3c7;border:1px solid #f59e0b;border-radius:4px;font-size:10px;font-weight:600;color:#b45309;cursor:help}.condition-badge mat-icon{font-size:12px;width:12px;height:12px}.step-collapsed-actions{display:flex;align-items:center;gap:4px}.step-collapsed-preview{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:12px;font-family:Monaco,Menlo,monospace}.step-input{color:#64748b;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.arrow-icon{font-size:14px;width:14px;height:14px;color:#6366f1}.step-output{color:#059669;font-weight:500;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.step-expanded{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px}.step-expanded .drag-handle{grid-row:1;align-self:center}.step-expanded .step-header,.step-expanded .step-content,.step-expanded .step-preview{grid-column:2}.step-header{display:flex;align-items:center;margin-bottom:12px}.step-header .step-number{flex:1}.step-header-actions{display:flex;align-items:center;gap:4px}.step-number{font-size:12px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px}.expand-btn,.collapse-btn{width:28px;height:28px;line-height:28px}.expand-btn mat-icon,.collapse-btn mat-icon{font-size:18px;width:18px;height:18px;color:#64748b}.expand-btn:hover mat-icon,.collapse-btn:hover mat-icon{color:#6366f1}.remove-step-btn{width:28px;height:28px;line-height:28px}.remove-step-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-step-btn:hover mat-icon{color:#ef4444}.step-preview{display:flex;align-items:center;gap:8px;margin-top:12px;padding:8px 12px;background:#f1f5f9;border-radius:6px}.preview-arrow{font-size:16px;width:16px;height:16px;color:#6366f1}.step-preview-value{font-size:13px;color:#475569;font-family:Monaco,Menlo,monospace;word-break:break-all;flex:1}.add-step-section{padding:12px 20px;border-top:1px solid #e2e8f0;background:#fafafa}.add-step-btn{width:100%;border-style:dashed;color:#64748b}.add-step-btn mat-icon{font-size:18px;width:18px;height:18px;margin-right:4px}.add-step-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.final-preview{margin-top:16px;background:linear-gradient(135deg,#ecfdf5,#d1fae5);border:2px solid #10b981;border-radius:8px;position:relative}.final-preview .preview-label{color:#059669;font-size:12px;font-weight:700}.final-preview .preview-value{color:#065f46;font-weight:600}.final-preview:before{content:\"\\2713\";position:absolute;right:12px;top:10px;color:#10b981;font-size:16px;font-weight:700}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;border-radius:0 0 12px 12px}.action-spacer{flex:1}::ng-deep .transformation-popover .mat-mdc-form-field{font-size:14px}::ng-deep .transformation-popover .mat-mdc-text-field-wrapper{background:#fff}::ng-deep .transformation-popover .mat-mdc-form-field-subscript-wrapper{font-size:11px}.drag-handle{cursor:grab;color:#94a3b8;font-size:20px;width:20px;height:20px;flex-shrink:0}.drag-handle:hover{color:#6366f1}.drag-handle:active{cursor:grabbing}.step-card.cdk-drag-preview{box-shadow:0 5px 20px #00000040;border-radius:8px;background:#fff}.step-card.cdk-drag-placeholder{opacity:.4;background:#e2e8f0;border:2px dashed #94a3b8}.step-card.cdk-drag-animating{transition:transform .2s ease}.steps-container.cdk-drop-list-dragging .step-card:not(.cdk-drag-placeholder){transition:transform .2s ease}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i9.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i9.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i9.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionBuilderComponent, selector: "condition-builder", inputs: ["fields", "condition", "compact"], outputs: ["conditionChange"] }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }] });
|
|
1887
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TransformationPopoverComponent, isStandalone: true, selector: "transformation-popover", inputs: { mapping: "mapping", position: "position", sampleData: "sampleData" }, outputs: { save: "save", delete: "delete", close: "close" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"transformation-popover\" [ngStyle]=\"getPopoverStyle()\">\r\n <div class=\"popover-arrow\"></div>\r\n\r\n <div class=\"popover-header\">\r\n <span class=\"popover-title\">Transformation</span>\r\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-content\">\r\n <!-- Source/Target Info -->\r\n <div class=\"mapping-info\">\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Source:</span>\r\n <span class=\"info-value\">{{ getSourceFieldNames() }}</span>\r\n </div>\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Target:</span>\r\n <span class=\"info-value\">{{ mapping.targetField.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Single Step Mode (default, clean UI) -->\r\n @if (!isMultiStep) {\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: steps[0], index: 0, showHeader: false }\"></ng-container>\r\n\r\n <!-- Preview Section -->\r\n <div class=\"preview-section\">\r\n <span class=\"preview-label\">Preview:</span>\r\n <div class=\"preview-value\">{{ stepPreviews[0] || '(empty)' }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Multi-Step Mode -->\r\n @if (isMultiStep) {\r\n <div class=\"steps-container\" cdkDropList (cdkDropListDropped)=\"onStepDrop($event)\">\r\n @for (step of steps; track $index; let i = $index) {\r\n <div class=\"step-card\" [class.expanded]=\"isStepExpanded(i)\" cdkDrag>\r\n <!-- Collapsed View -->\r\n @if (!isStepExpanded(i)) {\r\n <div class=\"step-collapsed\" (click)=\"toggleStep(i)\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle (click)=\"$event.stopPropagation()\">drag_indicator</mat-icon>\r\n <div class=\"step-collapsed-header\">\r\n <div class=\"step-title-row\">\r\n <span class=\"step-number\">Step {{ i + 1 }}: {{ getStepTypeLabel(step.type) }}</span>\r\n @if (hasCondition(step)) {\r\n <span class=\"condition-badge\" matTooltip=\"{{ getConditionSummary(step) }}\">\r\n <mat-icon>filter_alt</mat-icon>\r\n if\r\n </span>\r\n }\r\n </div>\r\n <div class=\"step-collapsed-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"expand-btn\"\r\n (click)=\"toggleStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Edit step\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"step-collapsed-preview\">\r\n <span class=\"step-input\">\"{{ (stepInputs[i] || '') | slice:0:20 }}{{ (stepInputs[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n <mat-icon class=\"arrow-icon\">arrow_forward</mat-icon>\r\n <span class=\"step-output\">\"{{ (stepPreviews[i] || '') | slice:0:20 }}{{ (stepPreviews[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Expanded View -->\r\n @if (isStepExpanded(i)) {\r\n <div class=\"step-expanded\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle>drag_indicator</mat-icon>\r\n <div class=\"step-header\">\r\n <span class=\"step-number\">Step {{ i + 1 }}</span>\r\n <div class=\"step-header-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"collapse-btn\"\r\n (click)=\"toggleStep(i)\"\r\n matTooltip=\"Collapse\"\r\n >\r\n <mat-icon>expand_less</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i)\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"step-content\">\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: step, index: i, showHeader: false }\"></ng-container>\r\n </div>\r\n\r\n <!-- Step Preview -->\r\n <div class=\"step-preview\">\r\n <mat-icon class=\"preview-arrow\">arrow_downward</mat-icon>\r\n <span class=\"step-preview-value\">{{ stepPreviews[i] || '(empty)' }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Final Preview -->\r\n <div class=\"preview-section final-preview\">\r\n <span class=\"preview-label\">Final Result:</span>\r\n <div class=\"preview-value\">{{ finalPreview || '(empty)' }}</div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Add Step Button - Always visible -->\r\n <div class=\"add-step-section\">\r\n <button mat-stroked-button class=\"add-step-btn\" (click)=\"addStep()\">\r\n <mat-icon>add</mat-icon>\r\n @if (isMultiStep) {\r\n Add Step\r\n } @else {\r\n Add Transformation Step\r\n }\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-actions\">\r\n <button mat-button color=\"warn\" (click)=\"onDelete()\" matTooltip=\"Remove this mapping\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n <div class=\"action-spacer\"></div>\r\n <button mat-button (click)=\"onClose()\">Cancel</button>\r\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\r\n </div>\r\n</div>\r\n\r\n<!-- Backdrop -->\r\n<div class=\"popover-backdrop\" (click)=\"onClose()\"></div>\r\n\r\n<!-- Step Configuration Template -->\r\n<ng-template #stepConfig let-step=\"step\" let-index=\"index\" let-showHeader=\"showHeader\">\r\n <!-- Transformation Type -->\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Transformation Type</mat-label>\r\n <mat-select [(ngModel)]=\"step.type\" (selectionChange)=\"onStepTypeChange(index)\">\r\n @for (t of availableTransformations; track t.type) {\r\n <mat-option [value]=\"t.type\">{{ t.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Type-specific options -->\r\n @switch (step.type) {\r\n @case ('concat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Separator</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.separator\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\" \"\r\n />\r\n <mat-hint>Join values with this (default: space)</mat-hint>\r\n </mat-form-field>\r\n <div class=\"or-divider\">or use template for custom format</div>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n [placeholder]=\"'{0} - {1}'\"\r\n />\r\n <mat-hint>Overrides separator if set</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('substring') {\r\n <div class=\"config-section config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Start Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.startIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>End Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.endIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('replace') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Search For</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.searchValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Replace With</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.replaceValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('dateFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Output Format</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.outputFormat\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"YYYY-MM-DD\"\r\n />\r\n <mat-hint>YYYY, MM, DD, HH, mm, ss</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('numberFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Decimal Places</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.decimalPlaces\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <div class=\"config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Prefix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.prefix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"$\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Suffix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.suffix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n }\r\n\r\n @case ('mask') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Pattern</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.pattern\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"(###) ###-####\"\r\n />\r\n <mat-hint># = character from input</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('template') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template Expression</mat-label>\r\n <textarea\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n rows=\"3\"\r\n [placeholder]=\"'Hello {0}, your ID is {1}'\"\r\n ></textarea>\r\n @if (index === 0) {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }}, {{ '{' }}1{{ '}' }}, etc. for source fields</mat-hint>\r\n } @else {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }} for the value from previous step</mat-hint>\r\n }\r\n </mat-form-field>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Condition Section -->\r\n <div class=\"condition-section\">\r\n <mat-checkbox\r\n [checked]=\"hasCondition(step)\"\r\n (change)=\"toggleCondition(step, $event.checked)\"\r\n class=\"condition-checkbox\"\r\n >\r\n Apply conditionally\r\n </mat-checkbox>\r\n\r\n @if (hasCondition(step)) {\r\n <div class=\"condition-content\">\r\n <condition-builder\r\n [condition]=\"step.condition?.root || null\"\r\n [compact]=\"true\"\r\n (conditionChange)=\"onConditionChange(step, $event)\"\r\n ></condition-builder>\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.popover-backdrop{position:fixed;inset:0;background:#0000004d;z-index:999}.transformation-popover{position:fixed;z-index:1000;width:420px;background:#fff;border-radius:12px;box-shadow:0 10px 40px #0003;transform:translate(-50%,-50%);animation:popoverIn .2s ease-out}@keyframes popoverIn{0%{opacity:0;transform:translate(-50%,-50%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#f8fafc;border-radius:12px 12px 0 0}.popover-title{font-size:16px;font-weight:600;color:#1e293b}.close-btn{width:32px;height:32px;line-height:32px}.close-btn mat-icon{font-size:20px;width:20px;height:20px}.popover-content{padding:20px;max-height:500px;overflow-y:auto}.mapping-info{background:#f1f5f9;border-radius:8px;padding:12px 16px;margin-bottom:16px}.info-row{display:flex;align-items:center;gap:8px}.info-row+.info-row{margin-top:8px}.info-label{font-size:12px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;width:60px}.info-value{font-size:14px;color:#1e293b;font-weight:500}.full-width{width:100%}.config-section{margin-top:12px}.or-divider{text-align:center;font-size:11px;color:#94a3b8;margin:8px 0}.condition-section{margin-top:16px;padding-top:12px;border-top:1px dashed #e2e8f0}.condition-checkbox{font-size:13px;color:#64748b}.condition-content{margin-top:12px}.config-row{display:flex;gap:12px}.config-row mat-form-field{flex:1}.preview-section{margin-top:16px;padding:12px 16px;background:linear-gradient(135deg,#eff6ff,#f0fdf4);border-radius:8px;border:1px solid #e0e7ff}.preview-label{font-size:11px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px;display:block;margin-bottom:6px}.preview-value{font-size:14px;color:#1e293b;font-family:Monaco,Menlo,monospace;word-break:break-all;min-height:20px}.steps-container{display:flex;flex-direction:column;gap:12px}.step-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;transition:all .2s ease}.step-card.expanded{border-color:#6366f1;border-width:2px;background:#fff}.step-collapsed{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px;cursor:pointer;transition:background .15s ease}.step-collapsed:hover{background:#f1f5f9}.step-collapsed .drag-handle{grid-row:span 2;align-self:center}.step-collapsed-header{display:flex;align-items:center;flex-wrap:wrap;gap:4px}.step-collapsed-header .step-title-row{display:flex;align-items:center;gap:8px;flex:1}.step-collapsed-header .step-number{flex-shrink:0}.condition-badge{display:inline-flex;align-items:center;gap:2px;padding:2px 6px;background:#fef3c7;border:1px solid #f59e0b;border-radius:4px;font-size:10px;font-weight:600;color:#b45309;cursor:help}.condition-badge mat-icon{font-size:12px;width:12px;height:12px}.step-collapsed-actions{display:flex;align-items:center;gap:4px}.step-collapsed-preview{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:12px;font-family:Monaco,Menlo,monospace}.step-input{color:#64748b;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.arrow-icon{font-size:14px;width:14px;height:14px;color:#6366f1}.step-output{color:#059669;font-weight:500;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.step-expanded{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px}.step-expanded .drag-handle{grid-row:1;align-self:center}.step-expanded .step-header,.step-expanded .step-content,.step-expanded .step-preview{grid-column:2}.step-header{display:flex;align-items:center;margin-bottom:12px}.step-header .step-number{flex:1}.step-header-actions{display:flex;align-items:center;gap:4px}.step-number{font-size:12px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px}.expand-btn,.collapse-btn{width:28px;height:28px;line-height:28px}.expand-btn mat-icon,.collapse-btn mat-icon{font-size:18px;width:18px;height:18px;color:#64748b}.expand-btn:hover mat-icon,.collapse-btn:hover mat-icon{color:#6366f1}.remove-step-btn{width:28px;height:28px;line-height:28px}.remove-step-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-step-btn:hover mat-icon{color:#ef4444}.step-preview{display:flex;align-items:center;gap:8px;margin-top:12px;padding:8px 12px;background:#f1f5f9;border-radius:6px}.preview-arrow{font-size:16px;width:16px;height:16px;color:#6366f1}.step-preview-value{font-size:13px;color:#475569;font-family:Monaco,Menlo,monospace;word-break:break-all;flex:1}.add-step-section{padding:12px 20px;border-top:1px solid #e2e8f0;background:#fafafa}.add-step-btn{width:100%;border-style:dashed;color:#64748b}.add-step-btn mat-icon{font-size:18px;width:18px;height:18px;margin-right:4px}.add-step-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.final-preview{margin-top:16px;background:linear-gradient(135deg,#ecfdf5,#d1fae5);border:2px solid #10b981;border-radius:8px;position:relative}.final-preview .preview-label{color:#059669;font-size:12px;font-weight:700}.final-preview .preview-value{color:#065f46;font-weight:600}.final-preview:before{content:\"\\2713\";position:absolute;right:12px;top:10px;color:#10b981;font-size:16px;font-weight:700}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;border-radius:0 0 12px 12px}.action-spacer{flex:1}::ng-deep .transformation-popover .mat-mdc-form-field{font-size:14px}::ng-deep .transformation-popover .mat-mdc-text-field-wrapper{background:#fff}::ng-deep .transformation-popover .mat-mdc-form-field-subscript-wrapper{font-size:11px}.drag-handle{cursor:grab;color:#94a3b8;font-size:20px;width:20px;height:20px;flex-shrink:0}.drag-handle:hover{color:#6366f1}.drag-handle:active{cursor:grabbing}.step-card.cdk-drag-preview{box-shadow:0 5px 20px #00000040;border-radius:8px;background:#fff}.step-card.cdk-drag-placeholder{opacity:.4;background:#e2e8f0;border:2px dashed #94a3b8}.step-card.cdk-drag-animating{transition:transform .2s ease}.steps-container.cdk-drop-list-dragging .step-card:not(.cdk-drag-placeholder){transition:transform .2s ease}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i4.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionBuilderComponent, selector: "condition-builder", inputs: ["fields", "condition", "compact"], outputs: ["conditionChange"] }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }] });
|
|
1763
1888
|
}
|
|
1764
1889
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, decorators: [{
|
|
1765
1890
|
type: Component,
|
|
@@ -1989,7 +2114,7 @@ class ArrayFilterModalComponent {
|
|
|
1989
2114
|
}
|
|
1990
2115
|
}
|
|
1991
2116
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1992
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ArrayFilterModalComponent, isStandalone: true, selector: "array-filter-modal", inputs: { arrayMapping: "arrayMapping" }, outputs: { save: "save", close: "close" }, ngImport: i0, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"filter-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>filter_list</mat-icon>\n <span>Array Filter</span>\n </div>\n <span class=\"array-path\">{{ arrayMapping.sourceArray.name }}[] → {{ arrayMapping.targetArray.name }}[]</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <!-- Filter mode selection -->\n <div class=\"filter-mode\">\n <mat-radio-group [value]=\"filterEnabled()\" (change)=\"filterEnabled.set($event.value)\">\n <mat-radio-button [value]=\"false\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>select_all</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">No filter</span>\n <span class=\"mode-desc\">Map all records from source to target</span>\n </div>\n </div>\n </mat-radio-button>\n <mat-radio-button [value]=\"true\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Filter records</span>\n <span class=\"mode-desc\">Only map records matching conditions</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Conditions builder (only when filter enabled) -->\n @if (filterEnabled()) {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <!-- Root group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <!-- Group header with logic toggle -->\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Group children -->\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n <!-- Logic connector between items -->\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <!-- Condition row -->\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <!-- Field selector -->\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Operator selector -->\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Value input (only if operator needs value) -->\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"number\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"text\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n }\n }\n\n <!-- Remove condition button -->\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <!-- Nested group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n <!-- Empty state -->\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition or group.</span>\n </div>\n }\n </div>\n\n <!-- Group actions -->\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.filter-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:600px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .array-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.filter-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.filter-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.filter-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.filter-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.filter-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.filter-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.filter-mode .mode-text{display:flex;flex-direction:column;gap:2px}.filter-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.filter-mode .mode-desc{font-size:12px;color:#64748b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#fffbeb;border-color:#f59e0b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#f59e0b}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#f59e0b;border-color:#f59e0b;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
2117
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ArrayFilterModalComponent, isStandalone: true, selector: "array-filter-modal", inputs: { arrayMapping: "arrayMapping" }, outputs: { save: "save", close: "close" }, ngImport: i0, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"filter-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>filter_list</mat-icon>\n <span>Array Filter</span>\n </div>\n <span class=\"array-path\">{{ arrayMapping.sourceArray.name }}[] → {{ arrayMapping.targetArray.name }}[]</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <!-- Filter mode selection -->\n <div class=\"filter-mode\">\n <mat-radio-group [value]=\"filterEnabled()\" (change)=\"filterEnabled.set($event.value)\">\n <mat-radio-button [value]=\"false\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>select_all</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">No filter</span>\n <span class=\"mode-desc\">Map all records from source to target</span>\n </div>\n </div>\n </mat-radio-button>\n <mat-radio-button [value]=\"true\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Filter records</span>\n <span class=\"mode-desc\">Only map records matching conditions</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Conditions builder (only when filter enabled) -->\n @if (filterEnabled()) {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <!-- Root group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <!-- Group header with logic toggle -->\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Group children -->\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n <!-- Logic connector between items -->\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <!-- Condition row -->\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <!-- Field selector -->\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Operator selector -->\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Value input (only if operator needs value) -->\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"number\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"text\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n }\n }\n\n <!-- Remove condition button -->\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <!-- Nested group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n <!-- Empty state -->\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition or group.</span>\n </div>\n }\n </div>\n\n <!-- Group actions -->\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.filter-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:600px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .array-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.filter-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.filter-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.filter-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.filter-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.filter-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.filter-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.filter-mode .mode-text{display:flex;flex-direction:column;gap:2px}.filter-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.filter-mode .mode-desc{font-size:12px;color:#64748b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#fffbeb;border-color:#f59e0b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#f59e0b}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#f59e0b;border-color:#f59e0b;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i4.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
1993
2118
|
}
|
|
1994
2119
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, decorators: [{
|
|
1995
2120
|
type: Component,
|
|
@@ -2193,7 +2318,7 @@ class ArraySelectorModalComponent {
|
|
|
2193
2318
|
}
|
|
2194
2319
|
}
|
|
2195
2320
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2196
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ArraySelectorModalComponent, isStandalone: true, selector: "array-selector-modal", inputs: { mapping: "mapping" }, outputs: { save: "save", close: "close" }, ngImport: i0, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"selector-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>swap_horiz</mat-icon>\n <span>Array to Object</span>\n </div>\n <span class=\"mapping-path\">{{ mapping.sourceArray.name }}[] → {{ mapping.targetObject.name }}</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <p class=\"description\">\n Select which item from the array should be mapped to the target object.\n </p>\n\n <!-- Selection mode -->\n <div class=\"selection-mode\">\n <mat-radio-group [value]=\"selectionMode()\" (change)=\"selectionMode.set($event.value)\">\n <mat-radio-button value=\"first\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>first_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First item</span>\n <span class=\"mode-desc\">Use the first item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"last\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>last_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Last item</span>\n <span class=\"mode-desc\">Use the last item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"condition\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First matching condition</span>\n <span class=\"mode-desc\">Use the first item that matches the condition</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Condition builder (only when condition mode selected) -->\n @if (selectionMode() === 'condition') {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: conditionGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"number\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"text\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n }\n }\n\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition to filter.</span>\n </div>\n }\n </div>\n\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.selector-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:580px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#8b5cf6,#6366f1);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .mapping-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.description{font-size:14px;color:#64748b;margin:0 0 16px}.selection-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.selection-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.selection-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.selection-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.selection-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.selection-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.selection-mode .mode-text{display:flex;flex-direction:column;gap:2px}.selection-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.selection-mode .mode-desc{font-size:12px;color:#64748b}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#f5f3ff;border-color:#8b5cf6}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#8b5cf6}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#8b5cf6;border-color:#8b5cf6;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
2321
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ArraySelectorModalComponent, isStandalone: true, selector: "array-selector-modal", inputs: { mapping: "mapping" }, outputs: { save: "save", close: "close" }, ngImport: i0, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"selector-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>swap_horiz</mat-icon>\n <span>Array to Object</span>\n </div>\n <span class=\"mapping-path\">{{ mapping.sourceArray.name }}[] → {{ mapping.targetObject.name }}</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <p class=\"description\">\n Select which item from the array should be mapped to the target object.\n </p>\n\n <!-- Selection mode -->\n <div class=\"selection-mode\">\n <mat-radio-group [value]=\"selectionMode()\" (change)=\"selectionMode.set($event.value)\">\n <mat-radio-button value=\"first\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>first_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First item</span>\n <span class=\"mode-desc\">Use the first item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"last\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>last_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Last item</span>\n <span class=\"mode-desc\">Use the last item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"condition\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First matching condition</span>\n <span class=\"mode-desc\">Use the first item that matches the condition</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Condition builder (only when condition mode selected) -->\n @if (selectionMode() === 'condition') {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: conditionGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"number\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"text\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n }\n }\n\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition to filter.</span>\n </div>\n }\n </div>\n\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.selector-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:580px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#8b5cf6,#6366f1);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .mapping-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.description{font-size:14px;color:#64748b;margin:0 0 16px}.selection-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.selection-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.selection-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.selection-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.selection-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.selection-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.selection-mode .mode-text{display:flex;flex-direction:column;gap:2px}.selection-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.selection-mode .mode-desc{font-size:12px;color:#64748b}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#f5f3ff;border-color:#8b5cf6}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#8b5cf6}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#8b5cf6;border-color:#8b5cf6;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i4.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
2197
2322
|
}
|
|
2198
2323
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, decorators: [{
|
|
2199
2324
|
type: Component,
|
|
@@ -2232,7 +2357,7 @@ class DefaultValuePopoverComponent {
|
|
|
2232
2357
|
dateValue = null;
|
|
2233
2358
|
ngOnInit() {
|
|
2234
2359
|
if (this.existingValue) {
|
|
2235
|
-
switch (this.
|
|
2360
|
+
switch (this.fieldType) {
|
|
2236
2361
|
case 'string':
|
|
2237
2362
|
this.stringValue = this.existingValue.value || '';
|
|
2238
2363
|
break;
|
|
@@ -2283,7 +2408,7 @@ class DefaultValuePopoverComponent {
|
|
|
2283
2408
|
}
|
|
2284
2409
|
}
|
|
2285
2410
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2286
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DefaultValuePopoverComponent, isStandalone: true, selector: "default-value-popover", inputs: { field: "field", existingValue: "existingValue", position: "position" }, outputs: { save: "save", delete: "delete", close: "close" }, ngImport: i0, template: "<div class=\"popover-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"popover-container\">\n <div class=\"popover-header\">\n <div class=\"header-title\">\n <mat-icon>edit</mat-icon>\n <span>Default Value</span>\n </div>\n <button mat-icon-button (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"popover-content\">\n <div class=\"field-info\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-type\">{{ fieldType }}</span>\n </div>\n\n <!-- String input -->\n @if (fieldType === 'string') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput [(ngModel)]=\"stringValue\" placeholder=\"Enter default value\">\n </mat-form-field>\n }\n\n <!-- Number input -->\n @if (fieldType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput type=\"number\" [(ngModel)]=\"numberValue\" placeholder=\"Enter number\">\n </mat-form-field>\n }\n\n <!-- Boolean input -->\n @if (fieldType === 'boolean') {\n <div class=\"boolean-input\">\n <mat-slide-toggle [(ngModel)]=\"booleanValue\">\n {{ booleanValue ? 'True' : 'False' }}\n </mat-slide-toggle>\n </div>\n }\n\n <!-- Date input -->\n @if (fieldType === 'date') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Date</mat-label>\n <input matInput [matDatepicker]=\"picker\" [(ngModel)]=\"dateValue\">\n <mat-datepicker-toggle matIconSuffix [for]=\"picker\"></mat-datepicker-toggle>\n <mat-datepicker #picker></mat-datepicker>\n </mat-form-field>\n }\n </div>\n\n <div class=\"popover-actions\">\n @if (existingValue) {\n <button mat-button color=\"warn\" (click)=\"onDelete()\">\n <mat-icon>delete</mat-icon>\n Remove\n </button>\n }\n <span class=\"spacer\"></span>\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Save</button>\n </div>\n </div>\n</div>\n", styles: [".popover-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center}.popover-container{background:#fff;border-radius:12px;box-shadow:0 8px 32px #0003;min-width:320px;max-width:400px;overflow:hidden}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:linear-gradient(135deg,#10b981,#059669);color:#fff}.popover-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.popover-header .header-title mat-icon{font-size:20px;width:20px;height:20px}.popover-header button{color:#fff}.popover-content{padding:20px}.field-info{display:flex;align-items:center;gap:8px;margin-bottom:16px;padding:10px 12px;background:#f8fafc;border-radius:8px}.field-info .field-name{font-weight:600;color:#1e293b}.field-info .field-type{font-size:12px;color:#64748b;background:#e2e8f0;padding:2px 8px;border-radius:4px;text-transform:uppercase}.full-width{width:100%}.boolean-input{padding:12px 0}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;background:#f8fafc;border-top:1px solid #e2e8f0}.popover-actions .spacer{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5
|
|
2411
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DefaultValuePopoverComponent, isStandalone: true, selector: "default-value-popover", inputs: { field: "field", existingValue: "existingValue", position: "position" }, outputs: { save: "save", delete: "delete", close: "close" }, ngImport: i0, template: "<div class=\"popover-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"popover-container\">\n <div class=\"popover-header\">\n <div class=\"header-title\">\n <mat-icon>edit</mat-icon>\n <span>Default Value</span>\n </div>\n <button mat-icon-button (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"popover-content\">\n <div class=\"field-info\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-type\">{{ fieldType }}</span>\n </div>\n\n <!-- String input -->\n @if (fieldType === 'string') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput [(ngModel)]=\"stringValue\" placeholder=\"Enter default value\">\n </mat-form-field>\n }\n\n <!-- Number input -->\n @if (fieldType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput type=\"number\" [(ngModel)]=\"numberValue\" placeholder=\"Enter number\">\n </mat-form-field>\n }\n\n <!-- Boolean input -->\n @if (fieldType === 'boolean') {\n <div class=\"boolean-input\">\n <mat-slide-toggle [(ngModel)]=\"booleanValue\">\n {{ booleanValue ? 'True' : 'False' }}\n </mat-slide-toggle>\n </div>\n }\n\n <!-- Date input -->\n @if (fieldType === 'date') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Date</mat-label>\n <input matInput [matDatepicker]=\"picker\" [(ngModel)]=\"dateValue\">\n <mat-datepicker-toggle matIconSuffix [for]=\"picker\"></mat-datepicker-toggle>\n <mat-datepicker #picker></mat-datepicker>\n </mat-form-field>\n }\n </div>\n\n <div class=\"popover-actions\">\n @if (existingValue) {\n <button mat-button color=\"warn\" (click)=\"onDelete()\">\n <mat-icon>delete</mat-icon>\n Remove\n </button>\n }\n <span class=\"spacer\"></span>\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Save</button>\n </div>\n </div>\n</div>\n", styles: [".popover-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center}.popover-container{background:#fff;border-radius:12px;box-shadow:0 8px 32px #0003;min-width:320px;max-width:400px;overflow:hidden}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:linear-gradient(135deg,#10b981,#059669);color:#fff}.popover-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.popover-header .header-title mat-icon{font-size:20px;width:20px;height:20px}.popover-header button{color:#fff}.popover-content{padding:20px}.field-info{display:flex;align-items:center;gap:8px;margin-bottom:16px;padding:10px 12px;background:#f8fafc;border-radius:8px}.field-info .field-name{font-weight:600;color:#1e293b}.field-info .field-type{font-size:12px;color:#64748b;background:#e2e8f0;padding:2px 8px;border-radius:4px;text-transform:uppercase}.full-width{width:100%}.boolean-input{padding:12px 0}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;background:#f8fafc;border-top:1px solid #e2e8f0}.popover-actions .spacer{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i6$1.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i6$1.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i6$1.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }] });
|
|
2287
2412
|
}
|
|
2288
2413
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, decorators: [{
|
|
2289
2414
|
type: Component,
|
|
@@ -2324,6 +2449,12 @@ class DataMapperComponent {
|
|
|
2324
2449
|
this._targetSchemaInput.set(value);
|
|
2325
2450
|
}
|
|
2326
2451
|
}
|
|
2452
|
+
set sourceSchemaRef(value) {
|
|
2453
|
+
this.mappingService.setSourceSchemaRef(value ?? null);
|
|
2454
|
+
}
|
|
2455
|
+
set targetSchemaRef(value) {
|
|
2456
|
+
this.mappingService.setTargetSchemaRef(value ?? null);
|
|
2457
|
+
}
|
|
2327
2458
|
sampleData = {};
|
|
2328
2459
|
mappingsChange = new EventEmitter();
|
|
2329
2460
|
svgContainer;
|
|
@@ -2376,6 +2507,12 @@ class DataMapperComponent {
|
|
|
2376
2507
|
dragSourceField = null;
|
|
2377
2508
|
dragStartPoint = null;
|
|
2378
2509
|
resizeObserver;
|
|
2510
|
+
// Endpoint drag state
|
|
2511
|
+
isEndpointDragging = false;
|
|
2512
|
+
endpointDragMappingId = null;
|
|
2513
|
+
endpointDragType = null;
|
|
2514
|
+
endpointDragSourceIndex = null;
|
|
2515
|
+
endpointDragAnchorPoint = null;
|
|
2379
2516
|
ngAfterViewInit() {
|
|
2380
2517
|
this.setupResizeObserver();
|
|
2381
2518
|
}
|
|
@@ -2411,15 +2548,27 @@ class DataMapperComponent {
|
|
|
2411
2548
|
document.body.style.cursor = 'grabbing';
|
|
2412
2549
|
}
|
|
2413
2550
|
onMouseMove(event) {
|
|
2414
|
-
if (!this.
|
|
2551
|
+
if (!this.svgContainer?.nativeElement)
|
|
2415
2552
|
return;
|
|
2416
2553
|
const containerRect = this.svgContainer.nativeElement.getBoundingClientRect();
|
|
2417
2554
|
const currentPoint = {
|
|
2418
2555
|
x: event.clientX - containerRect.left,
|
|
2419
2556
|
y: event.clientY - containerRect.top,
|
|
2420
2557
|
};
|
|
2421
|
-
|
|
2422
|
-
this.
|
|
2558
|
+
// Handle new connection dragging
|
|
2559
|
+
if (this.isDragging && this.dragStartPoint) {
|
|
2560
|
+
const path = this.svgConnectorService.createDragPath(this.dragStartPoint, currentPoint);
|
|
2561
|
+
this.dragPath.set(path);
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
// Handle endpoint dragging
|
|
2565
|
+
if (this.isEndpointDragging && this.endpointDragAnchorPoint) {
|
|
2566
|
+
// Show drag path from anchor to cursor
|
|
2567
|
+
const path = this.endpointDragType === 'source'
|
|
2568
|
+
? this.svgConnectorService.createDragPath(currentPoint, this.endpointDragAnchorPoint)
|
|
2569
|
+
: this.svgConnectorService.createDragPath(this.endpointDragAnchorPoint, currentPoint);
|
|
2570
|
+
this.dragPath.set(path);
|
|
2571
|
+
}
|
|
2423
2572
|
}
|
|
2424
2573
|
onMouseUp(event) {
|
|
2425
2574
|
if (this.isDragging) {
|
|
@@ -2429,8 +2578,53 @@ class DataMapperComponent {
|
|
|
2429
2578
|
this.dragStartPoint = null;
|
|
2430
2579
|
document.body.style.cursor = '';
|
|
2431
2580
|
}
|
|
2581
|
+
if (this.isEndpointDragging) {
|
|
2582
|
+
this.cancelEndpointDrag();
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
onEndpointDragStart(connection, endpointType, sourceIndex, event) {
|
|
2586
|
+
event.stopPropagation();
|
|
2587
|
+
event.preventDefault();
|
|
2588
|
+
if (!this.svgContainer?.nativeElement)
|
|
2589
|
+
return;
|
|
2590
|
+
this.isEndpointDragging = true;
|
|
2591
|
+
this.endpointDragMappingId = connection.mappingId;
|
|
2592
|
+
this.endpointDragType = endpointType;
|
|
2593
|
+
this.endpointDragSourceIndex = sourceIndex;
|
|
2594
|
+
// Set anchor point (the point that stays fixed)
|
|
2595
|
+
if (endpointType === 'source') {
|
|
2596
|
+
// Moving source, anchor is the target
|
|
2597
|
+
this.endpointDragAnchorPoint = connection.targetPoint;
|
|
2598
|
+
}
|
|
2599
|
+
else {
|
|
2600
|
+
// Moving target, anchor is the first source (or merge point for multi-source)
|
|
2601
|
+
this.endpointDragAnchorPoint = connection.sourcePoints[0];
|
|
2602
|
+
}
|
|
2603
|
+
// Update MappingService drag state so schema-tree can detect endpoint dragging
|
|
2604
|
+
this.mappingService.startEndpointDrag(connection.mappingId, endpointType, endpointType === 'source' ? connection.sourcePoints[sourceIndex] : connection.targetPoint, sourceIndex);
|
|
2605
|
+
document.body.style.cursor = 'grabbing';
|
|
2606
|
+
this.updateConnections(); // Update to show connection as being dragged
|
|
2607
|
+
}
|
|
2608
|
+
cancelEndpointDrag() {
|
|
2609
|
+
this.dragPath.set(null);
|
|
2610
|
+
this.isEndpointDragging = false;
|
|
2611
|
+
this.endpointDragMappingId = null;
|
|
2612
|
+
this.endpointDragType = null;
|
|
2613
|
+
this.endpointDragSourceIndex = null;
|
|
2614
|
+
this.endpointDragAnchorPoint = null;
|
|
2615
|
+
document.body.style.cursor = '';
|
|
2616
|
+
this.mappingService.endDrag(); // Reset MappingService drag state
|
|
2617
|
+
this.updateConnections(); // Update to restore connection visibility
|
|
2432
2618
|
}
|
|
2433
2619
|
onFieldDrop(event) {
|
|
2620
|
+
// Handle endpoint drag completion (drop on target field)
|
|
2621
|
+
if (this.isEndpointDragging && this.endpointDragMappingId && this.endpointDragType === 'target') {
|
|
2622
|
+
this.mappingService.changeTargetField(this.endpointDragMappingId, event.field);
|
|
2623
|
+
this.mappingsChange.emit(this.mappingService.allMappings());
|
|
2624
|
+
this.updateConnections();
|
|
2625
|
+
this.cancelEndpointDrag();
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2434
2628
|
if (!this.dragSourceField)
|
|
2435
2629
|
return;
|
|
2436
2630
|
// Create mapping
|
|
@@ -2444,6 +2638,15 @@ class DataMapperComponent {
|
|
|
2444
2638
|
this.dragPath.set(null);
|
|
2445
2639
|
document.body.style.cursor = '';
|
|
2446
2640
|
}
|
|
2641
|
+
onSourceFieldDrop(event) {
|
|
2642
|
+
// Handle endpoint drag completion (drop on source field)
|
|
2643
|
+
if (this.isEndpointDragging && this.endpointDragMappingId && this.endpointDragType === 'source') {
|
|
2644
|
+
this.mappingService.changeSourceField(this.endpointDragMappingId, event.field, this.endpointDragSourceIndex ?? undefined);
|
|
2645
|
+
this.mappingsChange.emit(this.mappingService.allMappings());
|
|
2646
|
+
this.updateConnections();
|
|
2647
|
+
this.cancelEndpointDrag();
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2447
2650
|
onConnectionClick(connection, event) {
|
|
2448
2651
|
event.stopPropagation();
|
|
2449
2652
|
this.selectedMappingId.set(connection.mappingId);
|
|
@@ -2602,6 +2805,7 @@ class DataMapperComponent {
|
|
|
2602
2805
|
id: `conn-${mapping.id}`,
|
|
2603
2806
|
mappingId: mapping.id,
|
|
2604
2807
|
paths,
|
|
2808
|
+
sourcePoints,
|
|
2605
2809
|
midPoint,
|
|
2606
2810
|
targetPoint,
|
|
2607
2811
|
hasTransformation: mapping.transformations.length > 1 || mapping.transformations[0]?.type !== 'direct',
|
|
@@ -2609,6 +2813,7 @@ class DataMapperComponent {
|
|
|
2609
2813
|
isArrayMapping: mapping.isArrayMapping || false,
|
|
2610
2814
|
isArrayToObjectMapping: mapping.isArrayToObjectMapping || false,
|
|
2611
2815
|
hasFilter,
|
|
2816
|
+
isBeingDragged: mapping.id === this.endpointDragMappingId,
|
|
2612
2817
|
});
|
|
2613
2818
|
}
|
|
2614
2819
|
this.connections.set(newConnections);
|
|
@@ -2677,7 +2882,7 @@ class DataMapperComponent {
|
|
|
2677
2882
|
return connection.id;
|
|
2678
2883
|
}
|
|
2679
2884
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2680
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DataMapperComponent, isStandalone: true, selector: "data-mapper", inputs: { sourceSchema: "sourceSchema", targetSchema: "targetSchema", sampleData: "sampleData" }, outputs: { mappingsChange: "mappingsChange" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp($event)" } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true }, { propertyName: "svgElement", first: true, predicate: ["svgElement"], descendants: true }], ngImport: i0, template: "<div class=\"data-mapper\">\n <!-- Header Toolbar -->\n <div class=\"mapper-toolbar\">\n <div class=\"toolbar-title\">\n <mat-icon>account_tree</mat-icon>\n <span>Data Mapper</span>\n </div>\n <div class=\"toolbar-actions\">\n <span class=\"mapping-count\">{{ mappings().length }} mapping(s)</span>\n <button\n mat-icon-button\n matTooltip=\"Clear all mappings\"\n (click)=\"clearAllMappings()\"\n [disabled]=\"mappings().length === 0\"\n >\n <mat-icon>delete_sweep</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Main Mapper Area -->\n <div class=\"mapper-container\" #svgContainer>\n <!-- Source Schema Panel -->\n <div class=\"schema-panel source-panel\">\n <schema-tree\n [schema]=\"sourceSchemaForTree()\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n ></schema-tree>\n </div>\n\n <!-- SVG Connection Layer -->\n <svg class=\"connection-layer\" #svgElement>\n <!-- Existing connections -->\n @for (connection of connections(); track trackByConnectionId($index, connection)) {\n <g class=\"connection-group\" [class.selected]=\"connection.isSelected\" [class.array-mapping]=\"connection.isArrayMapping\" [class.array-to-object-mapping]=\"connection.isArrayToObjectMapping\">\n <!-- Connection paths -->\n @for (path of connection.paths; track $index) {\n <!-- Shadow path for glow effect -->\n <path\n [attr.d]=\"path\"\n fill=\"none\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n stroke-width=\"6\"\n stroke-opacity=\"0.2\"\n stroke-linecap=\"round\"\n />\n <!-- Main connection path -->\n <path\n [attr.d]=\"path\"\n class=\"connection-path\"\n [class.selected]=\"connection.isSelected\"\n [class.array-path]=\"connection.isArrayMapping\"\n [class.array-to-object-path]=\"connection.isArrayToObjectMapping\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n fill=\"none\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? '8,4' : 'none'\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n <!-- Invisible wider path for easier clicking -->\n <path\n [attr.d]=\"path\"\n class=\"connection-hitbox\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"20\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n }\n\n <!-- Transformation node -->\n <g\n class=\"transformation-node\"\n [class.has-transformation]=\"connection.hasTransformation\"\n [class.is-array-mapping]=\"connection.isArrayMapping\"\n [class.is-array-to-object-mapping]=\"connection.isArrayToObjectMapping\"\n [attr.transform]=\"'translate(' + connection.midPoint.x + ',' + connection.midPoint.y + ')'\"\n (click)=\"onTransformationNodeClick(connection, $event)\"\n >\n <circle r=\"14\" class=\"node-bg\" [attr.fill]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" />\n <text\n class=\"node-icon\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n font-family=\"Material Icons\"\n font-size=\"16\"\n [attr.fill]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? 'white' : ''\"\n >\n {{ getTransformationIcon(connection.mappingId) }}\n </text>\n </g>\n </g>\n }\n\n <!-- Drag preview path -->\n @if (dragPath()) {\n <path\n [attr.d]=\"dragPath()\"\n class=\"drag-path\"\n fill=\"none\"\n stroke=\"#6366f1\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"8,4\"\n stroke-linecap=\"round\"\n />\n }\n </svg>\n\n <!-- Target Schema Panel -->\n <div class=\"schema-panel target-panel\">\n <schema-tree\n [schema]=\"targetSchemaForTree()\"\n [side]=\"'target'\"\n [mappings]=\"mappings()\"\n [defaultValues]=\"defaultValues()\"\n (fieldDrop)=\"onFieldDrop($event)\"\n (fieldPositionsChanged)=\"onTargetPositionsChanged($event)\"\n (fieldDefaultValueClick)=\"onDefaultValueClick($event)\"\n ></schema-tree>\n </div>\n </div>\n\n <!-- Instructions -->\n @if (mappings().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>drag_indicator</mat-icon>\n <p>Drag fields from the source schema to the target schema to create mappings</p>\n </div>\n }\n</div>\n\n<!-- Transformation Popover -->\n@if (showPopover() && selectedMapping()) {\n <transformation-popover\n [mapping]=\"selectedMapping()!\"\n [position]=\"popoverPosition()!\"\n [sampleData]=\"sampleData\"\n (save)=\"onPopoverSave($event)\"\n (delete)=\"onPopoverDelete()\"\n (close)=\"closePopover()\"\n ></transformation-popover>\n}\n\n<!-- Array Filter Modal -->\n@if (showArrayFilterModal() && selectedArrayMapping()) {\n <array-filter-modal\n [arrayMapping]=\"selectedArrayMapping()!\"\n (save)=\"onArrayFilterSave($event)\"\n (close)=\"closeArrayFilterModal()\"\n ></array-filter-modal>\n}\n\n<!-- Array Selector Modal (for array-to-object) -->\n@if (showArraySelectorModal() && selectedArrayToObjectMapping()) {\n <array-selector-modal\n [mapping]=\"selectedArrayToObjectMapping()!\"\n (save)=\"onArraySelectorSave($event)\"\n (close)=\"closeArraySelectorModal()\"\n ></array-selector-modal>\n}\n\n<!-- Default Value Popover -->\n@if (showDefaultValuePopover() && selectedDefaultValueField()) {\n <default-value-popover\n [field]=\"selectedDefaultValueField()!\"\n [existingValue]=\"getExistingDefaultValue(selectedDefaultValueField()!.id)\"\n [position]=\"defaultValuePopoverPosition()!\"\n (save)=\"onDefaultValueSave($event)\"\n (delete)=\"onDefaultValueDelete()\"\n (close)=\"closeDefaultValuePopover()\"\n ></default-value-popover>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;--data-mapper-bg: #f8fafc;--data-mapper-border-radius: 16px;--data-mapper-shadow: 0 4px 20px rgba(0, 0, 0, .08);--data-mapper-border-color: #e2e8f0;--data-mapper-toolbar-bg: white;--data-mapper-toolbar-border: #e2e8f0;--data-mapper-panel-bg: white;--data-mapper-panel-header-bg: #f8fafc;--data-mapper-panel-width: 320px;--data-mapper-panel-border-radius: 12px;--data-mapper-text-primary: #1e293b;--data-mapper-text-secondary: #64748b;--data-mapper-text-muted: #94a3b8;--data-mapper-accent-primary: #6366f1;--data-mapper-accent-success: #22c55e;--data-mapper-accent-warning: #f59e0b;--data-mapper-accent-danger: #ef4444;--data-mapper-connector-color: #6366f1;--data-mapper-connector-width: 2px;--data-mapper-connector-hover-color: #4f46e5;--data-mapper-spacing-sm: 8px;--data-mapper-spacing-md: 16px;--data-mapper-spacing-lg: 24px;--data-mapper-font-size-sm: 12px;--data-mapper-font-size-md: 14px;--data-mapper-font-size-lg: 18px}.data-mapper{display:flex;flex-direction:column;height:100%;min-height:0;flex:1;background:var(--data-mapper-bg);border-radius:var(--data-mapper-border-radius);overflow:hidden;box-shadow:var(--data-mapper-shadow)}.mapper-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--data-mapper-spacing-md) var(--data-mapper-spacing-lg);background:var(--data-mapper-toolbar-bg);border-bottom:1px solid var(--data-mapper-border-color);flex-shrink:0}.toolbar-title{display:flex;align-items:center;gap:12px;font-size:var(--data-mapper-font-size-lg);font-weight:600;color:var(--data-mapper-text-primary)}.toolbar-title mat-icon{color:var(--data-mapper-accent-primary)}.toolbar-actions{display:flex;align-items:center;gap:var(--data-mapper-spacing-md)}.mapping-count{font-size:var(--data-mapper-font-size-sm);color:var(--data-mapper-text-secondary);background:#f1f5f9;padding:6px 12px;border-radius:20px}.mapper-container{flex:1;display:flex;position:relative;padding:24px;gap:0;overflow:hidden;min-height:0}.schema-panel{width:var(--data-mapper-panel-width);flex-shrink:0;z-index:2;height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.schema-panel.source-panel{margin-right:auto}.schema-panel.target-panel{margin-left:auto}.schema-panel schema-tree{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.connection-layer{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1}.connection-group{pointer-events:auto;cursor:pointer}.connection-group:hover .connection-path{stroke-width:3;filter:drop-shadow(0 2px 4px rgba(99,102,241,.3))}.connection-group:hover .transformation-node .node-bg{transform:scale(1.1)}.connection-group.selected .connection-path{stroke-width:3;filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))}.connection-path{transition:stroke-width .15s ease,filter .15s ease}.connection-hitbox{cursor:pointer}.transformation-node{cursor:pointer;pointer-events:auto}.transformation-node .node-bg{fill:#fff;stroke:#6366f1;stroke-width:2;transition:transform .15s ease,fill .15s ease}.transformation-node .node-icon{fill:#6366f1;pointer-events:none}.transformation-node.has-transformation .node-bg{fill:#6366f1}.transformation-node.has-transformation .node-icon{fill:#fff}.transformation-node:hover .node-bg{transform:scale(1.15);filter:drop-shadow(0 2px 6px rgba(99,102,241,.4))}.drag-path{pointer-events:none;animation:dashMove .5s linear infinite}@keyframes dashMove{0%{stroke-dashoffset:24}to{stroke-dashoffset:0}}.empty-state{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;color:#94a3b8;pointer-events:none}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:14px;max-width:300px;line-height:1.6}@media(max-width:900px){.schema-panel{width:260px}.mapper-container{padding:16px}}@media(max-width:700px){.mapper-container{flex-direction:column;gap:24px}.schema-panel{width:100%;max-height:300px}.connection-layer{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SchemaTreeComponent, selector: "schema-tree", inputs: ["schema", "side", "mappings", "defaultValues"], outputs: ["fieldDragStart", "fieldDragEnd", "fieldDrop", "fieldPositionsChanged", "fieldDefaultValueClick"] }, { kind: "component", type: TransformationPopoverComponent, selector: "transformation-popover", inputs: ["mapping", "position", "sampleData"], outputs: ["save", "delete", "close"] }, { kind: "component", type: ArrayFilterModalComponent, selector: "array-filter-modal", inputs: ["arrayMapping"], outputs: ["save", "close"] }, { kind: "component", type: ArraySelectorModalComponent, selector: "array-selector-modal", inputs: ["mapping"], outputs: ["save", "close"] }, { kind: "component", type: DefaultValuePopoverComponent, selector: "default-value-popover", inputs: ["field", "existingValue", "position"], outputs: ["save", "delete", "close"] }] });
|
|
2885
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DataMapperComponent, isStandalone: true, selector: "data-mapper", inputs: { sourceSchema: "sourceSchema", targetSchema: "targetSchema", sourceSchemaRef: "sourceSchemaRef", targetSchemaRef: "targetSchemaRef", sampleData: "sampleData" }, outputs: { mappingsChange: "mappingsChange" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp($event)" } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true }, { propertyName: "svgElement", first: true, predicate: ["svgElement"], descendants: true }], ngImport: i0, template: "<div class=\"data-mapper\">\n <!-- Header Toolbar -->\n <div class=\"mapper-toolbar\">\n <div class=\"toolbar-title\">\n <mat-icon>account_tree</mat-icon>\n <span>Data Mapper</span>\n </div>\n <div class=\"toolbar-actions\">\n <span class=\"mapping-count\">{{ mappings().length }} mapping(s)</span>\n <button\n mat-icon-button\n matTooltip=\"Clear all mappings\"\n (click)=\"clearAllMappings()\"\n [disabled]=\"mappings().length === 0\"\n >\n <mat-icon>delete_sweep</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Main Mapper Area -->\n <div class=\"mapper-container\" #svgContainer>\n <!-- Source Schema Panel -->\n <div class=\"schema-panel source-panel\">\n <schema-tree\n [schema]=\"sourceSchemaForTree()\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n (sourceDrop)=\"onSourceFieldDrop($event)\"\n ></schema-tree>\n </div>\n\n <!-- SVG Connection Layer -->\n <svg class=\"connection-layer\" #svgElement>\n <!-- Existing connections -->\n @for (connection of connections(); track trackByConnectionId($index, connection)) {\n <g class=\"connection-group\" [class.selected]=\"connection.isSelected\" [class.array-mapping]=\"connection.isArrayMapping\" [class.array-to-object-mapping]=\"connection.isArrayToObjectMapping\" [class.being-dragged]=\"connection.isBeingDragged\">\n <!-- Connection paths -->\n @for (path of connection.paths; track $index) {\n <!-- Shadow path for glow effect -->\n <path\n [attr.d]=\"path\"\n fill=\"none\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n stroke-width=\"6\"\n stroke-opacity=\"0.2\"\n stroke-linecap=\"round\"\n />\n <!-- Main connection path -->\n <path\n [attr.d]=\"path\"\n class=\"connection-path\"\n [class.selected]=\"connection.isSelected\"\n [class.array-path]=\"connection.isArrayMapping\"\n [class.array-to-object-path]=\"connection.isArrayToObjectMapping\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n fill=\"none\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? '8,4' : 'none'\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n <!-- Invisible wider path for easier clicking -->\n <path\n [attr.d]=\"path\"\n class=\"connection-hitbox\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"20\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n }\n\n <!-- Transformation node -->\n <g\n class=\"transformation-node\"\n [class.has-transformation]=\"connection.hasTransformation\"\n [class.is-array-mapping]=\"connection.isArrayMapping\"\n [class.is-array-to-object-mapping]=\"connection.isArrayToObjectMapping\"\n [attr.transform]=\"'translate(' + connection.midPoint.x + ',' + connection.midPoint.y + ')'\"\n (click)=\"onTransformationNodeClick(connection, $event)\"\n >\n <circle r=\"14\" class=\"node-bg\" [attr.fill]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" />\n <text\n class=\"node-icon\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n font-family=\"Material Icons\"\n font-size=\"16\"\n [attr.fill]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? 'white' : ''\"\n >\n {{ getTransformationIcon(connection.mappingId) }}\n </text>\n </g>\n\n <!-- Source endpoint circles (draggable) -->\n @for (sourcePoint of connection.sourcePoints; track $index) {\n <circle\n class=\"endpoint-handle source-endpoint\"\n [attr.cx]=\"sourcePoint.x\"\n [attr.cy]=\"sourcePoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'source', $index, $event)\"\n />\n }\n\n <!-- Target endpoint circle (draggable) -->\n <circle\n class=\"endpoint-handle target-endpoint\"\n [attr.cx]=\"connection.targetPoint.x\"\n [attr.cy]=\"connection.targetPoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'target', 0, $event)\"\n />\n </g>\n }\n\n <!-- Drag preview path -->\n @if (dragPath()) {\n <path\n [attr.d]=\"dragPath()\"\n class=\"drag-path\"\n fill=\"none\"\n stroke=\"#6366f1\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"8,4\"\n stroke-linecap=\"round\"\n />\n }\n </svg>\n\n <!-- Target Schema Panel -->\n <div class=\"schema-panel target-panel\">\n <schema-tree\n [schema]=\"targetSchemaForTree()\"\n [side]=\"'target'\"\n [mappings]=\"mappings()\"\n [defaultValues]=\"defaultValues()\"\n (fieldDrop)=\"onFieldDrop($event)\"\n (fieldPositionsChanged)=\"onTargetPositionsChanged($event)\"\n (fieldDefaultValueClick)=\"onDefaultValueClick($event)\"\n ></schema-tree>\n </div>\n </div>\n\n <!-- Instructions -->\n @if (mappings().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>drag_indicator</mat-icon>\n <p>Drag fields from the source schema to the target schema to create mappings</p>\n </div>\n }\n</div>\n\n<!-- Transformation Popover -->\n@if (showPopover() && selectedMapping()) {\n <transformation-popover\n [mapping]=\"selectedMapping()!\"\n [position]=\"popoverPosition()!\"\n [sampleData]=\"sampleData\"\n (save)=\"onPopoverSave($event)\"\n (delete)=\"onPopoverDelete()\"\n (close)=\"closePopover()\"\n ></transformation-popover>\n}\n\n<!-- Array Filter Modal -->\n@if (showArrayFilterModal() && selectedArrayMapping()) {\n <array-filter-modal\n [arrayMapping]=\"selectedArrayMapping()!\"\n (save)=\"onArrayFilterSave($event)\"\n (close)=\"closeArrayFilterModal()\"\n ></array-filter-modal>\n}\n\n<!-- Array Selector Modal (for array-to-object) -->\n@if (showArraySelectorModal() && selectedArrayToObjectMapping()) {\n <array-selector-modal\n [mapping]=\"selectedArrayToObjectMapping()!\"\n (save)=\"onArraySelectorSave($event)\"\n (close)=\"closeArraySelectorModal()\"\n ></array-selector-modal>\n}\n\n<!-- Default Value Popover -->\n@if (showDefaultValuePopover() && selectedDefaultValueField()) {\n <default-value-popover\n [field]=\"selectedDefaultValueField()!\"\n [existingValue]=\"getExistingDefaultValue(selectedDefaultValueField()!.id)\"\n [position]=\"defaultValuePopoverPosition()!\"\n (save)=\"onDefaultValueSave($event)\"\n (delete)=\"onDefaultValueDelete()\"\n (close)=\"closeDefaultValuePopover()\"\n ></default-value-popover>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;--data-mapper-bg: #f8fafc;--data-mapper-border-radius: 16px;--data-mapper-shadow: 0 4px 20px rgba(0, 0, 0, .08);--data-mapper-border-color: #e2e8f0;--data-mapper-toolbar-bg: white;--data-mapper-toolbar-border: #e2e8f0;--data-mapper-panel-bg: white;--data-mapper-panel-header-bg: #f8fafc;--data-mapper-panel-width: 320px;--data-mapper-panel-border-radius: 12px;--data-mapper-text-primary: #1e293b;--data-mapper-text-secondary: #64748b;--data-mapper-text-muted: #94a3b8;--data-mapper-accent-primary: #6366f1;--data-mapper-accent-success: #22c55e;--data-mapper-accent-warning: #f59e0b;--data-mapper-accent-danger: #ef4444;--data-mapper-connector-color: #6366f1;--data-mapper-connector-width: 2px;--data-mapper-connector-hover-color: #4f46e5;--data-mapper-spacing-sm: 8px;--data-mapper-spacing-md: 16px;--data-mapper-spacing-lg: 24px;--data-mapper-font-size-sm: 12px;--data-mapper-font-size-md: 14px;--data-mapper-font-size-lg: 18px}.data-mapper{display:flex;flex-direction:column;height:100%;min-height:0;flex:1;background:var(--data-mapper-bg);border-radius:var(--data-mapper-border-radius);overflow:hidden;box-shadow:var(--data-mapper-shadow)}.mapper-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--data-mapper-spacing-md) var(--data-mapper-spacing-lg);background:var(--data-mapper-toolbar-bg);border-bottom:1px solid var(--data-mapper-border-color);flex-shrink:0}.toolbar-title{display:flex;align-items:center;gap:12px;font-size:var(--data-mapper-font-size-lg);font-weight:600;color:var(--data-mapper-text-primary)}.toolbar-title mat-icon{color:var(--data-mapper-accent-primary)}.toolbar-actions{display:flex;align-items:center;gap:var(--data-mapper-spacing-md)}.mapping-count{font-size:var(--data-mapper-font-size-sm);color:var(--data-mapper-text-secondary);background:#f1f5f9;padding:6px 12px;border-radius:20px}.mapper-container{flex:1;display:flex;position:relative;padding:24px;gap:0;overflow:hidden;min-height:0}.schema-panel{width:var(--data-mapper-panel-width);flex-shrink:0;z-index:2;height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.schema-panel.source-panel{margin-right:auto}.schema-panel.target-panel{margin-left:auto}.schema-panel schema-tree{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.connection-layer{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3}.connection-group{pointer-events:auto;cursor:pointer}.connection-group:hover .connection-path{stroke-width:3;filter:drop-shadow(0 2px 4px rgba(99,102,241,.3))}.connection-group:hover .transformation-node .node-bg{transform:scale(1.1)}.connection-group.selected .connection-path{stroke-width:3;filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))}.connection-group.being-dragged{opacity:.3;pointer-events:none}.connection-group.being-dragged .endpoint-handle{opacity:0}.connection-path{transition:stroke-width .15s ease,filter .15s ease}.connection-hitbox{cursor:pointer}.transformation-node{cursor:pointer;pointer-events:auto}.transformation-node .node-bg{fill:#fff;stroke:#6366f1;stroke-width:2;transition:transform .15s ease,fill .15s ease}.transformation-node .node-icon{fill:#6366f1;pointer-events:none}.transformation-node.has-transformation .node-bg{fill:#6366f1}.transformation-node.has-transformation .node-icon{fill:#fff}.transformation-node:hover .node-bg{transform:scale(1.15);filter:drop-shadow(0 2px 6px rgba(99,102,241,.4))}.drag-path{pointer-events:none;animation:dashMove .5s linear infinite}@keyframes dashMove{0%{stroke-dashoffset:24}to{stroke-dashoffset:0}}.endpoint-handle{fill:#fff;stroke:#6366f1;stroke-width:2;cursor:grab;opacity:0;transition:opacity .15s ease,transform .15s ease,r .15s ease;pointer-events:auto}.endpoint-handle:hover{opacity:1;r:8;fill:#6366f1;cursor:grab}.endpoint-handle:active{cursor:grabbing}.endpoint-handle.selected,.connection-group:hover .endpoint-handle{opacity:.7}.connection-group.selected .endpoint-handle{opacity:1}.empty-state{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;color:#94a3b8;pointer-events:none}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:14px;max-width:300px;line-height:1.6}@media(max-width:900px){.schema-panel{width:260px}.mapper-container{padding:16px}}@media(max-width:700px){.mapper-container{flex-direction:column;gap:24px}.schema-panel{width:100%;max-height:300px}.connection-layer{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SchemaTreeComponent, selector: "schema-tree", inputs: ["schema", "side", "mappings", "defaultValues"], outputs: ["fieldDragStart", "fieldDragEnd", "fieldDrop", "sourceDrop", "fieldPositionsChanged", "fieldDefaultValueClick"] }, { kind: "component", type: TransformationPopoverComponent, selector: "transformation-popover", inputs: ["mapping", "position", "sampleData"], outputs: ["save", "delete", "close"] }, { kind: "component", type: ArrayFilterModalComponent, selector: "array-filter-modal", inputs: ["arrayMapping"], outputs: ["save", "close"] }, { kind: "component", type: ArraySelectorModalComponent, selector: "array-selector-modal", inputs: ["mapping"], outputs: ["save", "close"] }, { kind: "component", type: DefaultValuePopoverComponent, selector: "default-value-popover", inputs: ["field", "existingValue", "position"], outputs: ["save", "delete", "close"] }] });
|
|
2681
2886
|
}
|
|
2682
2887
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, decorators: [{
|
|
2683
2888
|
type: Component,
|
|
@@ -2691,11 +2896,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
2691
2896
|
ArrayFilterModalComponent,
|
|
2692
2897
|
ArraySelectorModalComponent,
|
|
2693
2898
|
DefaultValuePopoverComponent,
|
|
2694
|
-
], template: "<div class=\"data-mapper\">\n <!-- Header Toolbar -->\n <div class=\"mapper-toolbar\">\n <div class=\"toolbar-title\">\n <mat-icon>account_tree</mat-icon>\n <span>Data Mapper</span>\n </div>\n <div class=\"toolbar-actions\">\n <span class=\"mapping-count\">{{ mappings().length }} mapping(s)</span>\n <button\n mat-icon-button\n matTooltip=\"Clear all mappings\"\n (click)=\"clearAllMappings()\"\n [disabled]=\"mappings().length === 0\"\n >\n <mat-icon>delete_sweep</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Main Mapper Area -->\n <div class=\"mapper-container\" #svgContainer>\n <!-- Source Schema Panel -->\n <div class=\"schema-panel source-panel\">\n <schema-tree\n [schema]=\"sourceSchemaForTree()\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n ></schema-tree>\n </div>\n\n <!-- SVG Connection Layer -->\n <svg class=\"connection-layer\" #svgElement>\n <!-- Existing connections -->\n @for (connection of connections(); track trackByConnectionId($index, connection)) {\n <g class=\"connection-group\" [class.selected]=\"connection.isSelected\" [class.array-mapping]=\"connection.isArrayMapping\" [class.array-to-object-mapping]=\"connection.isArrayToObjectMapping\">\n <!-- Connection paths -->\n @for (path of connection.paths; track $index) {\n <!-- Shadow path for glow effect -->\n <path\n [attr.d]=\"path\"\n fill=\"none\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n stroke-width=\"6\"\n stroke-opacity=\"0.2\"\n stroke-linecap=\"round\"\n />\n <!-- Main connection path -->\n <path\n [attr.d]=\"path\"\n class=\"connection-path\"\n [class.selected]=\"connection.isSelected\"\n [class.array-path]=\"connection.isArrayMapping\"\n [class.array-to-object-path]=\"connection.isArrayToObjectMapping\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n fill=\"none\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? '8,4' : 'none'\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n <!-- Invisible wider path for easier clicking -->\n <path\n [attr.d]=\"path\"\n class=\"connection-hitbox\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"20\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n }\n\n <!-- Transformation node -->\n <g\n class=\"transformation-node\"\n [class.has-transformation]=\"connection.hasTransformation\"\n [class.is-array-mapping]=\"connection.isArrayMapping\"\n [class.is-array-to-object-mapping]=\"connection.isArrayToObjectMapping\"\n [attr.transform]=\"'translate(' + connection.midPoint.x + ',' + connection.midPoint.y + ')'\"\n (click)=\"onTransformationNodeClick(connection, $event)\"\n >\n <circle r=\"14\" class=\"node-bg\" [attr.fill]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" />\n <text\n class=\"node-icon\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n font-family=\"Material Icons\"\n font-size=\"16\"\n [attr.fill]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? 'white' : ''\"\n >\n {{ getTransformationIcon(connection.mappingId) }}\n </text>\n </g>\n </g>\n }\n\n <!-- Drag preview path -->\n @if (dragPath()) {\n <path\n [attr.d]=\"dragPath()\"\n class=\"drag-path\"\n fill=\"none\"\n stroke=\"#6366f1\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"8,4\"\n stroke-linecap=\"round\"\n />\n }\n </svg>\n\n <!-- Target Schema Panel -->\n <div class=\"schema-panel target-panel\">\n <schema-tree\n [schema]=\"targetSchemaForTree()\"\n [side]=\"'target'\"\n [mappings]=\"mappings()\"\n [defaultValues]=\"defaultValues()\"\n (fieldDrop)=\"onFieldDrop($event)\"\n (fieldPositionsChanged)=\"onTargetPositionsChanged($event)\"\n (fieldDefaultValueClick)=\"onDefaultValueClick($event)\"\n ></schema-tree>\n </div>\n </div>\n\n <!-- Instructions -->\n @if (mappings().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>drag_indicator</mat-icon>\n <p>Drag fields from the source schema to the target schema to create mappings</p>\n </div>\n }\n</div>\n\n<!-- Transformation Popover -->\n@if (showPopover() && selectedMapping()) {\n <transformation-popover\n [mapping]=\"selectedMapping()!\"\n [position]=\"popoverPosition()!\"\n [sampleData]=\"sampleData\"\n (save)=\"onPopoverSave($event)\"\n (delete)=\"onPopoverDelete()\"\n (close)=\"closePopover()\"\n ></transformation-popover>\n}\n\n<!-- Array Filter Modal -->\n@if (showArrayFilterModal() && selectedArrayMapping()) {\n <array-filter-modal\n [arrayMapping]=\"selectedArrayMapping()!\"\n (save)=\"onArrayFilterSave($event)\"\n (close)=\"closeArrayFilterModal()\"\n ></array-filter-modal>\n}\n\n<!-- Array Selector Modal (for array-to-object) -->\n@if (showArraySelectorModal() && selectedArrayToObjectMapping()) {\n <array-selector-modal\n [mapping]=\"selectedArrayToObjectMapping()!\"\n (save)=\"onArraySelectorSave($event)\"\n (close)=\"closeArraySelectorModal()\"\n ></array-selector-modal>\n}\n\n<!-- Default Value Popover -->\n@if (showDefaultValuePopover() && selectedDefaultValueField()) {\n <default-value-popover\n [field]=\"selectedDefaultValueField()!\"\n [existingValue]=\"getExistingDefaultValue(selectedDefaultValueField()!.id)\"\n [position]=\"defaultValuePopoverPosition()!\"\n (save)=\"onDefaultValueSave($event)\"\n (delete)=\"onDefaultValueDelete()\"\n (close)=\"closeDefaultValuePopover()\"\n ></default-value-popover>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;--data-mapper-bg: #f8fafc;--data-mapper-border-radius: 16px;--data-mapper-shadow: 0 4px 20px rgba(0, 0, 0, .08);--data-mapper-border-color: #e2e8f0;--data-mapper-toolbar-bg: white;--data-mapper-toolbar-border: #e2e8f0;--data-mapper-panel-bg: white;--data-mapper-panel-header-bg: #f8fafc;--data-mapper-panel-width: 320px;--data-mapper-panel-border-radius: 12px;--data-mapper-text-primary: #1e293b;--data-mapper-text-secondary: #64748b;--data-mapper-text-muted: #94a3b8;--data-mapper-accent-primary: #6366f1;--data-mapper-accent-success: #22c55e;--data-mapper-accent-warning: #f59e0b;--data-mapper-accent-danger: #ef4444;--data-mapper-connector-color: #6366f1;--data-mapper-connector-width: 2px;--data-mapper-connector-hover-color: #4f46e5;--data-mapper-spacing-sm: 8px;--data-mapper-spacing-md: 16px;--data-mapper-spacing-lg: 24px;--data-mapper-font-size-sm: 12px;--data-mapper-font-size-md: 14px;--data-mapper-font-size-lg: 18px}.data-mapper{display:flex;flex-direction:column;height:100%;min-height:0;flex:1;background:var(--data-mapper-bg);border-radius:var(--data-mapper-border-radius);overflow:hidden;box-shadow:var(--data-mapper-shadow)}.mapper-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--data-mapper-spacing-md) var(--data-mapper-spacing-lg);background:var(--data-mapper-toolbar-bg);border-bottom:1px solid var(--data-mapper-border-color);flex-shrink:0}.toolbar-title{display:flex;align-items:center;gap:12px;font-size:var(--data-mapper-font-size-lg);font-weight:600;color:var(--data-mapper-text-primary)}.toolbar-title mat-icon{color:var(--data-mapper-accent-primary)}.toolbar-actions{display:flex;align-items:center;gap:var(--data-mapper-spacing-md)}.mapping-count{font-size:var(--data-mapper-font-size-sm);color:var(--data-mapper-text-secondary);background:#f1f5f9;padding:6px 12px;border-radius:20px}.mapper-container{flex:1;display:flex;position:relative;padding:24px;gap:0;overflow:hidden;min-height:0}.schema-panel{width:var(--data-mapper-panel-width);flex-shrink:0;z-index:2;height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.schema-panel.source-panel{margin-right:auto}.schema-panel.target-panel{margin-left:auto}.schema-panel schema-tree{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.connection-layer{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1}.connection-group{pointer-events:auto;cursor:pointer}.connection-group:hover .connection-path{stroke-width:3;filter:drop-shadow(0 2px 4px rgba(99,102,241,.3))}.connection-group:hover .transformation-node .node-bg{transform:scale(1.1)}.connection-group.selected .connection-path{stroke-width:3;filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))}.connection-path{transition:stroke-width .15s ease,filter .15s ease}.connection-hitbox{cursor:pointer}.transformation-node{cursor:pointer;pointer-events:auto}.transformation-node .node-bg{fill:#fff;stroke:#6366f1;stroke-width:2;transition:transform .15s ease,fill .15s ease}.transformation-node .node-icon{fill:#6366f1;pointer-events:none}.transformation-node.has-transformation .node-bg{fill:#6366f1}.transformation-node.has-transformation .node-icon{fill:#fff}.transformation-node:hover .node-bg{transform:scale(1.15);filter:drop-shadow(0 2px 6px rgba(99,102,241,.4))}.drag-path{pointer-events:none;animation:dashMove .5s linear infinite}@keyframes dashMove{0%{stroke-dashoffset:24}to{stroke-dashoffset:0}}.empty-state{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;color:#94a3b8;pointer-events:none}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:14px;max-width:300px;line-height:1.6}@media(max-width:900px){.schema-panel{width:260px}.mapper-container{padding:16px}}@media(max-width:700px){.mapper-container{flex-direction:column;gap:24px}.schema-panel{width:100%;max-height:300px}.connection-layer{display:none}}\n"] }]
|
|
2899
|
+
], template: "<div class=\"data-mapper\">\n <!-- Header Toolbar -->\n <div class=\"mapper-toolbar\">\n <div class=\"toolbar-title\">\n <mat-icon>account_tree</mat-icon>\n <span>Data Mapper</span>\n </div>\n <div class=\"toolbar-actions\">\n <span class=\"mapping-count\">{{ mappings().length }} mapping(s)</span>\n <button\n mat-icon-button\n matTooltip=\"Clear all mappings\"\n (click)=\"clearAllMappings()\"\n [disabled]=\"mappings().length === 0\"\n >\n <mat-icon>delete_sweep</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Main Mapper Area -->\n <div class=\"mapper-container\" #svgContainer>\n <!-- Source Schema Panel -->\n <div class=\"schema-panel source-panel\">\n <schema-tree\n [schema]=\"sourceSchemaForTree()\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n (sourceDrop)=\"onSourceFieldDrop($event)\"\n ></schema-tree>\n </div>\n\n <!-- SVG Connection Layer -->\n <svg class=\"connection-layer\" #svgElement>\n <!-- Existing connections -->\n @for (connection of connections(); track trackByConnectionId($index, connection)) {\n <g class=\"connection-group\" [class.selected]=\"connection.isSelected\" [class.array-mapping]=\"connection.isArrayMapping\" [class.array-to-object-mapping]=\"connection.isArrayToObjectMapping\" [class.being-dragged]=\"connection.isBeingDragged\">\n <!-- Connection paths -->\n @for (path of connection.paths; track $index) {\n <!-- Shadow path for glow effect -->\n <path\n [attr.d]=\"path\"\n fill=\"none\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n stroke-width=\"6\"\n stroke-opacity=\"0.2\"\n stroke-linecap=\"round\"\n />\n <!-- Main connection path -->\n <path\n [attr.d]=\"path\"\n class=\"connection-path\"\n [class.selected]=\"connection.isSelected\"\n [class.array-path]=\"connection.isArrayMapping\"\n [class.array-to-object-path]=\"connection.isArrayToObjectMapping\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n fill=\"none\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? '8,4' : 'none'\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n <!-- Invisible wider path for easier clicking -->\n <path\n [attr.d]=\"path\"\n class=\"connection-hitbox\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"20\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n }\n\n <!-- Transformation node -->\n <g\n class=\"transformation-node\"\n [class.has-transformation]=\"connection.hasTransformation\"\n [class.is-array-mapping]=\"connection.isArrayMapping\"\n [class.is-array-to-object-mapping]=\"connection.isArrayToObjectMapping\"\n [attr.transform]=\"'translate(' + connection.midPoint.x + ',' + connection.midPoint.y + ')'\"\n (click)=\"onTransformationNodeClick(connection, $event)\"\n >\n <circle r=\"14\" class=\"node-bg\" [attr.fill]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" />\n <text\n class=\"node-icon\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n font-family=\"Material Icons\"\n font-size=\"16\"\n [attr.fill]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? 'white' : ''\"\n >\n {{ getTransformationIcon(connection.mappingId) }}\n </text>\n </g>\n\n <!-- Source endpoint circles (draggable) -->\n @for (sourcePoint of connection.sourcePoints; track $index) {\n <circle\n class=\"endpoint-handle source-endpoint\"\n [attr.cx]=\"sourcePoint.x\"\n [attr.cy]=\"sourcePoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'source', $index, $event)\"\n />\n }\n\n <!-- Target endpoint circle (draggable) -->\n <circle\n class=\"endpoint-handle target-endpoint\"\n [attr.cx]=\"connection.targetPoint.x\"\n [attr.cy]=\"connection.targetPoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'target', 0, $event)\"\n />\n </g>\n }\n\n <!-- Drag preview path -->\n @if (dragPath()) {\n <path\n [attr.d]=\"dragPath()\"\n class=\"drag-path\"\n fill=\"none\"\n stroke=\"#6366f1\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"8,4\"\n stroke-linecap=\"round\"\n />\n }\n </svg>\n\n <!-- Target Schema Panel -->\n <div class=\"schema-panel target-panel\">\n <schema-tree\n [schema]=\"targetSchemaForTree()\"\n [side]=\"'target'\"\n [mappings]=\"mappings()\"\n [defaultValues]=\"defaultValues()\"\n (fieldDrop)=\"onFieldDrop($event)\"\n (fieldPositionsChanged)=\"onTargetPositionsChanged($event)\"\n (fieldDefaultValueClick)=\"onDefaultValueClick($event)\"\n ></schema-tree>\n </div>\n </div>\n\n <!-- Instructions -->\n @if (mappings().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>drag_indicator</mat-icon>\n <p>Drag fields from the source schema to the target schema to create mappings</p>\n </div>\n }\n</div>\n\n<!-- Transformation Popover -->\n@if (showPopover() && selectedMapping()) {\n <transformation-popover\n [mapping]=\"selectedMapping()!\"\n [position]=\"popoverPosition()!\"\n [sampleData]=\"sampleData\"\n (save)=\"onPopoverSave($event)\"\n (delete)=\"onPopoverDelete()\"\n (close)=\"closePopover()\"\n ></transformation-popover>\n}\n\n<!-- Array Filter Modal -->\n@if (showArrayFilterModal() && selectedArrayMapping()) {\n <array-filter-modal\n [arrayMapping]=\"selectedArrayMapping()!\"\n (save)=\"onArrayFilterSave($event)\"\n (close)=\"closeArrayFilterModal()\"\n ></array-filter-modal>\n}\n\n<!-- Array Selector Modal (for array-to-object) -->\n@if (showArraySelectorModal() && selectedArrayToObjectMapping()) {\n <array-selector-modal\n [mapping]=\"selectedArrayToObjectMapping()!\"\n (save)=\"onArraySelectorSave($event)\"\n (close)=\"closeArraySelectorModal()\"\n ></array-selector-modal>\n}\n\n<!-- Default Value Popover -->\n@if (showDefaultValuePopover() && selectedDefaultValueField()) {\n <default-value-popover\n [field]=\"selectedDefaultValueField()!\"\n [existingValue]=\"getExistingDefaultValue(selectedDefaultValueField()!.id)\"\n [position]=\"defaultValuePopoverPosition()!\"\n (save)=\"onDefaultValueSave($event)\"\n (delete)=\"onDefaultValueDelete()\"\n (close)=\"closeDefaultValuePopover()\"\n ></default-value-popover>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;--data-mapper-bg: #f8fafc;--data-mapper-border-radius: 16px;--data-mapper-shadow: 0 4px 20px rgba(0, 0, 0, .08);--data-mapper-border-color: #e2e8f0;--data-mapper-toolbar-bg: white;--data-mapper-toolbar-border: #e2e8f0;--data-mapper-panel-bg: white;--data-mapper-panel-header-bg: #f8fafc;--data-mapper-panel-width: 320px;--data-mapper-panel-border-radius: 12px;--data-mapper-text-primary: #1e293b;--data-mapper-text-secondary: #64748b;--data-mapper-text-muted: #94a3b8;--data-mapper-accent-primary: #6366f1;--data-mapper-accent-success: #22c55e;--data-mapper-accent-warning: #f59e0b;--data-mapper-accent-danger: #ef4444;--data-mapper-connector-color: #6366f1;--data-mapper-connector-width: 2px;--data-mapper-connector-hover-color: #4f46e5;--data-mapper-spacing-sm: 8px;--data-mapper-spacing-md: 16px;--data-mapper-spacing-lg: 24px;--data-mapper-font-size-sm: 12px;--data-mapper-font-size-md: 14px;--data-mapper-font-size-lg: 18px}.data-mapper{display:flex;flex-direction:column;height:100%;min-height:0;flex:1;background:var(--data-mapper-bg);border-radius:var(--data-mapper-border-radius);overflow:hidden;box-shadow:var(--data-mapper-shadow)}.mapper-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--data-mapper-spacing-md) var(--data-mapper-spacing-lg);background:var(--data-mapper-toolbar-bg);border-bottom:1px solid var(--data-mapper-border-color);flex-shrink:0}.toolbar-title{display:flex;align-items:center;gap:12px;font-size:var(--data-mapper-font-size-lg);font-weight:600;color:var(--data-mapper-text-primary)}.toolbar-title mat-icon{color:var(--data-mapper-accent-primary)}.toolbar-actions{display:flex;align-items:center;gap:var(--data-mapper-spacing-md)}.mapping-count{font-size:var(--data-mapper-font-size-sm);color:var(--data-mapper-text-secondary);background:#f1f5f9;padding:6px 12px;border-radius:20px}.mapper-container{flex:1;display:flex;position:relative;padding:24px;gap:0;overflow:hidden;min-height:0}.schema-panel{width:var(--data-mapper-panel-width);flex-shrink:0;z-index:2;height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.schema-panel.source-panel{margin-right:auto}.schema-panel.target-panel{margin-left:auto}.schema-panel schema-tree{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.connection-layer{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3}.connection-group{pointer-events:auto;cursor:pointer}.connection-group:hover .connection-path{stroke-width:3;filter:drop-shadow(0 2px 4px rgba(99,102,241,.3))}.connection-group:hover .transformation-node .node-bg{transform:scale(1.1)}.connection-group.selected .connection-path{stroke-width:3;filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))}.connection-group.being-dragged{opacity:.3;pointer-events:none}.connection-group.being-dragged .endpoint-handle{opacity:0}.connection-path{transition:stroke-width .15s ease,filter .15s ease}.connection-hitbox{cursor:pointer}.transformation-node{cursor:pointer;pointer-events:auto}.transformation-node .node-bg{fill:#fff;stroke:#6366f1;stroke-width:2;transition:transform .15s ease,fill .15s ease}.transformation-node .node-icon{fill:#6366f1;pointer-events:none}.transformation-node.has-transformation .node-bg{fill:#6366f1}.transformation-node.has-transformation .node-icon{fill:#fff}.transformation-node:hover .node-bg{transform:scale(1.15);filter:drop-shadow(0 2px 6px rgba(99,102,241,.4))}.drag-path{pointer-events:none;animation:dashMove .5s linear infinite}@keyframes dashMove{0%{stroke-dashoffset:24}to{stroke-dashoffset:0}}.endpoint-handle{fill:#fff;stroke:#6366f1;stroke-width:2;cursor:grab;opacity:0;transition:opacity .15s ease,transform .15s ease,r .15s ease;pointer-events:auto}.endpoint-handle:hover{opacity:1;r:8;fill:#6366f1;cursor:grab}.endpoint-handle:active{cursor:grabbing}.endpoint-handle.selected,.connection-group:hover .endpoint-handle{opacity:.7}.connection-group.selected .endpoint-handle{opacity:1}.empty-state{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;color:#94a3b8;pointer-events:none}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:14px;max-width:300px;line-height:1.6}@media(max-width:900px){.schema-panel{width:260px}.mapper-container{padding:16px}}@media(max-width:700px){.mapper-container{flex-direction:column;gap:24px}.schema-panel{width:100%;max-height:300px}.connection-layer{display:none}}\n"] }]
|
|
2695
2900
|
}], propDecorators: { sourceSchema: [{
|
|
2696
2901
|
type: Input
|
|
2697
2902
|
}], targetSchema: [{
|
|
2698
2903
|
type: Input
|
|
2904
|
+
}], sourceSchemaRef: [{
|
|
2905
|
+
type: Input
|
|
2906
|
+
}], targetSchemaRef: [{
|
|
2907
|
+
type: Input
|
|
2699
2908
|
}], sampleData: [{
|
|
2700
2909
|
type: Input
|
|
2701
2910
|
}], mappingsChange: [{
|
|
@@ -2717,14 +2926,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
2717
2926
|
class SchemaEditorComponent {
|
|
2718
2927
|
set schema(value) {
|
|
2719
2928
|
if (value) {
|
|
2929
|
+
// Don't overwrite fields if we have uncommitted changes (fields being edited or with empty names)
|
|
2930
|
+
const hasUncommittedChanges = this.fields().some(f => f.isEditing || f.isEditingDefault || f.isEditingValues || !f.name) || this.hasUncommittedChildFields(this.fields());
|
|
2720
2931
|
this.schemaName.set(value.title || 'New Schema');
|
|
2721
|
-
|
|
2932
|
+
if (!hasUncommittedChanges) {
|
|
2933
|
+
this.fields.set(this.jsonSchemaToEditorFields(value));
|
|
2934
|
+
}
|
|
2722
2935
|
}
|
|
2723
2936
|
}
|
|
2937
|
+
hasUncommittedChildFields(fields) {
|
|
2938
|
+
for (const field of fields) {
|
|
2939
|
+
if (field.children) {
|
|
2940
|
+
if (field.children.some(c => c.isEditing || c.isEditingDefault || c.isEditingValues || !c.name)) {
|
|
2941
|
+
return true;
|
|
2942
|
+
}
|
|
2943
|
+
if (this.hasUncommittedChildFields(field.children)) {
|
|
2944
|
+
return true;
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
return false;
|
|
2949
|
+
}
|
|
2950
|
+
showJsonToggle = true;
|
|
2724
2951
|
schemaChange = new EventEmitter();
|
|
2725
2952
|
save = new EventEmitter();
|
|
2726
2953
|
schemaName = signal('New Schema', ...(ngDevMode ? [{ debugName: "schemaName" }] : []));
|
|
2727
2954
|
fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
|
|
2955
|
+
// View mode: 'visual' or 'json'
|
|
2956
|
+
viewMode = signal('visual', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
|
|
2957
|
+
jsonText = signal('', ...(ngDevMode ? [{ debugName: "jsonText" }] : []));
|
|
2958
|
+
jsonError = signal(null, ...(ngDevMode ? [{ debugName: "jsonError" }] : []));
|
|
2728
2959
|
fieldTypes = [
|
|
2729
2960
|
{ value: 'string', label: 'String', icon: 'text_fields' },
|
|
2730
2961
|
{ value: 'number', label: 'Number', icon: 'pin' },
|
|
@@ -2755,7 +2986,8 @@ class SchemaEditorComponent {
|
|
|
2755
2986
|
expanded: false,
|
|
2756
2987
|
};
|
|
2757
2988
|
this.fields.update(fields => [...fields, newField]);
|
|
2758
|
-
|
|
2989
|
+
// Don't emit change here - field has empty name and would be filtered out
|
|
2990
|
+
// Change will be emitted in stopEdit() when user provides a name
|
|
2759
2991
|
}
|
|
2760
2992
|
// Add a child field to an object or array
|
|
2761
2993
|
addChildField(parent) {
|
|
@@ -2771,7 +3003,8 @@ class SchemaEditorComponent {
|
|
|
2771
3003
|
parent.children.push(newField);
|
|
2772
3004
|
parent.expanded = true;
|
|
2773
3005
|
this.fields.update(fields => [...fields]);
|
|
2774
|
-
|
|
3006
|
+
// Don't emit change here - field has empty name and would be filtered out
|
|
3007
|
+
// Change will be emitted in stopEdit() when user provides a name
|
|
2775
3008
|
}
|
|
2776
3009
|
// Delete a field
|
|
2777
3010
|
deleteField(field, parentList) {
|
|
@@ -2853,21 +3086,43 @@ class SchemaEditorComponent {
|
|
|
2853
3086
|
}
|
|
2854
3087
|
// Toggle allowed values editor
|
|
2855
3088
|
toggleValuesEditor(field) {
|
|
3089
|
+
const wasEditingDefault = field.isEditingDefault;
|
|
2856
3090
|
field.isEditingValues = !field.isEditingValues;
|
|
2857
|
-
if (field.isEditingValues
|
|
2858
|
-
|
|
3091
|
+
if (field.isEditingValues) {
|
|
3092
|
+
// Close default editor when opening values editor
|
|
3093
|
+
field.isEditingDefault = false;
|
|
3094
|
+
if (!field.allowedValues) {
|
|
3095
|
+
field.allowedValues = [];
|
|
3096
|
+
}
|
|
2859
3097
|
}
|
|
2860
3098
|
this.fields.update(fields => [...fields]);
|
|
3099
|
+
// Emit change if we closed the default editor (to save any default value)
|
|
3100
|
+
if (wasEditingDefault) {
|
|
3101
|
+
this.emitChange();
|
|
3102
|
+
}
|
|
2861
3103
|
}
|
|
2862
3104
|
// Add allowed value
|
|
2863
3105
|
addAllowedValue(field, input) {
|
|
2864
|
-
|
|
3106
|
+
// Handle both direct input reference and event from button click
|
|
3107
|
+
let inputEl = null;
|
|
3108
|
+
if (input instanceof HTMLInputElement) {
|
|
3109
|
+
inputEl = input;
|
|
3110
|
+
}
|
|
3111
|
+
else {
|
|
3112
|
+
// Find the input by traversing up to .values-header and then querying for .value-input
|
|
3113
|
+
const target = input.target;
|
|
3114
|
+
const header = target.closest('.values-header');
|
|
3115
|
+
inputEl = header?.querySelector('.value-input');
|
|
3116
|
+
}
|
|
3117
|
+
if (!inputEl)
|
|
3118
|
+
return;
|
|
3119
|
+
const value = inputEl.value.trim();
|
|
2865
3120
|
if (value && !field.allowedValues?.includes(value)) {
|
|
2866
3121
|
if (!field.allowedValues) {
|
|
2867
3122
|
field.allowedValues = [];
|
|
2868
3123
|
}
|
|
2869
3124
|
field.allowedValues.push(value);
|
|
2870
|
-
|
|
3125
|
+
inputEl.value = '';
|
|
2871
3126
|
this.fields.update(fields => [...fields]);
|
|
2872
3127
|
this.emitChange();
|
|
2873
3128
|
}
|
|
@@ -2890,6 +3145,52 @@ class SchemaEditorComponent {
|
|
|
2890
3145
|
this.addAllowedValue(field, input);
|
|
2891
3146
|
}
|
|
2892
3147
|
}
|
|
3148
|
+
// Toggle default value editor
|
|
3149
|
+
toggleDefaultEditor(field) {
|
|
3150
|
+
const wasEditingValues = field.isEditingValues;
|
|
3151
|
+
field.isEditingDefault = !field.isEditingDefault;
|
|
3152
|
+
if (field.isEditingDefault) {
|
|
3153
|
+
// Close values editor when opening default editor
|
|
3154
|
+
field.isEditingValues = false;
|
|
3155
|
+
}
|
|
3156
|
+
this.fields.update(fields => [...fields]);
|
|
3157
|
+
// Emit change if we closed the values editor (to save any allowed values)
|
|
3158
|
+
if (wasEditingValues) {
|
|
3159
|
+
this.emitChange();
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
// Update default value
|
|
3163
|
+
onDefaultValueChange(field, value) {
|
|
3164
|
+
if (value === '') {
|
|
3165
|
+
field.defaultValue = undefined;
|
|
3166
|
+
}
|
|
3167
|
+
else if (field.type === 'number') {
|
|
3168
|
+
const num = parseFloat(value);
|
|
3169
|
+
field.defaultValue = isNaN(num) ? undefined : num;
|
|
3170
|
+
}
|
|
3171
|
+
else if (field.type === 'boolean') {
|
|
3172
|
+
field.defaultValue = value === 'true';
|
|
3173
|
+
}
|
|
3174
|
+
else {
|
|
3175
|
+
field.defaultValue = value;
|
|
3176
|
+
}
|
|
3177
|
+
this.fields.update(fields => [...fields]);
|
|
3178
|
+
this.emitChange();
|
|
3179
|
+
}
|
|
3180
|
+
// Clear default value
|
|
3181
|
+
clearDefaultValue(field) {
|
|
3182
|
+
field.defaultValue = undefined;
|
|
3183
|
+
field.isEditingDefault = false;
|
|
3184
|
+
this.fields.update(fields => [...fields]);
|
|
3185
|
+
this.emitChange();
|
|
3186
|
+
}
|
|
3187
|
+
// Handle Enter key in default value input
|
|
3188
|
+
onDefaultValueKeydown(event, field) {
|
|
3189
|
+
if (event.key === 'Enter' || event.key === 'Escape') {
|
|
3190
|
+
field.isEditingDefault = false;
|
|
3191
|
+
this.fields.update(fields => [...fields]);
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
2893
3194
|
// Handle keyboard events in field name input
|
|
2894
3195
|
onFieldNameKeydown(event, field) {
|
|
2895
3196
|
if (event.key === 'Enter') {
|
|
@@ -2918,6 +3219,15 @@ class SchemaEditorComponent {
|
|
|
2918
3219
|
this.emitChange();
|
|
2919
3220
|
}
|
|
2920
3221
|
}
|
|
3222
|
+
// Handle drag and drop reorder
|
|
3223
|
+
onFieldDrop(event) {
|
|
3224
|
+
if (event.previousIndex !== event.currentIndex) {
|
|
3225
|
+
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
|
|
3226
|
+
this.fields.update(fields => [...fields]);
|
|
3227
|
+
// Don't emit change here - it causes parent to reset expanded state
|
|
3228
|
+
// Order change will be captured on next edit or save
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
2921
3231
|
// Check if field can be indented (previous sibling must be object/array)
|
|
2922
3232
|
canIndent(field, parentList) {
|
|
2923
3233
|
const index = parentList.indexOf(field);
|
|
@@ -2941,9 +3251,11 @@ class SchemaEditorComponent {
|
|
|
2941
3251
|
prevSibling.children = [];
|
|
2942
3252
|
}
|
|
2943
3253
|
prevSibling.children.push(field);
|
|
3254
|
+
// Always expand the target (keep open if already open, open if closed)
|
|
2944
3255
|
prevSibling.expanded = true;
|
|
2945
3256
|
this.fields.update(fields => [...fields]);
|
|
2946
|
-
|
|
3257
|
+
// Don't emit change here - it causes parent to reset expanded state
|
|
3258
|
+
// Structure change will be captured on next edit or save
|
|
2947
3259
|
}
|
|
2948
3260
|
// Outdent field - move out of parent to grandparent level
|
|
2949
3261
|
outdentField(field, parentList, level) {
|
|
@@ -2964,7 +3276,8 @@ class SchemaEditorComponent {
|
|
|
2964
3276
|
const parentIndex = grandparentList.indexOf(parent);
|
|
2965
3277
|
grandparentList.splice(parentIndex + 1, 0, field);
|
|
2966
3278
|
this.fields.update(fields => [...fields]);
|
|
2967
|
-
|
|
3279
|
+
// Don't emit change here - it causes parent to reset expanded state
|
|
3280
|
+
// Structure change will be captured on next edit or save
|
|
2968
3281
|
}
|
|
2969
3282
|
// Find the parent field that contains a given list
|
|
2970
3283
|
findParentOfList(searchIn, targetList) {
|
|
@@ -3030,6 +3343,7 @@ class SchemaEditorComponent {
|
|
|
3030
3343
|
description: schema.description,
|
|
3031
3344
|
required: isRequired,
|
|
3032
3345
|
allowedValues: schema.enum,
|
|
3346
|
+
defaultValue: schema.default,
|
|
3033
3347
|
expanded: false,
|
|
3034
3348
|
};
|
|
3035
3349
|
if (schema.type === 'object' && schema.properties) {
|
|
@@ -3148,11 +3462,15 @@ class SchemaEditorComponent {
|
|
|
3148
3462
|
if (field.allowedValues && field.allowedValues.length > 0) {
|
|
3149
3463
|
schema['enum'] = field.allowedValues;
|
|
3150
3464
|
}
|
|
3465
|
+
// Add default value
|
|
3466
|
+
if (field.defaultValue !== undefined) {
|
|
3467
|
+
schema['default'] = field.defaultValue;
|
|
3468
|
+
}
|
|
3151
3469
|
return schema;
|
|
3152
3470
|
}
|
|
3153
3471
|
stripEditingState(fields) {
|
|
3154
3472
|
return fields.map(f => {
|
|
3155
|
-
const { isEditing, isEditingValues, ...rest } = f;
|
|
3473
|
+
const { isEditing, isEditingValues, isEditingDefault, ...rest } = f;
|
|
3156
3474
|
return {
|
|
3157
3475
|
...rest,
|
|
3158
3476
|
children: f.children ? this.stripEditingState(f.children) : undefined,
|
|
@@ -3163,8 +3481,56 @@ class SchemaEditorComponent {
|
|
|
3163
3481
|
trackByFieldId(index, field) {
|
|
3164
3482
|
return field.id;
|
|
3165
3483
|
}
|
|
3484
|
+
// --- JSON View Methods ---
|
|
3485
|
+
setViewMode(mode) {
|
|
3486
|
+
if (mode === 'json') {
|
|
3487
|
+
// Sync JSON text from current schema state
|
|
3488
|
+
this.jsonText.set(JSON.stringify(this.toJsonSchema(), null, 2));
|
|
3489
|
+
this.jsonError.set(null);
|
|
3490
|
+
}
|
|
3491
|
+
this.viewMode.set(mode);
|
|
3492
|
+
}
|
|
3493
|
+
onJsonTextChange(text) {
|
|
3494
|
+
this.jsonText.set(text);
|
|
3495
|
+
try {
|
|
3496
|
+
JSON.parse(text);
|
|
3497
|
+
this.jsonError.set(null);
|
|
3498
|
+
}
|
|
3499
|
+
catch (e) {
|
|
3500
|
+
this.jsonError.set(e.message);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
applyJsonChanges() {
|
|
3504
|
+
try {
|
|
3505
|
+
const parsed = JSON.parse(this.jsonText());
|
|
3506
|
+
// Update internal state from JSON
|
|
3507
|
+
this.schemaName.set(parsed.title || 'New Schema');
|
|
3508
|
+
this.fields.set(this.jsonSchemaToEditorFields(parsed));
|
|
3509
|
+
this.jsonError.set(null);
|
|
3510
|
+
this.emitChange();
|
|
3511
|
+
}
|
|
3512
|
+
catch (e) {
|
|
3513
|
+
this.jsonError.set(e.message);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
formatJson() {
|
|
3517
|
+
try {
|
|
3518
|
+
const parsed = JSON.parse(this.jsonText());
|
|
3519
|
+
this.jsonText.set(JSON.stringify(parsed, null, 2));
|
|
3520
|
+
this.jsonError.set(null);
|
|
3521
|
+
}
|
|
3522
|
+
catch (e) {
|
|
3523
|
+
this.jsonError.set(e.message);
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
copyJson() {
|
|
3527
|
+
const text = this.jsonText();
|
|
3528
|
+
navigator.clipboard.writeText(text).catch(err => {
|
|
3529
|
+
console.error('Failed to copy JSON to clipboard:', err);
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3166
3532
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3167
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaEditorComponent, isStandalone: true, selector: "schema-editor", inputs: { schema: "schema" }, outputs: { schemaChange: "schemaChange", save: "save" }, ngImport: i0, template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n <div class=\"schema-name-section\">\n <mat-form-field appearance=\"outline\" class=\"schema-name-field\">\n <mat-label>Schema Name</mat-label>\n <input\n #schemaNameInput\n matInput\n [value]=\"schemaName()\"\n (input)=\"onSchemaNameChange($any($event.target).value, schemaNameInput)\"\n placeholder=\"Enter schema name\"\n />\n </mat-form-field>\n </div>\n <div class=\"header-actions\">\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields List -->\n <div class=\"fields-container\">\n @if (fields().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>schema</mat-icon>\n <p>No fields yet. Click \"Add Field\" to get started.</p>\n </div>\n } @else {\n <div class=\"fields-list\">\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: fields(), level: 0, parentList: fields() }\"></ng-container>\n </div>\n }\n </div>\n</div>\n\n<!-- Recursive Field Template -->\n<ng-template #fieldListTemplate let-fields=\"fields\" let-level=\"level\" let-parentList=\"parentList\">\n @for (field of fields; track trackByFieldId($index, field); let i = $index; let first = $first; let last = $last) {\n <div\n class=\"field-item\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n [style.--level]=\"level\"\n >\n <!-- Reorder Buttons -->\n <div class=\"reorder-buttons\">\n <button\n class=\"reorder-btn\"\n [disabled]=\"first\"\n (click)=\"moveFieldUp(field, parentList)\"\n matTooltip=\"Move up\"\n >\n <mat-icon>keyboard_arrow_up</mat-icon>\n </button>\n <button\n class=\"reorder-btn\"\n [disabled]=\"last\"\n (click)=\"moveFieldDown(field, parentList)\"\n matTooltip=\"Move down\"\n >\n <mat-icon>keyboard_arrow_down</mat-icon>\n </button>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, parentList)\"\n (click)=\"indentField(field, parentList)\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n [disabled]=\"level === 0\"\n (click)=\"outdentField(field, parentList, level)\"\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n class=\"expand-btn\"\n (click)=\"toggleExpand(field)\"\n matTooltip=\"{{ field.expanded ? 'Collapse' : 'Expand' }}\"\n >\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Type Icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field Name -->\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required (click to make optional)' : 'Optional (click to make required)'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n\n <!-- Description Input -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n (click)=\"addChildField(field)\"\n matTooltip=\"Add child field\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values (' + field.allowedValues.length + ')' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>{{ field.allowedValues?.length ? 'list' : 'list' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"fieldMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #fieldMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, parentList)\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, parentList)\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n\n <!-- Allowed Values Editor -->\n @if (field.isEditingValues && (field.type === 'string' || field.type === 'number')) {\n <div class=\"allowed-values-editor\" [style.--level]=\"level\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #valueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, field, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(field, valueInput)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (field.allowedValues && field.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of field.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(field, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded) {\n <div class=\"nested-fields\" [style.--level]=\"level + 1\">\n @if (field.children && field.children.length > 0) {\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: field.children, level: level + 1, parentList: field.children }\"></ng-container>\n } @else {\n <div class=\"empty-nested\">\n <span>No child fields</span>\n <button mat-button (click)=\"addChildField(field)\" color=\"primary\">\n <mat-icon>add</mat-icon>\n Add field\n </button>\n </div>\n }\n </div>\n }\n }\n</ng-template>\n", styles: [":host{--schema-editor-bg: white;--schema-editor-border-radius: 12px;--schema-editor-shadow: 0 4px 20px rgba(0, 0, 0, .08);--schema-editor-border-color: #e2e8f0;--schema-editor-header-bg: white;--schema-editor-header-border: #e2e8f0;--schema-editor-field-bg: #f8fafc;--schema-editor-field-bg-hover: #f1f5f9;--schema-editor-field-bg-editing: #eff6ff;--schema-editor-field-bg-complex: #fefce8;--schema-editor-field-border-radius: 8px;--schema-editor-text-primary: #1e293b;--schema-editor-text-secondary: #64748b;--schema-editor-text-muted: #94a3b8;--schema-editor-accent-primary: #3b82f6;--schema-editor-accent-success: #22c55e;--schema-editor-accent-warning: #f59e0b;--schema-editor-accent-danger: #ef4444;--schema-editor-spacing-sm: 8px;--schema-editor-spacing-md: 16px;--schema-editor-spacing-lg: 24px;--schema-editor-font-size-sm: 12px;--schema-editor-font-size-md: 14px;--schema-editor-font-size-lg: 16px}.schema-editor{background:var(--schema-editor-bg);border-radius:var(--schema-editor-border-radius);box-shadow:var(--schema-editor-shadow);height:100%;display:flex;flex-direction:column;overflow:hidden}.editor-header{display:flex;align-items:center;justify-content:space-between;padding:var(--schema-editor-spacing-md) var(--schema-editor-spacing-lg);border-bottom:1px solid var(--schema-editor-border-color);gap:var(--schema-editor-spacing-md);flex-shrink:0;background:var(--schema-editor-header-bg)}.editor-header .schema-name-section{flex:1;max-width:400px}.editor-header .schema-name-field{width:100%}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.editor-header .header-actions{display:flex;gap:12px}.fields-container{flex:1;overflow-y:auto;padding:16px;min-height:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 24px;color:#94a3b8;text-align:center}.empty-state mat-icon{font-size:64px;width:64px;height:64px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:16px;margin:0}.fields-list{display:flex;flex-direction:column;gap:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;padding-left:calc(12px + var(--level, 0) * 24px);background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease}.field-item:hover{background:var(--schema-editor-field-bg-hover);border-color:var(--schema-editor-border-color)}.field-item.is-editing{background:var(--schema-editor-field-bg-editing);border-color:var(--schema-editor-accent-primary)}.field-item.is-complex{background:var(--schema-editor-field-bg-complex)}.field-item.is-complex:hover{background:#fef9c3}.field-item.cdk-drag-preview{box-shadow:0 4px 16px #00000026;border:1px solid var(--schema-editor-accent-primary)}.field-item.cdk-drag-placeholder{opacity:.3}.reorder-buttons{display:flex;flex-direction:column;gap:0}.reorder-buttons .reorder-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:14px;border-radius:3px;transition:all .15s ease}.reorder-buttons .reorder-btn:hover:not(:disabled){background:#e2e8f0;color:#3b82f6}.reorder-buttons .reorder-btn:disabled{opacity:.3;cursor:default}.reorder-buttons .reorder-btn mat-icon{font-size:16px;width:16px;height:16px}.indent-buttons{display:flex;flex-direction:row;gap:2px}.indent-buttons .indent-btn{background:none;border:none;padding:2px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:3px;transition:all .15s ease}.indent-buttons .indent-btn:hover:not(:disabled){background:#dbeafe;color:#2563eb}.indent-buttons .indent-btn:disabled{opacity:.3;cursor:default}.indent-buttons .indent-btn mat-icon{font-size:16px;width:16px;height:16px}.expand-btn{background:none;border:none;padding:4px;cursor:pointer;color:#64748b;border-radius:4px;display:flex;align-items:center;transition:all .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;cursor:pointer;padding:4px 8px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name:hover{background:#e2e8f0}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-name-input{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:2px solid #3b82f6;border-radius:6px;outline:none;background:#fff}.field-name-input::placeholder{color:#94a3b8;font-weight:400}.required-btn{background:none;border:none;padding:4px;cursor:pointer;color:#cbd5e1;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .15s ease;flex-shrink:0}.required-btn:hover{color:#f59e0b;background:#fef3c7}.required-btn.is-required{color:#f59e0b}.required-btn mat-icon{font-size:18px;width:18px;height:18px}.description-input{flex:1;font-size:13px;color:#64748b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;min-width:100px;transition:all .15s ease}.description-input:focus{border-color:#3b82f6;background:#fff}.description-input::placeholder{color:#94a3b8;font-style:italic}.type-selector{width:140px;flex-shrink:0}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important}.type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.field-actions{display:flex;align-items:center;gap:4px;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions{opacity:1}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.nested-fields{margin-left:calc(var(--level, 0) * 24px + 24px);padding-left:16px;border-left:2px solid #e2e8f0;display:flex;flex-direction:column;gap:4px;margin-top:4px;margin-bottom:8px}.empty-nested{display:flex;align-items:center;gap:12px;padding:12px 16px;color:#94a3b8;font-size:13px;font-style:italic;background:#f8fafc;border-radius:6px;border:1px dashed #e2e8f0}.allowed-values-editor{margin-left:calc(var(--level, 0) * 24px + 48px);margin-top:4px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px}.allowed-values-editor .values-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.allowed-values-editor .values-label{font-size:12px;font-weight:500;color:#166534}.allowed-values-editor .value-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #86efac;border-radius:4px;outline:none;background:#fff}.allowed-values-editor .value-input:focus{border-color:#22c55e}.allowed-values-editor .value-input::placeholder{color:#94a3b8}.allowed-values-editor .add-value-btn{background:#22c55e;border:none;color:#fff;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.allowed-values-editor .add-value-btn:hover{background:#16a34a}.allowed-values-editor .add-value-btn mat-icon{font-size:18px;width:18px;height:18px}.allowed-values-editor .values-list{display:flex;flex-wrap:wrap;gap:6px}.allowed-values-editor .value-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#fff;border:1px solid #86efac;border-radius:4px;font-size:12px;color:#166534}.allowed-values-editor .value-chip .remove-value-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.allowed-values-editor .value-chip .remove-value-btn:hover{color:#ef4444}.allowed-values-editor .value-chip .remove-value-btn mat-icon{font-size:14px;width:14px;height:14px}.allowed-values-editor .no-values{font-size:12px;color:#94a3b8;font-style:italic}.field-actions .has-values{color:#22c55e!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7$2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: DragDropModule }] });
|
|
3533
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaEditorComponent, isStandalone: true, selector: "schema-editor", inputs: { schema: "schema", showJsonToggle: "showJsonToggle" }, outputs: { schemaChange: "schemaChange", save: "save" }, ngImport: i0, template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n <div class=\"schema-name-section\">\n <mat-form-field appearance=\"outline\" class=\"schema-name-field\">\n <mat-label>Schema Name</mat-label>\n <input\n #schemaNameInput\n matInput\n [value]=\"schemaName()\"\n (input)=\"onSchemaNameChange($any($event.target).value, schemaNameInput)\"\n placeholder=\"Enter schema name\"\n />\n </mat-form-field>\n </div>\n\n @if (showJsonToggle) {\n <mat-button-toggle-group [value]=\"viewMode()\" (change)=\"setViewMode($event.value)\" class=\"view-toggle\">\n <mat-button-toggle value=\"visual\">Visual</mat-button-toggle>\n <mat-button-toggle value=\"json\">JSON</mat-button-toggle>\n </mat-button-toggle-group>\n }\n\n <div class=\"header-actions\">\n @if (viewMode() === 'visual') {\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n } @else {\n <button mat-stroked-button (click)=\"copyJson()\" matTooltip=\"Copy JSON to clipboard\">\n <mat-icon>content_copy</mat-icon>\n Copy\n </button>\n <button mat-stroked-button (click)=\"formatJson()\">\n <mat-icon>auto_fix_high</mat-icon>\n Format\n </button>\n <button mat-flat-button color=\"primary\" (click)=\"applyJsonChanges()\" [disabled]=\"jsonError()\">\n <mat-icon>check</mat-icon>\n Apply\n </button>\n }\n </div>\n </div>\n\n <!-- JSON View -->\n @if (viewMode() === 'json') {\n <div class=\"json-view\">\n @if (jsonError()) {\n <div class=\"json-error\">\n <mat-icon>error</mat-icon>\n {{ jsonError() }}\n </div>\n }\n <textarea\n class=\"json-textarea\"\n [value]=\"jsonText()\"\n (input)=\"onJsonTextChange($any($event.target).value)\"\n spellcheck=\"false\"\n ></textarea>\n </div>\n }\n\n <!-- Fields List (Visual View) -->\n <div class=\"fields-container\" [class.hidden]=\"viewMode() === 'json'\">\n @if (fields().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>schema</mat-icon>\n <p>No fields yet. Click \"Add Field\" to get started.</p>\n </div>\n } @else {\n <div\n class=\"fields-list\"\n cdkDropList\n [cdkDropListData]=\"fields()\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @for (field of fields(); track field.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(field.type) }}</mat-icon>\n {{ field.name || 'unnamed' }}\n </div>\n\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <!-- Drag Handle -->\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, fields())\"\n (click)=\"indentField(field, fields())\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n disabled\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n class=\"expand-btn\"\n (click)=\"toggleExpand(field)\"\n [matTooltip]=\"field.expanded ? 'Collapse' : 'Expand'\"\n >\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Type Icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field Name -->\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required (click to make optional)' : 'Optional (click to make required)'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n (click)=\"addChildField(field)\"\n matTooltip=\"Add child field\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values (' + field.allowedValues!.length + ')' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"fieldMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #fieldMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, fields())\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, fields())\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n </div>\n\n <!-- Allowed Values Editor -->\n @if (field.isEditingValues && (field.type === 'string' || field.type === 'number')) {\n <div class=\"allowed-values-editor\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #valueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, field, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(field, $event)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (field.allowedValues && field.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of field.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(field, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Default Value Editor -->\n @if (field.isEditingDefault && field.type !== 'object' && field.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (field.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"field.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(field, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"field.type === 'number' ? 'number' : 'text'\"\n [value]=\"field.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(field, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, field)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (field.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(field)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded && field.children) {\n <div\n class=\"nested-fields\"\n cdkDropList\n [cdkDropListData]=\"field.children\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @if (field.children.length > 0) {\n @for (child of field.children; track child.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(child.type) }}</mat-icon>\n {{ child.name || 'unnamed' }}\n </div>\n <ng-container *ngTemplateOutlet=\"fieldItemTemplate; context: { field: child, parentList: field.children, level: 1 }\"></ng-container>\n\n <!-- Allowed Values Editor for nested field -->\n @if (child.isEditingValues && (child.type === 'string' || child.type === 'number')) {\n <div class=\"allowed-values-editor\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #childValueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, child, childValueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(child, $event)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (child.allowedValues && child.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of child.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(child, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Default Value Editor for nested field -->\n @if (child.isEditingDefault && child.type !== 'object' && child.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (child.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"child.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(child, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"child.type === 'number' ? 'number' : 'text'\"\n [value]=\"child.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(child, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, child)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (child.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(child)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"empty-nested\">\n <span>No child fields</span>\n <button mat-button (click)=\"addChildField(field)\" color=\"primary\">\n <mat-icon>add</mat-icon>\n Add field\n </button>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- Field Item Template (for nested fields) -->\n<ng-template #fieldItemTemplate let-field=\"field\" let-parentList=\"parentList\" let-level=\"level\">\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, parentList)\"\n (click)=\"indentField(field, parentList)\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n (click)=\"outdentField(field, parentList, level)\"\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n @if (field.type === 'object' || field.type === 'array') {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field)\" [matTooltip]=\"field.expanded ? 'Collapse' : 'Expand'\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required' : 'Optional'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button mat-icon-button (click)=\"addChildField(field)\" matTooltip=\"Add child field\">\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"nestedMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #nestedMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, parentList)\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, parentList)\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [":host{--schema-editor-bg: white;--schema-editor-border-radius: 12px;--schema-editor-shadow: 0 4px 20px rgba(0, 0, 0, .08);--schema-editor-border-color: #e2e8f0;--schema-editor-header-bg: white;--schema-editor-header-border: #e2e8f0;--schema-editor-field-bg: #f8fafc;--schema-editor-field-bg-hover: #f1f5f9;--schema-editor-field-bg-editing: #eff6ff;--schema-editor-field-bg-complex: #fefce8;--schema-editor-field-border-radius: 8px;--schema-editor-text-primary: #1e293b;--schema-editor-text-secondary: #64748b;--schema-editor-text-muted: #94a3b8;--schema-editor-accent-primary: #3b82f6;--schema-editor-accent-success: #22c55e;--schema-editor-accent-warning: #f59e0b;--schema-editor-accent-danger: #ef4444;--schema-editor-spacing-sm: 8px;--schema-editor-spacing-md: 16px;--schema-editor-spacing-lg: 24px;--schema-editor-font-size-sm: 12px;--schema-editor-font-size-md: 14px;--schema-editor-font-size-lg: 16px}.schema-editor{background:var(--schema-editor-bg);border-radius:var(--schema-editor-border-radius);box-shadow:var(--schema-editor-shadow);height:100%;display:flex;flex-direction:column;overflow:hidden}.editor-header{display:flex;align-items:center;justify-content:space-between;padding:var(--schema-editor-spacing-md) var(--schema-editor-spacing-lg);border-bottom:1px solid var(--schema-editor-border-color);gap:var(--schema-editor-spacing-md);flex-shrink:0;background:var(--schema-editor-bg)}.editor-header .schema-name-section{flex:1;max-width:400px}.editor-header .schema-name-field{width:100%}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.editor-header .schema-name-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-infix{padding-top:8px;padding-bottom:8px;min-height:36px}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__leading,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__trailing{border-color:var(--schema-editor-border-color)!important}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch{border-right:none}.editor-header .schema-name-field ::ng-deep input.mat-mdc-input-element{font-size:14px}.editor-header .schema-name-field ::ng-deep .mat-mdc-floating-label{font-size:13px}.editor-header .header-actions{display:flex;gap:12px}.editor-header .header-actions button{display:flex;align-items:center;gap:6px}.editor-header .view-toggle{border:1px solid var(--schema-editor-border-color);border-radius:8px;overflow:hidden}.editor-header .view-toggle ::ng-deep .mat-button-toggle{background:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle.mat-button-toggle-checked{background:var(--schema-editor-accent-primary);color:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle .mat-button-toggle-label-content{padding:0 16px;font-size:13px;font-weight:500;line-height:32px}.json-view{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.json-error{display:flex;align-items:center;gap:8px;padding:10px 16px;background:#fef2f2;border-bottom:1px solid #fecaca;color:#dc2626;font-size:12px;flex-shrink:0}.json-error mat-icon{font-size:16px;width:16px;height:16px}.json-textarea{flex:1;width:100%;padding:16px;border:none;outline:none;resize:none;font-family:SF Mono,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:13px;line-height:1.6;color:var(--schema-editor-text-primary);background:var(--schema-editor-bg);tab-size:2;box-sizing:border-box}.json-textarea:focus{outline:none}.hidden{display:none!important}.fields-container{flex:1;overflow-y:auto;padding:16px;min-height:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 24px;color:#94a3b8;text-align:center}.empty-state mat-icon{font-size:64px;width:64px;height:64px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:16px;margin:0}.fields-list{display:block;width:100%;min-height:50px}.fields-list .field-wrapper{margin-bottom:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease;box-sizing:border-box}.field-item:hover{background:var(--schema-editor-field-bg-hover);border-color:var(--schema-editor-border-color)}.field-item.is-editing{background:var(--schema-editor-field-bg-editing);border-color:var(--schema-editor-accent-primary)}.field-item.is-complex{background:var(--schema-editor-field-bg-complex)}.field-item.is-complex:hover{background:#fef9c3}.field-left{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0}.field-right{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0;margin-left:auto}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:grab;color:#94a3b8;padding:4px;border-radius:4px;transition:all .15s ease}.drag-handle:hover{background:#e2e8f0;color:#64748b}.drag-handle:active{cursor:grabbing}.drag-handle mat-icon{font-size:20px;width:20px;height:20px}.field-wrapper{display:block;width:100%;box-sizing:border-box}::ng-deep .drag-preview{display:flex!important;align-items:center!important;gap:8px!important;padding:12px 16px!important;background:#fff!important;border:2px solid #3b82f6!important;border-radius:8px!important;box-shadow:0 8px 24px #0003!important;font-size:14px!important;font-weight:500!important;color:#1e293b!important;overflow:hidden!important;white-space:nowrap!important;box-sizing:border-box!important}::ng-deep .drag-preview mat-icon{color:#3b82f6!important;font-size:20px!important;width:20px!important;height:20px!important;flex-shrink:0!important;overflow:hidden!important}.drag-placeholder{background:#e2e8f0;border:2px dashed var(--schema-editor-accent-primary);border-radius:var(--schema-editor-field-border-radius);min-height:48px;margin-bottom:4px}.drag-placeholder .placeholder-content{height:100%;min-height:48px}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.cdk-drop-list-dragging .field-wrapper:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.indent-buttons{display:flex;flex-direction:row;gap:2px}.indent-buttons .indent-btn{background:none;border:none;padding:2px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:3px;transition:all .15s ease}.indent-buttons .indent-btn:hover:not(:disabled){background:#dbeafe;color:#2563eb}.indent-buttons .indent-btn:disabled{opacity:.3;cursor:default}.indent-buttons .indent-btn mat-icon{font-size:16px;width:16px;height:16px}.expand-btn{background:none;border:none;padding:4px;cursor:pointer;color:#64748b;border-radius:4px;display:flex;align-items:center;transition:all .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;cursor:pointer;padding:4px 8px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name:hover{background:#e2e8f0}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-name-input{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:2px solid #3b82f6;border-radius:6px;outline:none;background:#fff}.field-name-input::placeholder{color:#94a3b8;font-weight:400}.required-btn{background:none;border:none;padding:4px;cursor:pointer;color:#cbd5e1;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .15s ease;flex-shrink:0}.required-btn:hover{color:#f59e0b;background:#fef3c7}.required-btn.is-required{color:#f59e0b}.required-btn mat-icon{font-size:18px;width:18px;height:18px}.description-input{flex:1;font-size:13px;color:#64748b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;min-width:80px;max-width:100%;transition:all .15s ease}.description-input:focus{border-color:#3b82f6;background:#fff}.description-input::placeholder{color:#94a3b8;font-style:italic}.type-selector{width:140px;flex-shrink:0}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important}.type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.field-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;width:128px;flex-shrink:0;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions,.field-right:hover .field-actions{opacity:1}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.nested-fields{display:block;min-height:30px;margin-top:4px;margin-bottom:8px;padding-left:32px}.nested-fields .field-wrapper{margin-bottom:4px}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor{margin-left:16px}.empty-nested{display:flex;align-items:center;gap:12px;padding:12px 16px;color:#94a3b8;font-size:13px;font-style:italic;background:#f8fafc;border-radius:6px;border:1px dashed #e2e8f0}.allowed-values-editor{margin-left:48px;margin-top:4px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px}.allowed-values-editor .values-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.allowed-values-editor .values-label{font-size:12px;font-weight:500;color:#166534}.allowed-values-editor .value-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #86efac;border-radius:4px;outline:none;background:#fff}.allowed-values-editor .value-input:focus{border-color:#22c55e}.allowed-values-editor .value-input::placeholder{color:#94a3b8}.allowed-values-editor .add-value-btn{background:#22c55e;border:none;color:#fff;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.allowed-values-editor .add-value-btn:hover{background:#16a34a}.allowed-values-editor .add-value-btn mat-icon{font-size:18px;width:18px;height:18px}.allowed-values-editor .values-list{display:flex;flex-wrap:wrap;gap:6px}.allowed-values-editor .value-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#fff;border:1px solid #86efac;border-radius:4px;font-size:12px;color:#166534}.allowed-values-editor .value-chip .remove-value-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.allowed-values-editor .value-chip .remove-value-btn:hover{color:#ef4444}.allowed-values-editor .value-chip .remove-value-btn mat-icon{font-size:14px;width:14px;height:14px}.allowed-values-editor .no-values{font-size:12px;color:#94a3b8;font-style:italic}.field-actions .has-values{color:#22c55e!important}.field-actions .has-default{color:#8b5cf6!important}.default-value-editor{margin-left:48px;margin-top:4px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px}.default-value-editor .default-header{display:flex;align-items:center;gap:8px}.default-value-editor .default-label{font-size:12px;font-weight:500;color:#6d28d9}.default-value-editor .default-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;max-width:200px}.default-value-editor .default-input:focus{border-color:#8b5cf6}.default-value-editor .default-input::placeholder{color:#94a3b8}.default-value-editor .default-select{padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;cursor:pointer}.default-value-editor .default-select:focus{border-color:#8b5cf6}.default-value-editor .clear-default-btn{background:none;border:none;padding:4px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.default-value-editor .clear-default-btn:hover{color:#ef4444}.default-value-editor .clear-default-btn mat-icon{font-size:16px;width:16px;height:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i8$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i8$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i9$1.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i9$1.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i10.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i10.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }] });
|
|
3168
3534
|
}
|
|
3169
3535
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, decorators: [{
|
|
3170
3536
|
type: Component,
|
|
@@ -3178,10 +3544,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
3178
3544
|
MatSelectModule,
|
|
3179
3545
|
MatTooltipModule,
|
|
3180
3546
|
MatMenuModule,
|
|
3547
|
+
MatButtonToggleModule,
|
|
3181
3548
|
DragDropModule,
|
|
3182
|
-
], template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n <div class=\"schema-name-section\">\n <mat-form-field appearance=\"outline\" class=\"schema-name-field\">\n <mat-label>Schema Name</mat-label>\n <input\n #schemaNameInput\n matInput\n [value]=\"schemaName()\"\n (input)=\"onSchemaNameChange($any($event.target).value, schemaNameInput)\"\n placeholder=\"Enter schema name\"\n />\n </mat-form-field>\n </div>\n <div class=\"header-actions\">\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields List -->\n <div class=\"fields-container\">\n @if (fields().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>schema</mat-icon>\n <p>No fields yet. Click \"Add Field\" to get started.</p>\n </div>\n } @else {\n <div class=\"fields-list\">\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: fields(), level: 0, parentList: fields() }\"></ng-container>\n </div>\n }\n </div>\n</div>\n\n<!-- Recursive Field Template -->\n<ng-template #fieldListTemplate let-fields=\"fields\" let-level=\"level\" let-parentList=\"parentList\">\n @for (field of fields; track trackByFieldId($index, field); let i = $index; let first = $first; let last = $last) {\n <div\n class=\"field-item\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n [style.--level]=\"level\"\n >\n <!-- Reorder Buttons -->\n <div class=\"reorder-buttons\">\n <button\n class=\"reorder-btn\"\n [disabled]=\"first\"\n (click)=\"moveFieldUp(field, parentList)\"\n matTooltip=\"Move up\"\n >\n <mat-icon>keyboard_arrow_up</mat-icon>\n </button>\n <button\n class=\"reorder-btn\"\n [disabled]=\"last\"\n (click)=\"moveFieldDown(field, parentList)\"\n matTooltip=\"Move down\"\n >\n <mat-icon>keyboard_arrow_down</mat-icon>\n </button>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, parentList)\"\n (click)=\"indentField(field, parentList)\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n [disabled]=\"level === 0\"\n (click)=\"outdentField(field, parentList, level)\"\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n class=\"expand-btn\"\n (click)=\"toggleExpand(field)\"\n matTooltip=\"{{ field.expanded ? 'Collapse' : 'Expand' }}\"\n >\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Type Icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field Name -->\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required (click to make optional)' : 'Optional (click to make required)'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n\n <!-- Description Input -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n (click)=\"addChildField(field)\"\n matTooltip=\"Add child field\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values (' + field.allowedValues.length + ')' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>{{ field.allowedValues?.length ? 'list' : 'list' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"fieldMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #fieldMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, parentList)\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, parentList)\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n\n <!-- Allowed Values Editor -->\n @if (field.isEditingValues && (field.type === 'string' || field.type === 'number')) {\n <div class=\"allowed-values-editor\" [style.--level]=\"level\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #valueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, field, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(field, valueInput)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (field.allowedValues && field.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of field.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(field, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded) {\n <div class=\"nested-fields\" [style.--level]=\"level + 1\">\n @if (field.children && field.children.length > 0) {\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: field.children, level: level + 1, parentList: field.children }\"></ng-container>\n } @else {\n <div class=\"empty-nested\">\n <span>No child fields</span>\n <button mat-button (click)=\"addChildField(field)\" color=\"primary\">\n <mat-icon>add</mat-icon>\n Add field\n </button>\n </div>\n }\n </div>\n }\n }\n</ng-template>\n", styles: [":host{--schema-editor-bg: white;--schema-editor-border-radius: 12px;--schema-editor-shadow: 0 4px 20px rgba(0, 0, 0, .08);--schema-editor-border-color: #e2e8f0;--schema-editor-header-bg: white;--schema-editor-header-border: #e2e8f0;--schema-editor-field-bg: #f8fafc;--schema-editor-field-bg-hover: #f1f5f9;--schema-editor-field-bg-editing: #eff6ff;--schema-editor-field-bg-complex: #fefce8;--schema-editor-field-border-radius: 8px;--schema-editor-text-primary: #1e293b;--schema-editor-text-secondary: #64748b;--schema-editor-text-muted: #94a3b8;--schema-editor-accent-primary: #3b82f6;--schema-editor-accent-success: #22c55e;--schema-editor-accent-warning: #f59e0b;--schema-editor-accent-danger: #ef4444;--schema-editor-spacing-sm: 8px;--schema-editor-spacing-md: 16px;--schema-editor-spacing-lg: 24px;--schema-editor-font-size-sm: 12px;--schema-editor-font-size-md: 14px;--schema-editor-font-size-lg: 16px}.schema-editor{background:var(--schema-editor-bg);border-radius:var(--schema-editor-border-radius);box-shadow:var(--schema-editor-shadow);height:100%;display:flex;flex-direction:column;overflow:hidden}.editor-header{display:flex;align-items:center;justify-content:space-between;padding:var(--schema-editor-spacing-md) var(--schema-editor-spacing-lg);border-bottom:1px solid var(--schema-editor-border-color);gap:var(--schema-editor-spacing-md);flex-shrink:0;background:var(--schema-editor-header-bg)}.editor-header .schema-name-section{flex:1;max-width:400px}.editor-header .schema-name-field{width:100%}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.editor-header .header-actions{display:flex;gap:12px}.fields-container{flex:1;overflow-y:auto;padding:16px;min-height:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 24px;color:#94a3b8;text-align:center}.empty-state mat-icon{font-size:64px;width:64px;height:64px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:16px;margin:0}.fields-list{display:flex;flex-direction:column;gap:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;padding-left:calc(12px + var(--level, 0) * 24px);background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease}.field-item:hover{background:var(--schema-editor-field-bg-hover);border-color:var(--schema-editor-border-color)}.field-item.is-editing{background:var(--schema-editor-field-bg-editing);border-color:var(--schema-editor-accent-primary)}.field-item.is-complex{background:var(--schema-editor-field-bg-complex)}.field-item.is-complex:hover{background:#fef9c3}.field-item.cdk-drag-preview{box-shadow:0 4px 16px #00000026;border:1px solid var(--schema-editor-accent-primary)}.field-item.cdk-drag-placeholder{opacity:.3}.reorder-buttons{display:flex;flex-direction:column;gap:0}.reorder-buttons .reorder-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:14px;border-radius:3px;transition:all .15s ease}.reorder-buttons .reorder-btn:hover:not(:disabled){background:#e2e8f0;color:#3b82f6}.reorder-buttons .reorder-btn:disabled{opacity:.3;cursor:default}.reorder-buttons .reorder-btn mat-icon{font-size:16px;width:16px;height:16px}.indent-buttons{display:flex;flex-direction:row;gap:2px}.indent-buttons .indent-btn{background:none;border:none;padding:2px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:3px;transition:all .15s ease}.indent-buttons .indent-btn:hover:not(:disabled){background:#dbeafe;color:#2563eb}.indent-buttons .indent-btn:disabled{opacity:.3;cursor:default}.indent-buttons .indent-btn mat-icon{font-size:16px;width:16px;height:16px}.expand-btn{background:none;border:none;padding:4px;cursor:pointer;color:#64748b;border-radius:4px;display:flex;align-items:center;transition:all .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;cursor:pointer;padding:4px 8px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name:hover{background:#e2e8f0}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-name-input{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:2px solid #3b82f6;border-radius:6px;outline:none;background:#fff}.field-name-input::placeholder{color:#94a3b8;font-weight:400}.required-btn{background:none;border:none;padding:4px;cursor:pointer;color:#cbd5e1;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .15s ease;flex-shrink:0}.required-btn:hover{color:#f59e0b;background:#fef3c7}.required-btn.is-required{color:#f59e0b}.required-btn mat-icon{font-size:18px;width:18px;height:18px}.description-input{flex:1;font-size:13px;color:#64748b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;min-width:100px;transition:all .15s ease}.description-input:focus{border-color:#3b82f6;background:#fff}.description-input::placeholder{color:#94a3b8;font-style:italic}.type-selector{width:140px;flex-shrink:0}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important}.type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.field-actions{display:flex;align-items:center;gap:4px;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions{opacity:1}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.nested-fields{margin-left:calc(var(--level, 0) * 24px + 24px);padding-left:16px;border-left:2px solid #e2e8f0;display:flex;flex-direction:column;gap:4px;margin-top:4px;margin-bottom:8px}.empty-nested{display:flex;align-items:center;gap:12px;padding:12px 16px;color:#94a3b8;font-size:13px;font-style:italic;background:#f8fafc;border-radius:6px;border:1px dashed #e2e8f0}.allowed-values-editor{margin-left:calc(var(--level, 0) * 24px + 48px);margin-top:4px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px}.allowed-values-editor .values-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.allowed-values-editor .values-label{font-size:12px;font-weight:500;color:#166534}.allowed-values-editor .value-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #86efac;border-radius:4px;outline:none;background:#fff}.allowed-values-editor .value-input:focus{border-color:#22c55e}.allowed-values-editor .value-input::placeholder{color:#94a3b8}.allowed-values-editor .add-value-btn{background:#22c55e;border:none;color:#fff;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.allowed-values-editor .add-value-btn:hover{background:#16a34a}.allowed-values-editor .add-value-btn mat-icon{font-size:18px;width:18px;height:18px}.allowed-values-editor .values-list{display:flex;flex-wrap:wrap;gap:6px}.allowed-values-editor .value-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#fff;border:1px solid #86efac;border-radius:4px;font-size:12px;color:#166534}.allowed-values-editor .value-chip .remove-value-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.allowed-values-editor .value-chip .remove-value-btn:hover{color:#ef4444}.allowed-values-editor .value-chip .remove-value-btn mat-icon{font-size:14px;width:14px;height:14px}.allowed-values-editor .no-values{font-size:12px;color:#94a3b8;font-style:italic}.field-actions .has-values{color:#22c55e!important}\n"] }]
|
|
3549
|
+
], template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n <div class=\"schema-name-section\">\n <mat-form-field appearance=\"outline\" class=\"schema-name-field\">\n <mat-label>Schema Name</mat-label>\n <input\n #schemaNameInput\n matInput\n [value]=\"schemaName()\"\n (input)=\"onSchemaNameChange($any($event.target).value, schemaNameInput)\"\n placeholder=\"Enter schema name\"\n />\n </mat-form-field>\n </div>\n\n @if (showJsonToggle) {\n <mat-button-toggle-group [value]=\"viewMode()\" (change)=\"setViewMode($event.value)\" class=\"view-toggle\">\n <mat-button-toggle value=\"visual\">Visual</mat-button-toggle>\n <mat-button-toggle value=\"json\">JSON</mat-button-toggle>\n </mat-button-toggle-group>\n }\n\n <div class=\"header-actions\">\n @if (viewMode() === 'visual') {\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n } @else {\n <button mat-stroked-button (click)=\"copyJson()\" matTooltip=\"Copy JSON to clipboard\">\n <mat-icon>content_copy</mat-icon>\n Copy\n </button>\n <button mat-stroked-button (click)=\"formatJson()\">\n <mat-icon>auto_fix_high</mat-icon>\n Format\n </button>\n <button mat-flat-button color=\"primary\" (click)=\"applyJsonChanges()\" [disabled]=\"jsonError()\">\n <mat-icon>check</mat-icon>\n Apply\n </button>\n }\n </div>\n </div>\n\n <!-- JSON View -->\n @if (viewMode() === 'json') {\n <div class=\"json-view\">\n @if (jsonError()) {\n <div class=\"json-error\">\n <mat-icon>error</mat-icon>\n {{ jsonError() }}\n </div>\n }\n <textarea\n class=\"json-textarea\"\n [value]=\"jsonText()\"\n (input)=\"onJsonTextChange($any($event.target).value)\"\n spellcheck=\"false\"\n ></textarea>\n </div>\n }\n\n <!-- Fields List (Visual View) -->\n <div class=\"fields-container\" [class.hidden]=\"viewMode() === 'json'\">\n @if (fields().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>schema</mat-icon>\n <p>No fields yet. Click \"Add Field\" to get started.</p>\n </div>\n } @else {\n <div\n class=\"fields-list\"\n cdkDropList\n [cdkDropListData]=\"fields()\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @for (field of fields(); track field.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(field.type) }}</mat-icon>\n {{ field.name || 'unnamed' }}\n </div>\n\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <!-- Drag Handle -->\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, fields())\"\n (click)=\"indentField(field, fields())\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n disabled\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n class=\"expand-btn\"\n (click)=\"toggleExpand(field)\"\n [matTooltip]=\"field.expanded ? 'Collapse' : 'Expand'\"\n >\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Type Icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field Name -->\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required (click to make optional)' : 'Optional (click to make required)'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n (click)=\"addChildField(field)\"\n matTooltip=\"Add child field\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values (' + field.allowedValues!.length + ')' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"fieldMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #fieldMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, fields())\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, fields())\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n </div>\n\n <!-- Allowed Values Editor -->\n @if (field.isEditingValues && (field.type === 'string' || field.type === 'number')) {\n <div class=\"allowed-values-editor\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #valueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, field, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(field, $event)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (field.allowedValues && field.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of field.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(field, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Default Value Editor -->\n @if (field.isEditingDefault && field.type !== 'object' && field.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (field.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"field.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(field, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"field.type === 'number' ? 'number' : 'text'\"\n [value]=\"field.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(field, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, field)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (field.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(field)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded && field.children) {\n <div\n class=\"nested-fields\"\n cdkDropList\n [cdkDropListData]=\"field.children\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @if (field.children.length > 0) {\n @for (child of field.children; track child.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(child.type) }}</mat-icon>\n {{ child.name || 'unnamed' }}\n </div>\n <ng-container *ngTemplateOutlet=\"fieldItemTemplate; context: { field: child, parentList: field.children, level: 1 }\"></ng-container>\n\n <!-- Allowed Values Editor for nested field -->\n @if (child.isEditingValues && (child.type === 'string' || child.type === 'number')) {\n <div class=\"allowed-values-editor\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #childValueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, child, childValueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(child, $event)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (child.allowedValues && child.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of child.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(child, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Default Value Editor for nested field -->\n @if (child.isEditingDefault && child.type !== 'object' && child.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (child.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"child.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(child, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"child.type === 'number' ? 'number' : 'text'\"\n [value]=\"child.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(child, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, child)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (child.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(child)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"empty-nested\">\n <span>No child fields</span>\n <button mat-button (click)=\"addChildField(field)\" color=\"primary\">\n <mat-icon>add</mat-icon>\n Add field\n </button>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- Field Item Template (for nested fields) -->\n<ng-template #fieldItemTemplate let-field=\"field\" let-parentList=\"parentList\" let-level=\"level\">\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, parentList)\"\n (click)=\"indentField(field, parentList)\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n (click)=\"outdentField(field, parentList, level)\"\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n @if (field.type === 'object' || field.type === 'array') {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field)\" [matTooltip]=\"field.expanded ? 'Collapse' : 'Expand'\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required' : 'Optional'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button mat-icon-button (click)=\"addChildField(field)\" matTooltip=\"Add child field\">\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"nestedMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #nestedMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, parentList)\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, parentList)\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [":host{--schema-editor-bg: white;--schema-editor-border-radius: 12px;--schema-editor-shadow: 0 4px 20px rgba(0, 0, 0, .08);--schema-editor-border-color: #e2e8f0;--schema-editor-header-bg: white;--schema-editor-header-border: #e2e8f0;--schema-editor-field-bg: #f8fafc;--schema-editor-field-bg-hover: #f1f5f9;--schema-editor-field-bg-editing: #eff6ff;--schema-editor-field-bg-complex: #fefce8;--schema-editor-field-border-radius: 8px;--schema-editor-text-primary: #1e293b;--schema-editor-text-secondary: #64748b;--schema-editor-text-muted: #94a3b8;--schema-editor-accent-primary: #3b82f6;--schema-editor-accent-success: #22c55e;--schema-editor-accent-warning: #f59e0b;--schema-editor-accent-danger: #ef4444;--schema-editor-spacing-sm: 8px;--schema-editor-spacing-md: 16px;--schema-editor-spacing-lg: 24px;--schema-editor-font-size-sm: 12px;--schema-editor-font-size-md: 14px;--schema-editor-font-size-lg: 16px}.schema-editor{background:var(--schema-editor-bg);border-radius:var(--schema-editor-border-radius);box-shadow:var(--schema-editor-shadow);height:100%;display:flex;flex-direction:column;overflow:hidden}.editor-header{display:flex;align-items:center;justify-content:space-between;padding:var(--schema-editor-spacing-md) var(--schema-editor-spacing-lg);border-bottom:1px solid var(--schema-editor-border-color);gap:var(--schema-editor-spacing-md);flex-shrink:0;background:var(--schema-editor-bg)}.editor-header .schema-name-section{flex:1;max-width:400px}.editor-header .schema-name-field{width:100%}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.editor-header .schema-name-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-infix{padding-top:8px;padding-bottom:8px;min-height:36px}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__leading,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__trailing{border-color:var(--schema-editor-border-color)!important}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch{border-right:none}.editor-header .schema-name-field ::ng-deep input.mat-mdc-input-element{font-size:14px}.editor-header .schema-name-field ::ng-deep .mat-mdc-floating-label{font-size:13px}.editor-header .header-actions{display:flex;gap:12px}.editor-header .header-actions button{display:flex;align-items:center;gap:6px}.editor-header .view-toggle{border:1px solid var(--schema-editor-border-color);border-radius:8px;overflow:hidden}.editor-header .view-toggle ::ng-deep .mat-button-toggle{background:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle.mat-button-toggle-checked{background:var(--schema-editor-accent-primary);color:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle .mat-button-toggle-label-content{padding:0 16px;font-size:13px;font-weight:500;line-height:32px}.json-view{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.json-error{display:flex;align-items:center;gap:8px;padding:10px 16px;background:#fef2f2;border-bottom:1px solid #fecaca;color:#dc2626;font-size:12px;flex-shrink:0}.json-error mat-icon{font-size:16px;width:16px;height:16px}.json-textarea{flex:1;width:100%;padding:16px;border:none;outline:none;resize:none;font-family:SF Mono,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:13px;line-height:1.6;color:var(--schema-editor-text-primary);background:var(--schema-editor-bg);tab-size:2;box-sizing:border-box}.json-textarea:focus{outline:none}.hidden{display:none!important}.fields-container{flex:1;overflow-y:auto;padding:16px;min-height:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 24px;color:#94a3b8;text-align:center}.empty-state mat-icon{font-size:64px;width:64px;height:64px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:16px;margin:0}.fields-list{display:block;width:100%;min-height:50px}.fields-list .field-wrapper{margin-bottom:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease;box-sizing:border-box}.field-item:hover{background:var(--schema-editor-field-bg-hover);border-color:var(--schema-editor-border-color)}.field-item.is-editing{background:var(--schema-editor-field-bg-editing);border-color:var(--schema-editor-accent-primary)}.field-item.is-complex{background:var(--schema-editor-field-bg-complex)}.field-item.is-complex:hover{background:#fef9c3}.field-left{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0}.field-right{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0;margin-left:auto}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:grab;color:#94a3b8;padding:4px;border-radius:4px;transition:all .15s ease}.drag-handle:hover{background:#e2e8f0;color:#64748b}.drag-handle:active{cursor:grabbing}.drag-handle mat-icon{font-size:20px;width:20px;height:20px}.field-wrapper{display:block;width:100%;box-sizing:border-box}::ng-deep .drag-preview{display:flex!important;align-items:center!important;gap:8px!important;padding:12px 16px!important;background:#fff!important;border:2px solid #3b82f6!important;border-radius:8px!important;box-shadow:0 8px 24px #0003!important;font-size:14px!important;font-weight:500!important;color:#1e293b!important;overflow:hidden!important;white-space:nowrap!important;box-sizing:border-box!important}::ng-deep .drag-preview mat-icon{color:#3b82f6!important;font-size:20px!important;width:20px!important;height:20px!important;flex-shrink:0!important;overflow:hidden!important}.drag-placeholder{background:#e2e8f0;border:2px dashed var(--schema-editor-accent-primary);border-radius:var(--schema-editor-field-border-radius);min-height:48px;margin-bottom:4px}.drag-placeholder .placeholder-content{height:100%;min-height:48px}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.cdk-drop-list-dragging .field-wrapper:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.indent-buttons{display:flex;flex-direction:row;gap:2px}.indent-buttons .indent-btn{background:none;border:none;padding:2px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:3px;transition:all .15s ease}.indent-buttons .indent-btn:hover:not(:disabled){background:#dbeafe;color:#2563eb}.indent-buttons .indent-btn:disabled{opacity:.3;cursor:default}.indent-buttons .indent-btn mat-icon{font-size:16px;width:16px;height:16px}.expand-btn{background:none;border:none;padding:4px;cursor:pointer;color:#64748b;border-radius:4px;display:flex;align-items:center;transition:all .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;cursor:pointer;padding:4px 8px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name:hover{background:#e2e8f0}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-name-input{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:2px solid #3b82f6;border-radius:6px;outline:none;background:#fff}.field-name-input::placeholder{color:#94a3b8;font-weight:400}.required-btn{background:none;border:none;padding:4px;cursor:pointer;color:#cbd5e1;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .15s ease;flex-shrink:0}.required-btn:hover{color:#f59e0b;background:#fef3c7}.required-btn.is-required{color:#f59e0b}.required-btn mat-icon{font-size:18px;width:18px;height:18px}.description-input{flex:1;font-size:13px;color:#64748b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;min-width:80px;max-width:100%;transition:all .15s ease}.description-input:focus{border-color:#3b82f6;background:#fff}.description-input::placeholder{color:#94a3b8;font-style:italic}.type-selector{width:140px;flex-shrink:0}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important}.type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.field-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;width:128px;flex-shrink:0;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions,.field-right:hover .field-actions{opacity:1}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.nested-fields{display:block;min-height:30px;margin-top:4px;margin-bottom:8px;padding-left:32px}.nested-fields .field-wrapper{margin-bottom:4px}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor{margin-left:16px}.empty-nested{display:flex;align-items:center;gap:12px;padding:12px 16px;color:#94a3b8;font-size:13px;font-style:italic;background:#f8fafc;border-radius:6px;border:1px dashed #e2e8f0}.allowed-values-editor{margin-left:48px;margin-top:4px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px}.allowed-values-editor .values-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.allowed-values-editor .values-label{font-size:12px;font-weight:500;color:#166534}.allowed-values-editor .value-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #86efac;border-radius:4px;outline:none;background:#fff}.allowed-values-editor .value-input:focus{border-color:#22c55e}.allowed-values-editor .value-input::placeholder{color:#94a3b8}.allowed-values-editor .add-value-btn{background:#22c55e;border:none;color:#fff;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.allowed-values-editor .add-value-btn:hover{background:#16a34a}.allowed-values-editor .add-value-btn mat-icon{font-size:18px;width:18px;height:18px}.allowed-values-editor .values-list{display:flex;flex-wrap:wrap;gap:6px}.allowed-values-editor .value-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#fff;border:1px solid #86efac;border-radius:4px;font-size:12px;color:#166534}.allowed-values-editor .value-chip .remove-value-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.allowed-values-editor .value-chip .remove-value-btn:hover{color:#ef4444}.allowed-values-editor .value-chip .remove-value-btn mat-icon{font-size:14px;width:14px;height:14px}.allowed-values-editor .no-values{font-size:12px;color:#94a3b8;font-style:italic}.field-actions .has-values{color:#22c55e!important}.field-actions .has-default{color:#8b5cf6!important}.default-value-editor{margin-left:48px;margin-top:4px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px}.default-value-editor .default-header{display:flex;align-items:center;gap:8px}.default-value-editor .default-label{font-size:12px;font-weight:500;color:#6d28d9}.default-value-editor .default-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;max-width:200px}.default-value-editor .default-input:focus{border-color:#8b5cf6}.default-value-editor .default-input::placeholder{color:#94a3b8}.default-value-editor .default-select{padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;cursor:pointer}.default-value-editor .default-select:focus{border-color:#8b5cf6}.default-value-editor .clear-default-btn{background:none;border:none;padding:4px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.default-value-editor .clear-default-btn:hover{color:#ef4444}.default-value-editor .clear-default-btn mat-icon{font-size:16px;width:16px;height:16px}\n"] }]
|
|
3183
3550
|
}], propDecorators: { schema: [{
|
|
3184
3551
|
type: Input
|
|
3552
|
+
}], showJsonToggle: [{
|
|
3553
|
+
type: Input
|
|
3185
3554
|
}], schemaChange: [{
|
|
3186
3555
|
type: Output
|
|
3187
3556
|
}], save: [{
|