@expeed/ngx-data-mapper 1.0.0 → 1.1.0
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.
|
@@ -4,29 +4,120 @@ import * as i1 from '@angular/common';
|
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import * as i3 from '@angular/material/icon';
|
|
6
6
|
import { MatIconModule } from '@angular/material/icon';
|
|
7
|
-
import * as i2
|
|
7
|
+
import * as i2 from '@angular/material/button';
|
|
8
8
|
import { MatButtonModule } from '@angular/material/button';
|
|
9
|
-
import * as
|
|
9
|
+
import * as i7 from '@angular/material/tooltip';
|
|
10
10
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
11
|
-
import * as i2 from '@angular/forms';
|
|
11
|
+
import * as i2$1 from '@angular/forms';
|
|
12
12
|
import { FormsModule } from '@angular/forms';
|
|
13
13
|
import * as i5 from '@angular/material/select';
|
|
14
14
|
import { MatSelectModule } from '@angular/material/select';
|
|
15
15
|
import * as i5$1 from '@angular/material/input';
|
|
16
16
|
import { MatInputModule } from '@angular/material/input';
|
|
17
17
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
18
|
+
import * as i8 from '@angular/material/checkbox';
|
|
19
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
20
|
+
import * as i9 from '@angular/cdk/drag-drop';
|
|
21
|
+
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
18
22
|
import * as i6 from '@angular/material/radio';
|
|
19
23
|
import { MatRadioModule } from '@angular/material/radio';
|
|
20
|
-
import * as i7 from '@angular/material/slide-toggle';
|
|
24
|
+
import * as i7$1 from '@angular/material/slide-toggle';
|
|
21
25
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
22
|
-
import * as i9 from '@angular/material/divider';
|
|
26
|
+
import * as i9$1 from '@angular/material/divider';
|
|
23
27
|
import { MatDividerModule } from '@angular/material/divider';
|
|
24
28
|
import * as i6$1 from '@angular/material/datepicker';
|
|
25
29
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
26
30
|
import { MatNativeDateModule } from '@angular/material/core';
|
|
27
|
-
import * as i7$
|
|
31
|
+
import * as i7$2 from '@angular/material/menu';
|
|
28
32
|
import { MatMenuModule } from '@angular/material/menu';
|
|
29
|
-
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Standard JSON Schema (draft-07) TypeScript interfaces
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Convert JSON Schema to flat field list for UI rendering
|
|
39
|
+
*/
|
|
40
|
+
function schemaToFields(schema, parentPath = '') {
|
|
41
|
+
const fields = [];
|
|
42
|
+
if (schema.type === 'object' && schema.properties) {
|
|
43
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
44
|
+
const path = parentPath ? `${parentPath}.${name}` : name;
|
|
45
|
+
const field = {
|
|
46
|
+
name,
|
|
47
|
+
path,
|
|
48
|
+
schema: propSchema,
|
|
49
|
+
expanded: false,
|
|
50
|
+
};
|
|
51
|
+
if (propSchema.type === 'object' && propSchema.properties) {
|
|
52
|
+
field.children = schemaToFields(propSchema, path);
|
|
53
|
+
}
|
|
54
|
+
else if (propSchema.type === 'array' && propSchema.items) {
|
|
55
|
+
field.children = schemaToFields(propSchema.items, `${path}[]`);
|
|
56
|
+
}
|
|
57
|
+
fields.push(field);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return fields;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the simple type for display purposes
|
|
64
|
+
*/
|
|
65
|
+
function getSchemaType(schema) {
|
|
66
|
+
if (Array.isArray(schema.type)) {
|
|
67
|
+
return schema.type.filter(t => t !== 'null').join(' | ');
|
|
68
|
+
}
|
|
69
|
+
if (schema.type === 'integer') {
|
|
70
|
+
return 'number';
|
|
71
|
+
}
|
|
72
|
+
if (schema.format === 'date' || schema.format === 'date-time') {
|
|
73
|
+
return 'date';
|
|
74
|
+
}
|
|
75
|
+
return schema.type || 'any';
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create an empty JSON Schema for a new schema definition
|
|
79
|
+
*/
|
|
80
|
+
function createEmptySchema(title = 'New Schema') {
|
|
81
|
+
return {
|
|
82
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
83
|
+
title,
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {},
|
|
86
|
+
required: [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Add a property to a schema
|
|
91
|
+
*/
|
|
92
|
+
function addProperty(schema, name, type, options) {
|
|
93
|
+
const newSchema = { ...schema };
|
|
94
|
+
newSchema.properties = { ...newSchema.properties };
|
|
95
|
+
const propSchema = { type };
|
|
96
|
+
if (options?.description) {
|
|
97
|
+
propSchema.description = options.description;
|
|
98
|
+
}
|
|
99
|
+
if (type === 'object') {
|
|
100
|
+
propSchema.properties = {};
|
|
101
|
+
}
|
|
102
|
+
else if (type === 'array') {
|
|
103
|
+
propSchema.items = { type: 'string' };
|
|
104
|
+
}
|
|
105
|
+
newSchema.properties[name] = propSchema;
|
|
106
|
+
if (options?.required) {
|
|
107
|
+
newSchema.required = [...(newSchema.required || []), name];
|
|
108
|
+
}
|
|
109
|
+
return newSchema;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Remove a property from a schema
|
|
113
|
+
*/
|
|
114
|
+
function removeProperty(schema, name) {
|
|
115
|
+
const newSchema = { ...schema };
|
|
116
|
+
newSchema.properties = { ...newSchema.properties };
|
|
117
|
+
delete newSchema.properties[name];
|
|
118
|
+
newSchema.required = (newSchema.required || []).filter(r => r !== name);
|
|
119
|
+
return newSchema;
|
|
120
|
+
}
|
|
30
121
|
|
|
31
122
|
class MappingService {
|
|
32
123
|
mappings = signal([], ...(ngDevMode ? [{ debugName: "mappings" }] : []));
|
|
@@ -97,9 +188,9 @@ class MappingService {
|
|
|
97
188
|
...existingMapping.sourceFields,
|
|
98
189
|
...sourceFields.filter((sf) => !existingMapping.sourceFields.some((esf) => esf.id === sf.id)),
|
|
99
190
|
],
|
|
100
|
-
|
|
101
|
-
? { type: 'concat', separator: ' ', ...transformation }
|
|
102
|
-
: transformation
|
|
191
|
+
transformations: existingMapping.sourceFields.length + sourceFields.length > 1
|
|
192
|
+
? [{ type: 'concat', separator: ' ', ...transformation }]
|
|
193
|
+
: transformation ? [transformation] : [{ type: 'direct' }],
|
|
103
194
|
};
|
|
104
195
|
this.mappings.update((mappings) => mappings.map((m) => (m.id === existingMapping.id ? updatedMapping : m)));
|
|
105
196
|
return updatedMapping;
|
|
@@ -108,7 +199,7 @@ class MappingService {
|
|
|
108
199
|
id: this.generateId(),
|
|
109
200
|
sourceFields,
|
|
110
201
|
targetField,
|
|
111
|
-
|
|
202
|
+
transformations: transformation ? [transformation] : [{ type: 'direct' }],
|
|
112
203
|
isArrayMapping: false, // Only true for array-to-array connections
|
|
113
204
|
arrayMappingId, // Links to parent array mapping if within array context
|
|
114
205
|
isArrayToObjectMapping: false,
|
|
@@ -150,7 +241,7 @@ class MappingService {
|
|
|
150
241
|
id: arrayMapping.id,
|
|
151
242
|
sourceFields: [sourceArray],
|
|
152
243
|
targetField: targetArray,
|
|
153
|
-
|
|
244
|
+
transformations: [{ type: 'direct' }],
|
|
154
245
|
isArrayMapping: true,
|
|
155
246
|
};
|
|
156
247
|
this.mappings.update((mappings) => [...mappings, fieldMapping]);
|
|
@@ -177,7 +268,7 @@ class MappingService {
|
|
|
177
268
|
id: arrayToObjectMapping.id,
|
|
178
269
|
sourceFields: [sourceArray],
|
|
179
270
|
targetField: targetObject,
|
|
180
|
-
|
|
271
|
+
transformations: [{ type: 'direct' }],
|
|
181
272
|
isArrayToObjectMapping: true,
|
|
182
273
|
};
|
|
183
274
|
this.mappings.update((mappings) => [...mappings, fieldMapping]);
|
|
@@ -241,8 +332,8 @@ class MappingService {
|
|
|
241
332
|
updateMapping(mappingId, updates) {
|
|
242
333
|
this.mappings.update((mappings) => mappings.map((m) => (m.id === mappingId ? { ...m, ...updates } : m)));
|
|
243
334
|
}
|
|
244
|
-
|
|
245
|
-
this.mappings.update((mappings) => mappings.map((m) => m.id === mappingId ? { ...m,
|
|
335
|
+
updateTransformations(mappingId, transformations) {
|
|
336
|
+
this.mappings.update((mappings) => mappings.map((m) => m.id === mappingId ? { ...m, transformations } : m));
|
|
246
337
|
}
|
|
247
338
|
removeMapping(mappingId) {
|
|
248
339
|
this.mappings.update((mappings) => mappings.filter((m) => m.id !== mappingId));
|
|
@@ -262,9 +353,9 @@ class MappingService {
|
|
|
262
353
|
? {
|
|
263
354
|
...m,
|
|
264
355
|
sourceFields: m.sourceFields.filter((sf) => sf.id !== sourceFieldId),
|
|
265
|
-
|
|
266
|
-
? { type: 'direct' }
|
|
267
|
-
: m.
|
|
356
|
+
transformations: m.sourceFields.length - 1 === 1
|
|
357
|
+
? [{ type: 'direct' }]
|
|
358
|
+
: m.transformations,
|
|
268
359
|
}
|
|
269
360
|
: m));
|
|
270
361
|
}
|
|
@@ -320,8 +411,11 @@ class MappingService {
|
|
|
320
411
|
default: return 'string';
|
|
321
412
|
}
|
|
322
413
|
}
|
|
323
|
-
exportMappings() {
|
|
414
|
+
exportMappings(name, description) {
|
|
324
415
|
const exportData = {
|
|
416
|
+
version: '1.0',
|
|
417
|
+
name: name || 'Mapping Configuration',
|
|
418
|
+
description: description || '',
|
|
325
419
|
mappings: this.mappings(),
|
|
326
420
|
arrayMappings: this.arrayMappings(),
|
|
327
421
|
arrayToObjectMappings: this.arrayToObjectMappings(),
|
|
@@ -329,6 +423,20 @@ class MappingService {
|
|
|
329
423
|
};
|
|
330
424
|
return JSON.stringify(exportData, null, 2);
|
|
331
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* Export mappings as a MappingDocument object (not stringified)
|
|
428
|
+
*/
|
|
429
|
+
exportMappingsAsObject(name, description) {
|
|
430
|
+
return {
|
|
431
|
+
version: '1.0',
|
|
432
|
+
name: name || 'Mapping Configuration',
|
|
433
|
+
description: description || '',
|
|
434
|
+
mappings: this.mappings(),
|
|
435
|
+
arrayMappings: this.arrayMappings(),
|
|
436
|
+
arrayToObjectMappings: this.arrayToObjectMappings(),
|
|
437
|
+
defaultValues: this.defaultValues(),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
332
440
|
importMappings(json) {
|
|
333
441
|
try {
|
|
334
442
|
const data = JSON.parse(json);
|
|
@@ -377,6 +485,10 @@ class TransformationService {
|
|
|
377
485
|
return String(values[0] ?? '').toUpperCase();
|
|
378
486
|
case 'lowercase':
|
|
379
487
|
return String(values[0] ?? '').toLowerCase();
|
|
488
|
+
case 'trim':
|
|
489
|
+
return String(values[0] ?? '').trim();
|
|
490
|
+
case 'mask':
|
|
491
|
+
return this.applyMask(String(values[0] ?? ''), config.pattern ?? '');
|
|
380
492
|
case 'dateFormat':
|
|
381
493
|
return this.formatDate(values[0], config.inputFormat, config.outputFormat);
|
|
382
494
|
case 'extractYear':
|
|
@@ -395,12 +507,83 @@ class TransformationService {
|
|
|
395
507
|
return this.formatNumber(values[0], config.decimalPlaces, config.prefix, config.suffix);
|
|
396
508
|
case 'template':
|
|
397
509
|
return this.applyTemplate(config.template ?? '', sourceFields, sourceValues);
|
|
398
|
-
case 'custom':
|
|
399
|
-
return this.applyCustomExpression(config.expression ?? '', sourceFields, sourceValues);
|
|
400
510
|
default:
|
|
401
511
|
return String(values[0] ?? '');
|
|
402
512
|
}
|
|
403
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Apply a transformation to a single value (for chained transformations)
|
|
516
|
+
*/
|
|
517
|
+
applyTransformationToValue(value, config) {
|
|
518
|
+
const str = String(value ?? '');
|
|
519
|
+
switch (config.type) {
|
|
520
|
+
case 'direct':
|
|
521
|
+
return str;
|
|
522
|
+
case 'concat':
|
|
523
|
+
// For single value, just return it (concat needs multiple values)
|
|
524
|
+
return str;
|
|
525
|
+
case 'substring':
|
|
526
|
+
return str.substring(config.startIndex ?? 0, config.endIndex ?? str.length);
|
|
527
|
+
case 'replace':
|
|
528
|
+
return str.replace(new RegExp(config.searchValue ?? '', 'g'), config.replaceValue ?? '');
|
|
529
|
+
case 'uppercase':
|
|
530
|
+
return str.toUpperCase();
|
|
531
|
+
case 'lowercase':
|
|
532
|
+
return str.toLowerCase();
|
|
533
|
+
case 'trim':
|
|
534
|
+
return str.trim();
|
|
535
|
+
case 'mask':
|
|
536
|
+
return this.applyMask(str, config.pattern ?? '');
|
|
537
|
+
case 'dateFormat':
|
|
538
|
+
return this.formatDate(value, config.inputFormat, config.outputFormat);
|
|
539
|
+
case 'extractYear':
|
|
540
|
+
return this.extractDatePart(value, 'year');
|
|
541
|
+
case 'extractMonth':
|
|
542
|
+
return this.extractDatePart(value, 'month');
|
|
543
|
+
case 'extractDay':
|
|
544
|
+
return this.extractDatePart(value, 'day');
|
|
545
|
+
case 'extractHour':
|
|
546
|
+
return this.extractDatePart(value, 'hour');
|
|
547
|
+
case 'extractMinute':
|
|
548
|
+
return this.extractDatePart(value, 'minute');
|
|
549
|
+
case 'extractSecond':
|
|
550
|
+
return this.extractDatePart(value, 'second');
|
|
551
|
+
case 'numberFormat':
|
|
552
|
+
return this.formatNumber(value, config.decimalPlaces, config.prefix, config.suffix);
|
|
553
|
+
case 'template':
|
|
554
|
+
// For single value, replace {0} with the value
|
|
555
|
+
return (config.template ?? '').replace(/\{0\}/g, str);
|
|
556
|
+
default:
|
|
557
|
+
return str;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Apply multiple transformations in sequence, respecting conditions
|
|
562
|
+
*/
|
|
563
|
+
applyTransformations(sourceValues, sourceFields, transformations) {
|
|
564
|
+
if (transformations.length === 0) {
|
|
565
|
+
return '';
|
|
566
|
+
}
|
|
567
|
+
// Get initial value for first transformation
|
|
568
|
+
const initialValues = sourceFields.map((f) => this.getValueByPath(sourceValues, f.path));
|
|
569
|
+
const initialValue = initialValues.length === 1 ? initialValues[0] : initialValues.join(' ');
|
|
570
|
+
// Apply first transformation using source fields (check condition first)
|
|
571
|
+
let result;
|
|
572
|
+
if (this.isConditionMet(initialValue, transformations[0])) {
|
|
573
|
+
result = this.applyTransformation(sourceValues, sourceFields, transformations[0]);
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
result = String(initialValue ?? '');
|
|
577
|
+
}
|
|
578
|
+
// Apply subsequent transformations to the result
|
|
579
|
+
for (let i = 1; i < transformations.length; i++) {
|
|
580
|
+
if (this.isConditionMet(result, transformations[i])) {
|
|
581
|
+
result = this.applyTransformationToValue(result, transformations[i]);
|
|
582
|
+
}
|
|
583
|
+
// If condition not met, result passes through unchanged
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
404
587
|
getValueByPath(obj, path) {
|
|
405
588
|
return path.split('.').reduce((acc, part) => {
|
|
406
589
|
if (acc && typeof acc === 'object') {
|
|
@@ -411,10 +594,10 @@ class TransformationService {
|
|
|
411
594
|
}
|
|
412
595
|
applyTemplate(template, sourceFields, sourceValues) {
|
|
413
596
|
let result = template;
|
|
414
|
-
|
|
597
|
+
// Replace positional placeholders {0}, {1}, etc.
|
|
598
|
+
sourceFields.forEach((field, index) => {
|
|
415
599
|
const value = this.getValueByPath(sourceValues, field.path);
|
|
416
|
-
result = result.replace(new RegExp(`\\{${
|
|
417
|
-
result = result.replace(new RegExp(`\\{${field.path}\\}`, 'g'), String(value ?? ''));
|
|
600
|
+
result = result.replace(new RegExp(`\\{${index}\\}`, 'g'), String(value ?? ''));
|
|
418
601
|
});
|
|
419
602
|
return result;
|
|
420
603
|
}
|
|
@@ -478,21 +661,24 @@ class TransformationService {
|
|
|
478
661
|
: num.toString();
|
|
479
662
|
return `${prefix ?? ''}${formatted}${suffix ?? ''}`;
|
|
480
663
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
664
|
+
applyMask(input, pattern) {
|
|
665
|
+
if (!pattern)
|
|
666
|
+
return input;
|
|
667
|
+
let result = '';
|
|
668
|
+
let inputIndex = 0;
|
|
669
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
670
|
+
const patternChar = pattern.charAt(i);
|
|
671
|
+
if (patternChar === '#') {
|
|
672
|
+
if (inputIndex < input.length) {
|
|
673
|
+
result += input.charAt(inputIndex);
|
|
674
|
+
inputIndex++;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
result += patternChar;
|
|
679
|
+
}
|
|
495
680
|
}
|
|
681
|
+
return result;
|
|
496
682
|
}
|
|
497
683
|
getTransformationLabel(type) {
|
|
498
684
|
const labels = {
|
|
@@ -502,6 +688,8 @@ class TransformationService {
|
|
|
502
688
|
replace: 'Find & Replace',
|
|
503
689
|
uppercase: 'Uppercase',
|
|
504
690
|
lowercase: 'Lowercase',
|
|
691
|
+
trim: 'Trim',
|
|
692
|
+
mask: 'Mask',
|
|
505
693
|
dateFormat: 'Date Format',
|
|
506
694
|
extractYear: 'Extract Year',
|
|
507
695
|
extractMonth: 'Extract Month',
|
|
@@ -511,7 +699,6 @@ class TransformationService {
|
|
|
511
699
|
extractSecond: 'Extract Second',
|
|
512
700
|
numberFormat: 'Number Format',
|
|
513
701
|
template: 'Template',
|
|
514
|
-
custom: 'Custom Expression',
|
|
515
702
|
};
|
|
516
703
|
return labels[type];
|
|
517
704
|
}
|
|
@@ -523,6 +710,8 @@ class TransformationService {
|
|
|
523
710
|
{ type: 'replace', label: 'Find & Replace', category: 'String' },
|
|
524
711
|
{ type: 'uppercase', label: 'Uppercase', category: 'String' },
|
|
525
712
|
{ type: 'lowercase', label: 'Lowercase', category: 'String' },
|
|
713
|
+
{ type: 'trim', label: 'Trim', category: 'String' },
|
|
714
|
+
{ type: 'mask', label: 'Mask', category: 'String' },
|
|
526
715
|
{ type: 'template', label: 'Template', category: 'String' },
|
|
527
716
|
{ type: 'dateFormat', label: 'Format Date', category: 'Date' },
|
|
528
717
|
{ type: 'extractYear', label: 'Extract Year', category: 'Date' },
|
|
@@ -532,9 +721,77 @@ class TransformationService {
|
|
|
532
721
|
{ type: 'extractMinute', label: 'Extract Minute', category: 'Date' },
|
|
533
722
|
{ type: 'extractSecond', label: 'Extract Second', category: 'Date' },
|
|
534
723
|
{ type: 'numberFormat', label: 'Number Format', category: 'Number' },
|
|
535
|
-
{ type: 'custom', label: 'Custom Expression', category: 'Advanced' },
|
|
536
724
|
];
|
|
537
725
|
}
|
|
726
|
+
// Condition evaluation methods
|
|
727
|
+
evaluateCondition(value, condition) {
|
|
728
|
+
return this.evaluateGroup(value, condition);
|
|
729
|
+
}
|
|
730
|
+
evaluateGroup(value, group) {
|
|
731
|
+
if (group.children.length === 0) {
|
|
732
|
+
return true; // Empty group is always true
|
|
733
|
+
}
|
|
734
|
+
if (group.logic === 'and') {
|
|
735
|
+
return group.children.every(child => this.evaluateItem(value, child));
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
return group.children.some(child => this.evaluateItem(value, child));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
evaluateItem(value, item) {
|
|
742
|
+
if (item.type === 'group') {
|
|
743
|
+
return this.evaluateGroup(value, item);
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
return this.evaluateConditionItem(value, item);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
evaluateConditionItem(value, condition) {
|
|
750
|
+
const strValue = String(value ?? '');
|
|
751
|
+
const numValue = Number(value);
|
|
752
|
+
const condValue = condition.value;
|
|
753
|
+
switch (condition.operator) {
|
|
754
|
+
case 'equals':
|
|
755
|
+
return strValue === String(condValue);
|
|
756
|
+
case 'notEquals':
|
|
757
|
+
return strValue !== String(condValue);
|
|
758
|
+
case 'contains':
|
|
759
|
+
return strValue.includes(String(condValue));
|
|
760
|
+
case 'notContains':
|
|
761
|
+
return !strValue.includes(String(condValue));
|
|
762
|
+
case 'startsWith':
|
|
763
|
+
return strValue.startsWith(String(condValue));
|
|
764
|
+
case 'endsWith':
|
|
765
|
+
return strValue.endsWith(String(condValue));
|
|
766
|
+
case 'isEmpty':
|
|
767
|
+
return strValue === '' || value === null || value === undefined;
|
|
768
|
+
case 'isNotEmpty':
|
|
769
|
+
return strValue !== '' && value !== null && value !== undefined;
|
|
770
|
+
case 'greaterThan':
|
|
771
|
+
return !isNaN(numValue) && numValue > Number(condValue);
|
|
772
|
+
case 'lessThan':
|
|
773
|
+
return !isNaN(numValue) && numValue < Number(condValue);
|
|
774
|
+
case 'greaterThanOrEqual':
|
|
775
|
+
return !isNaN(numValue) && numValue >= Number(condValue);
|
|
776
|
+
case 'lessThanOrEqual':
|
|
777
|
+
return !isNaN(numValue) && numValue <= Number(condValue);
|
|
778
|
+
case 'isTrue':
|
|
779
|
+
return value === true || strValue.toLowerCase() === 'true';
|
|
780
|
+
case 'isFalse':
|
|
781
|
+
return value === false || strValue.toLowerCase() === 'false';
|
|
782
|
+
default:
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Check if a transformation's condition is met
|
|
788
|
+
*/
|
|
789
|
+
isConditionMet(value, config) {
|
|
790
|
+
if (!config.condition?.enabled || !config.condition.root) {
|
|
791
|
+
return true; // No condition means always apply
|
|
792
|
+
}
|
|
793
|
+
return this.evaluateCondition(value, config.condition.root);
|
|
794
|
+
}
|
|
538
795
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
539
796
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationService, providedIn: 'root' });
|
|
540
797
|
}
|
|
@@ -761,7 +1018,7 @@ class SchemaParserService {
|
|
|
761
1018
|
return field;
|
|
762
1019
|
}
|
|
763
1020
|
mapType(schema) {
|
|
764
|
-
const type = schema.type;
|
|
1021
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
765
1022
|
const format = schema.format;
|
|
766
1023
|
// Check format first for date types
|
|
767
1024
|
if (format === 'date' || format === 'date-time' || format === 'time') {
|
|
@@ -987,7 +1244,7 @@ class SchemaTreeComponent {
|
|
|
987
1244
|
return field.id;
|
|
988
1245
|
}
|
|
989
1246
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
990
|
-
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:
|
|
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"] }] });
|
|
991
1248
|
}
|
|
992
1249
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, decorators: [{
|
|
993
1250
|
type: Component,
|
|
@@ -1018,6 +1275,215 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
1018
1275
|
args: ['fieldItem']
|
|
1019
1276
|
}] } });
|
|
1020
1277
|
|
|
1278
|
+
class ConditionBuilderComponent {
|
|
1279
|
+
// Available fields for conditions (optional - if not provided, uses 'value' as the only field)
|
|
1280
|
+
fields = [];
|
|
1281
|
+
// The condition group to edit
|
|
1282
|
+
condition = null;
|
|
1283
|
+
// Whether to show a compact/inline version
|
|
1284
|
+
compact = false;
|
|
1285
|
+
// Emits when condition changes
|
|
1286
|
+
conditionChange = new EventEmitter();
|
|
1287
|
+
rootGroup;
|
|
1288
|
+
// Default field for transformation conditions (the input value)
|
|
1289
|
+
defaultField = { path: 'value', name: 'Input value', type: 'string' };
|
|
1290
|
+
// Operators by type
|
|
1291
|
+
stringOperators = [
|
|
1292
|
+
{ value: 'equals', label: 'equals', needsValue: true },
|
|
1293
|
+
{ value: 'notEquals', label: 'not equals', needsValue: true },
|
|
1294
|
+
{ value: 'contains', label: 'contains', needsValue: true },
|
|
1295
|
+
{ value: 'notContains', label: 'not contain', needsValue: true },
|
|
1296
|
+
{ value: 'startsWith', label: 'starts with', needsValue: true },
|
|
1297
|
+
{ value: 'endsWith', label: 'ends with', needsValue: true },
|
|
1298
|
+
{ value: 'isEmpty', label: 'is empty', needsValue: false },
|
|
1299
|
+
{ value: 'isNotEmpty', label: 'is not empty', needsValue: false },
|
|
1300
|
+
];
|
|
1301
|
+
numberOperators = [
|
|
1302
|
+
{ value: 'equals', label: 'equals', needsValue: true },
|
|
1303
|
+
{ value: 'notEquals', label: 'not equals', needsValue: true },
|
|
1304
|
+
{ value: 'greaterThan', label: '>', needsValue: true },
|
|
1305
|
+
{ value: 'lessThan', label: '<', needsValue: true },
|
|
1306
|
+
{ value: 'greaterThanOrEqual', label: '>=', needsValue: true },
|
|
1307
|
+
{ value: 'lessThanOrEqual', label: '<=', needsValue: true },
|
|
1308
|
+
];
|
|
1309
|
+
booleanOperators = [
|
|
1310
|
+
{ value: 'isTrue', label: 'is true', needsValue: false },
|
|
1311
|
+
{ value: 'isFalse', label: 'is false', needsValue: false },
|
|
1312
|
+
];
|
|
1313
|
+
ngOnInit() {
|
|
1314
|
+
if (this.condition) {
|
|
1315
|
+
this.rootGroup = this.cloneGroup(this.condition);
|
|
1316
|
+
}
|
|
1317
|
+
else {
|
|
1318
|
+
this.rootGroup = this.createEmptyGroup();
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
get availableFields() {
|
|
1322
|
+
return this.fields.length > 0 ? this.fields : [this.defaultField];
|
|
1323
|
+
}
|
|
1324
|
+
get showFieldSelector() {
|
|
1325
|
+
return this.fields.length > 1;
|
|
1326
|
+
}
|
|
1327
|
+
createEmptyGroup() {
|
|
1328
|
+
return {
|
|
1329
|
+
id: this.generateId(),
|
|
1330
|
+
type: 'group',
|
|
1331
|
+
logic: 'and',
|
|
1332
|
+
children: [],
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
generateId() {
|
|
1336
|
+
return `cond-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1337
|
+
}
|
|
1338
|
+
cloneGroup(group) {
|
|
1339
|
+
return {
|
|
1340
|
+
...group,
|
|
1341
|
+
children: group.children.map((child) => child.type === 'group' ? this.cloneGroup(child) : { ...child }),
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
getOperatorsForField(fieldPath) {
|
|
1345
|
+
const field = this.availableFields.find((f) => f.path === fieldPath);
|
|
1346
|
+
if (!field)
|
|
1347
|
+
return this.stringOperators;
|
|
1348
|
+
switch (field.type) {
|
|
1349
|
+
case 'number':
|
|
1350
|
+
return this.numberOperators;
|
|
1351
|
+
case 'boolean':
|
|
1352
|
+
return this.booleanOperators;
|
|
1353
|
+
default:
|
|
1354
|
+
return this.stringOperators;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
operatorNeedsValue(operator) {
|
|
1358
|
+
const allOperators = [...this.stringOperators, ...this.numberOperators, ...this.booleanOperators];
|
|
1359
|
+
const op = allOperators.find((o) => o.value === operator);
|
|
1360
|
+
return op?.needsValue ?? true;
|
|
1361
|
+
}
|
|
1362
|
+
isCondition(item) {
|
|
1363
|
+
return item.type === 'condition';
|
|
1364
|
+
}
|
|
1365
|
+
isGroup(item) {
|
|
1366
|
+
return item.type === 'group';
|
|
1367
|
+
}
|
|
1368
|
+
addCondition(group) {
|
|
1369
|
+
const firstField = this.availableFields[0];
|
|
1370
|
+
const newCondition = {
|
|
1371
|
+
id: this.generateId(),
|
|
1372
|
+
type: 'condition',
|
|
1373
|
+
field: firstField.path,
|
|
1374
|
+
fieldName: firstField.name,
|
|
1375
|
+
operator: 'isNotEmpty',
|
|
1376
|
+
value: '',
|
|
1377
|
+
valueType: firstField.type,
|
|
1378
|
+
};
|
|
1379
|
+
group.children = [...group.children, newCondition];
|
|
1380
|
+
this.emitChange();
|
|
1381
|
+
}
|
|
1382
|
+
addGroup(parentGroup) {
|
|
1383
|
+
const newGroup = {
|
|
1384
|
+
id: this.generateId(),
|
|
1385
|
+
type: 'group',
|
|
1386
|
+
logic: parentGroup.logic === 'and' ? 'or' : 'and',
|
|
1387
|
+
children: [],
|
|
1388
|
+
};
|
|
1389
|
+
parentGroup.children = [...parentGroup.children, newGroup];
|
|
1390
|
+
this.emitChange();
|
|
1391
|
+
}
|
|
1392
|
+
removeItem(parentGroup, itemId) {
|
|
1393
|
+
parentGroup.children = parentGroup.children.filter((c) => c.id !== itemId);
|
|
1394
|
+
this.emitChange();
|
|
1395
|
+
}
|
|
1396
|
+
onFieldChange(condition, fieldPath) {
|
|
1397
|
+
const field = this.availableFields.find((f) => f.path === fieldPath);
|
|
1398
|
+
if (field) {
|
|
1399
|
+
condition.field = fieldPath;
|
|
1400
|
+
condition.fieldName = field.name;
|
|
1401
|
+
condition.valueType = field.type;
|
|
1402
|
+
const operators = this.getOperatorsForField(fieldPath);
|
|
1403
|
+
condition.operator = operators[0].value;
|
|
1404
|
+
condition.value = '';
|
|
1405
|
+
}
|
|
1406
|
+
this.emitChange();
|
|
1407
|
+
}
|
|
1408
|
+
onOperatorChange(condition, operator) {
|
|
1409
|
+
condition.operator = operator;
|
|
1410
|
+
if (!this.operatorNeedsValue(operator)) {
|
|
1411
|
+
condition.value = '';
|
|
1412
|
+
}
|
|
1413
|
+
this.emitChange();
|
|
1414
|
+
}
|
|
1415
|
+
onValueChange(condition, value) {
|
|
1416
|
+
if (condition.valueType === 'number') {
|
|
1417
|
+
condition.value = parseFloat(value) || 0;
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
condition.value = value;
|
|
1421
|
+
}
|
|
1422
|
+
this.emitChange();
|
|
1423
|
+
}
|
|
1424
|
+
onLogicChange(group, logic) {
|
|
1425
|
+
group.logic = logic;
|
|
1426
|
+
this.emitChange();
|
|
1427
|
+
}
|
|
1428
|
+
emitChange() {
|
|
1429
|
+
this.rootGroup = this.cloneGroup(this.rootGroup);
|
|
1430
|
+
this.conditionChange.emit(this.rootGroup);
|
|
1431
|
+
}
|
|
1432
|
+
getConditionSummary() {
|
|
1433
|
+
return this.summarizeGroup(this.rootGroup);
|
|
1434
|
+
}
|
|
1435
|
+
summarizeGroup(group) {
|
|
1436
|
+
if (group.children.length === 0)
|
|
1437
|
+
return '';
|
|
1438
|
+
const parts = group.children.map(child => {
|
|
1439
|
+
if (child.type === 'condition') {
|
|
1440
|
+
return this.summarizeCondition(child);
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
return `(${this.summarizeGroup(child)})`;
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
return parts.join(` ${group.logic.toUpperCase()} `);
|
|
1447
|
+
}
|
|
1448
|
+
summarizeCondition(cond) {
|
|
1449
|
+
const opLabel = this.getOperatorLabel(cond.operator);
|
|
1450
|
+
if (this.operatorNeedsValue(cond.operator)) {
|
|
1451
|
+
return `${opLabel} "${cond.value}"`;
|
|
1452
|
+
}
|
|
1453
|
+
return opLabel;
|
|
1454
|
+
}
|
|
1455
|
+
getOperatorLabel(operator) {
|
|
1456
|
+
const allOperators = [...this.stringOperators, ...this.numberOperators, ...this.booleanOperators];
|
|
1457
|
+
const op = allOperators.find((o) => o.value === operator);
|
|
1458
|
+
return op?.label || operator;
|
|
1459
|
+
}
|
|
1460
|
+
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" }] });
|
|
1462
|
+
}
|
|
1463
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ConditionBuilderComponent, decorators: [{
|
|
1464
|
+
type: Component,
|
|
1465
|
+
args: [{ selector: 'condition-builder', standalone: true, imports: [
|
|
1466
|
+
CommonModule,
|
|
1467
|
+
FormsModule,
|
|
1468
|
+
MatButtonModule,
|
|
1469
|
+
MatIconModule,
|
|
1470
|
+
MatSelectModule,
|
|
1471
|
+
MatInputModule,
|
|
1472
|
+
MatFormFieldModule,
|
|
1473
|
+
MatRadioModule,
|
|
1474
|
+
MatSlideToggleModule,
|
|
1475
|
+
MatTooltipModule,
|
|
1476
|
+
], 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"] }]
|
|
1477
|
+
}], propDecorators: { fields: [{
|
|
1478
|
+
type: Input
|
|
1479
|
+
}], condition: [{
|
|
1480
|
+
type: Input
|
|
1481
|
+
}], compact: [{
|
|
1482
|
+
type: Input
|
|
1483
|
+
}], conditionChange: [{
|
|
1484
|
+
type: Output
|
|
1485
|
+
}] } });
|
|
1486
|
+
|
|
1021
1487
|
class TransformationPopoverComponent {
|
|
1022
1488
|
mapping;
|
|
1023
1489
|
position = { x: 0, y: 0 };
|
|
@@ -1026,9 +1492,15 @@ class TransformationPopoverComponent {
|
|
|
1026
1492
|
delete = new EventEmitter();
|
|
1027
1493
|
close = new EventEmitter();
|
|
1028
1494
|
transformationService = inject(TransformationService);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1495
|
+
// Array of transformation steps
|
|
1496
|
+
steps = [];
|
|
1497
|
+
// Preview results for each step
|
|
1498
|
+
stepPreviews = [];
|
|
1499
|
+
// Input values for each step (to show input → output)
|
|
1500
|
+
stepInputs = [];
|
|
1501
|
+
finalPreview = '';
|
|
1502
|
+
// Track which step is expanded (-1 means none, used in single-step mode)
|
|
1503
|
+
expandedStepIndex = -1;
|
|
1032
1504
|
availableTransformations = this.transformationService.getAvailableTransformations();
|
|
1033
1505
|
ngOnInit() {
|
|
1034
1506
|
this.initFromMapping();
|
|
@@ -1040,54 +1512,156 @@ class TransformationPopoverComponent {
|
|
|
1040
1512
|
}
|
|
1041
1513
|
initFromMapping() {
|
|
1042
1514
|
if (this.mapping) {
|
|
1043
|
-
|
|
1044
|
-
this.
|
|
1515
|
+
// Copy transformations array, with fallback for empty/undefined
|
|
1516
|
+
if (this.mapping.transformations && this.mapping.transformations.length > 0) {
|
|
1517
|
+
this.steps = this.mapping.transformations.map(t => ({ ...t }));
|
|
1518
|
+
}
|
|
1519
|
+
else {
|
|
1520
|
+
// Fallback to a single direct transformation
|
|
1521
|
+
this.steps = [{ type: 'direct' }];
|
|
1522
|
+
}
|
|
1045
1523
|
this.updatePreview();
|
|
1046
1524
|
}
|
|
1047
1525
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1526
|
+
// Check if we're in multi-step mode (more than one step)
|
|
1527
|
+
get isMultiStep() {
|
|
1528
|
+
return this.steps.length > 1;
|
|
1529
|
+
}
|
|
1530
|
+
onStepTypeChange(index) {
|
|
1531
|
+
const step = this.steps[index];
|
|
1053
1532
|
// Set defaults based on type
|
|
1054
|
-
switch (
|
|
1533
|
+
switch (step.type) {
|
|
1055
1534
|
case 'concat':
|
|
1056
|
-
|
|
1057
|
-
|
|
1535
|
+
step.separator = step.separator ?? ' ';
|
|
1536
|
+
step.template = step.template ?? this.getDefaultTemplate();
|
|
1058
1537
|
break;
|
|
1059
1538
|
case 'substring':
|
|
1060
|
-
|
|
1061
|
-
|
|
1539
|
+
step.startIndex = step.startIndex ?? 0;
|
|
1540
|
+
step.endIndex = step.endIndex ?? 10;
|
|
1062
1541
|
break;
|
|
1063
1542
|
case 'replace':
|
|
1064
|
-
|
|
1065
|
-
|
|
1543
|
+
step.searchValue = step.searchValue ?? '';
|
|
1544
|
+
step.replaceValue = step.replaceValue ?? '';
|
|
1066
1545
|
break;
|
|
1067
1546
|
case 'dateFormat':
|
|
1068
|
-
|
|
1547
|
+
step.outputFormat = step.outputFormat ?? 'YYYY-MM-DD';
|
|
1069
1548
|
break;
|
|
1070
1549
|
case 'numberFormat':
|
|
1071
|
-
|
|
1550
|
+
step.decimalPlaces = step.decimalPlaces ?? 2;
|
|
1551
|
+
break;
|
|
1552
|
+
case 'mask':
|
|
1553
|
+
step.pattern = step.pattern ?? '(###) ###-####';
|
|
1072
1554
|
break;
|
|
1073
1555
|
}
|
|
1074
1556
|
this.updatePreview();
|
|
1075
1557
|
}
|
|
1076
1558
|
getDefaultTemplate() {
|
|
1077
|
-
return this.mapping.sourceFields.map((
|
|
1559
|
+
return this.mapping.sourceFields.map((_, i) => `{${i}}`).join(' ');
|
|
1560
|
+
}
|
|
1561
|
+
addStep() {
|
|
1562
|
+
this.steps.push({ type: 'direct' });
|
|
1563
|
+
// Expand the newly added step
|
|
1564
|
+
this.expandedStepIndex = this.steps.length - 1;
|
|
1565
|
+
this.updatePreview();
|
|
1566
|
+
}
|
|
1567
|
+
removeStep(index) {
|
|
1568
|
+
if (this.steps.length > 1) {
|
|
1569
|
+
this.steps.splice(index, 1);
|
|
1570
|
+
// Collapse after deletion
|
|
1571
|
+
this.expandedStepIndex = -1;
|
|
1572
|
+
this.updatePreview();
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
onStepDrop(event) {
|
|
1576
|
+
if (event.previousIndex !== event.currentIndex) {
|
|
1577
|
+
moveItemInArray(this.steps, event.previousIndex, event.currentIndex);
|
|
1578
|
+
// Move expanded index with the step
|
|
1579
|
+
if (this.expandedStepIndex === event.previousIndex) {
|
|
1580
|
+
this.expandedStepIndex = event.currentIndex;
|
|
1581
|
+
}
|
|
1582
|
+
else if (this.expandedStepIndex > event.previousIndex &&
|
|
1583
|
+
this.expandedStepIndex <= event.currentIndex) {
|
|
1584
|
+
this.expandedStepIndex--;
|
|
1585
|
+
}
|
|
1586
|
+
else if (this.expandedStepIndex < event.previousIndex &&
|
|
1587
|
+
this.expandedStepIndex >= event.currentIndex) {
|
|
1588
|
+
this.expandedStepIndex++;
|
|
1589
|
+
}
|
|
1590
|
+
this.updatePreview();
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
toggleStep(index) {
|
|
1594
|
+
if (this.expandedStepIndex === index) {
|
|
1595
|
+
this.expandedStepIndex = -1; // Collapse
|
|
1596
|
+
}
|
|
1597
|
+
else {
|
|
1598
|
+
this.expandedStepIndex = index; // Expand
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
isStepExpanded(index) {
|
|
1602
|
+
return this.expandedStepIndex === index;
|
|
1078
1603
|
}
|
|
1079
1604
|
updatePreview() {
|
|
1080
1605
|
if (!this.mapping || !this.sampleData) {
|
|
1081
|
-
this.
|
|
1606
|
+
this.stepPreviews = [];
|
|
1607
|
+
this.stepInputs = [];
|
|
1608
|
+
this.finalPreview = '';
|
|
1082
1609
|
return;
|
|
1083
1610
|
}
|
|
1084
|
-
this.
|
|
1611
|
+
this.stepPreviews = [];
|
|
1612
|
+
this.stepInputs = [];
|
|
1613
|
+
let currentValue = null;
|
|
1614
|
+
// Get initial input from source fields
|
|
1615
|
+
const initialValues = this.mapping.sourceFields
|
|
1616
|
+
.map(f => this.getValueByPath(this.sampleData, f.path))
|
|
1617
|
+
.map(v => String(v ?? ''));
|
|
1618
|
+
const initialInput = initialValues.join(', ');
|
|
1619
|
+
// For the first step, use the source fields from sample data
|
|
1620
|
+
for (let i = 0; i < this.steps.length; i++) {
|
|
1621
|
+
const step = this.steps[i];
|
|
1622
|
+
if (i === 0) {
|
|
1623
|
+
// First step: input is from source fields
|
|
1624
|
+
this.stepInputs.push(initialInput);
|
|
1625
|
+
// Check condition before applying transformation
|
|
1626
|
+
if (this.transformationService.isConditionMet(initialInput, step)) {
|
|
1627
|
+
currentValue = this.transformationService.applyTransformation(this.sampleData, this.mapping.sourceFields, step);
|
|
1628
|
+
}
|
|
1629
|
+
else {
|
|
1630
|
+
currentValue = initialInput; // Pass through if condition not met
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
else {
|
|
1634
|
+
// Subsequent steps: input is previous output
|
|
1635
|
+
this.stepInputs.push(this.stepPreviews[i - 1]);
|
|
1636
|
+
// Check condition before applying transformation
|
|
1637
|
+
if (this.transformationService.isConditionMet(currentValue, step)) {
|
|
1638
|
+
currentValue = this.transformationService.applyTransformationToValue(currentValue, step);
|
|
1639
|
+
}
|
|
1640
|
+
// If condition not met, currentValue passes through unchanged
|
|
1641
|
+
}
|
|
1642
|
+
this.stepPreviews.push(String(currentValue ?? ''));
|
|
1643
|
+
}
|
|
1644
|
+
this.finalPreview = this.stepPreviews[this.stepPreviews.length - 1] || '';
|
|
1645
|
+
}
|
|
1646
|
+
getValueByPath(obj, path) {
|
|
1647
|
+
return path.split('.').reduce((acc, part) => {
|
|
1648
|
+
if (acc && typeof acc === 'object') {
|
|
1649
|
+
return acc[part];
|
|
1650
|
+
}
|
|
1651
|
+
return undefined;
|
|
1652
|
+
}, obj);
|
|
1085
1653
|
}
|
|
1086
1654
|
onConfigChange() {
|
|
1087
1655
|
this.updatePreview();
|
|
1088
1656
|
}
|
|
1089
1657
|
onSave() {
|
|
1090
|
-
|
|
1658
|
+
// Filter out 'direct' transformations if they're not the only one
|
|
1659
|
+
const cleanedSteps = this.steps.length === 1
|
|
1660
|
+
? this.steps
|
|
1661
|
+
: this.steps.filter(s => s.type !== 'direct');
|
|
1662
|
+
// If all filtered out, keep at least one direct
|
|
1663
|
+
const finalSteps = cleanedSteps.length > 0 ? cleanedSteps : [{ type: 'direct' }];
|
|
1664
|
+
this.save.emit(finalSteps);
|
|
1091
1665
|
}
|
|
1092
1666
|
onDelete() {
|
|
1093
1667
|
this.delete.emit();
|
|
@@ -1104,8 +1678,88 @@ class TransformationPopoverComponent {
|
|
|
1104
1678
|
top: `${this.position.y}px`,
|
|
1105
1679
|
};
|
|
1106
1680
|
}
|
|
1681
|
+
getStepTypeLabel(type) {
|
|
1682
|
+
const t = this.availableTransformations.find(t => t.type === type);
|
|
1683
|
+
return t?.label || type;
|
|
1684
|
+
}
|
|
1685
|
+
// Condition methods
|
|
1686
|
+
hasCondition(step) {
|
|
1687
|
+
return step.condition?.enabled === true;
|
|
1688
|
+
}
|
|
1689
|
+
toggleCondition(step, enabled) {
|
|
1690
|
+
if (enabled) {
|
|
1691
|
+
if (!step.condition) {
|
|
1692
|
+
step.condition = {
|
|
1693
|
+
enabled: true,
|
|
1694
|
+
root: this.createEmptyConditionGroup(),
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
else {
|
|
1698
|
+
step.condition.enabled = true;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
else {
|
|
1702
|
+
if (step.condition) {
|
|
1703
|
+
step.condition.enabled = false;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
onConditionChange(step, group) {
|
|
1708
|
+
if (step.condition) {
|
|
1709
|
+
step.condition.root = group;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
createEmptyConditionGroup() {
|
|
1713
|
+
return {
|
|
1714
|
+
id: `cond-${Date.now()}`,
|
|
1715
|
+
type: 'group',
|
|
1716
|
+
logic: 'and',
|
|
1717
|
+
children: [],
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
getConditionSummary(step) {
|
|
1721
|
+
if (!step.condition?.enabled || !step.condition.root)
|
|
1722
|
+
return '';
|
|
1723
|
+
return this.summarizeConditionGroup(step.condition.root);
|
|
1724
|
+
}
|
|
1725
|
+
summarizeConditionGroup(group) {
|
|
1726
|
+
if (group.children.length === 0)
|
|
1727
|
+
return '';
|
|
1728
|
+
const parts = group.children.map(child => {
|
|
1729
|
+
if (child.type === 'condition') {
|
|
1730
|
+
const opLabel = this.getOperatorLabel(child.operator);
|
|
1731
|
+
if (['isEmpty', 'isNotEmpty', 'isTrue', 'isFalse'].includes(child.operator)) {
|
|
1732
|
+
return opLabel;
|
|
1733
|
+
}
|
|
1734
|
+
return `${opLabel} "${child.value}"`;
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
return `(${this.summarizeConditionGroup(child)})`;
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
return parts.join(` ${group.logic.toUpperCase()} `);
|
|
1741
|
+
}
|
|
1742
|
+
getOperatorLabel(operator) {
|
|
1743
|
+
const labels = {
|
|
1744
|
+
equals: '=',
|
|
1745
|
+
notEquals: '!=',
|
|
1746
|
+
contains: 'contains',
|
|
1747
|
+
notContains: 'not contain',
|
|
1748
|
+
startsWith: 'starts with',
|
|
1749
|
+
endsWith: 'ends with',
|
|
1750
|
+
isEmpty: 'is empty',
|
|
1751
|
+
isNotEmpty: 'is not empty',
|
|
1752
|
+
greaterThan: '>',
|
|
1753
|
+
lessThan: '<',
|
|
1754
|
+
greaterThanOrEqual: '>=',
|
|
1755
|
+
lessThanOrEqual: '<=',
|
|
1756
|
+
isTrue: 'is true',
|
|
1757
|
+
isFalse: 'is false',
|
|
1758
|
+
};
|
|
1759
|
+
return labels[operator] || operator;
|
|
1760
|
+
}
|
|
1107
1761
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1108
|
-
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()\">\n <div class=\"popover-arrow\"></div>\n\n <div class=\"popover-header\">\n <span class=\"popover-title\">Transformation</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=\"popover-content\">\n <!-- Source/Target Info -->\n <div class=\"mapping-info\">\n <div class=\"info-row\">\n <span class=\"info-label\">Source:</span>\n <span class=\"info-value\">{{ getSourceFieldNames() }}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"info-label\">Target:</span>\n <span class=\"info-value\">{{ mapping.targetField.name }}</span>\n </div>\n </div>\n\n <!-- Transformation Type -->\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Transformation Type</mat-label>\n <mat-select [(ngModel)]=\"transformationType\" (selectionChange)=\"onTypeChange()\">\n @for (t of availableTransformations; track t.type) {\n <mat-option [value]=\"t.type\">{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Type-specific options -->\n @switch (transformationType) {\n @case ('concat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Template</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.template\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"{field1} - {field2}\"\n />\n <mat-hint>Use curly braces around fieldName for values</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('substring') {\n <div class=\"config-section config-row\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Start Index</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.startIndex\"\n (ngModelChange)=\"onConfigChange()\"\n min=\"0\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>End Index</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.endIndex\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n }\n\n @case ('replace') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Search For</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.searchValue\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Replace With</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.replaceValue\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n }\n\n @case ('dateFormat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Output Format</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.outputFormat\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"YYYY-MM-DD\"\n />\n <mat-hint>YYYY, MM, DD, HH, mm, ss</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('numberFormat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Decimal Places</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.decimalPlaces\"\n (ngModelChange)=\"onConfigChange()\"\n min=\"0\"\n />\n </mat-form-field>\n <div class=\"config-row\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Prefix</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.prefix\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"$\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Suffix</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.suffix\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n </div>\n }\n\n @case ('template') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Template Expression</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"config.template\"\n (ngModelChange)=\"onConfigChange()\"\n rows=\"3\"\n placeholder=\"Hello {firstName}, your ID is {id}\"\n ></textarea>\n </mat-form-field>\n </div>\n }\n\n @case ('custom') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>JavaScript Expression</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"config.expression\"\n (ngModelChange)=\"onConfigChange()\"\n rows=\"3\"\n placeholder=\"fieldName.toUpperCase()\"\n ></textarea>\n <mat-hint>Use field names as variables</mat-hint>\n </mat-form-field>\n </div>\n }\n }\n\n <!-- Preview Section -->\n <div class=\"preview-section\">\n <span class=\"preview-label\">Preview:</span>\n <div class=\"preview-value\">{{ preview || '(empty)' }}</div>\n </div>\n </div>\n\n <div class=\"popover-actions\">\n <button mat-button color=\"warn\" (click)=\"onDelete()\" matTooltip=\"Remove this mapping\">\n <mat-icon>delete</mat-icon>\n Delete\n </button>\n <div class=\"action-spacer\"></div>\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\n </div>\n</div>\n\n<!-- Backdrop -->\n<div class=\"popover-backdrop\" (click)=\"onClose()\"></div>\n", styles: [".popover-backdrop{position:fixed;inset:0;background:#0000004d;z-index:999}.transformation-popover{position:fixed;z-index:1000;width:360px;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:400px;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}.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}.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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.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$1.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$1.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: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
|
|
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" }] });
|
|
1109
1763
|
}
|
|
1110
1764
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, decorators: [{
|
|
1111
1765
|
type: Component,
|
|
@@ -1118,7 +1772,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
1118
1772
|
MatInputModule,
|
|
1119
1773
|
MatFormFieldModule,
|
|
1120
1774
|
MatTooltipModule,
|
|
1121
|
-
|
|
1775
|
+
MatCheckboxModule,
|
|
1776
|
+
DragDropModule,
|
|
1777
|
+
ConditionBuilderComponent,
|
|
1778
|
+
], 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"] }]
|
|
1122
1779
|
}], propDecorators: { mapping: [{
|
|
1123
1780
|
type: Input
|
|
1124
1781
|
}], position: [{
|
|
@@ -1332,7 +1989,7 @@ class ArrayFilterModalComponent {
|
|
|
1332
1989
|
}
|
|
1333
1990
|
}
|
|
1334
1991
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1335
|
-
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$1.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$1.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.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: i8.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" }] });
|
|
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" }] });
|
|
1336
1993
|
}
|
|
1337
1994
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, decorators: [{
|
|
1338
1995
|
type: Component,
|
|
@@ -1536,7 +2193,7 @@ class ArraySelectorModalComponent {
|
|
|
1536
2193
|
}
|
|
1537
2194
|
}
|
|
1538
2195
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1539
|
-
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$1.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$1.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.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: i8.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" }] });
|
|
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" }] });
|
|
1540
2197
|
}
|
|
1541
2198
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, decorators: [{
|
|
1542
2199
|
type: Component,
|
|
@@ -1626,7 +2283,7 @@ class DefaultValuePopoverComponent {
|
|
|
1626
2283
|
}
|
|
1627
2284
|
}
|
|
1628
2285
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1629
|
-
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.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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2
|
|
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$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: "directive", type: i5.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"] }] });
|
|
1630
2287
|
}
|
|
1631
2288
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, decorators: [{
|
|
1632
2289
|
type: Component,
|
|
@@ -1657,8 +2314,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
1657
2314
|
}] } });
|
|
1658
2315
|
|
|
1659
2316
|
class DataMapperComponent {
|
|
1660
|
-
sourceSchema
|
|
1661
|
-
|
|
2317
|
+
set sourceSchema(value) {
|
|
2318
|
+
if (value) {
|
|
2319
|
+
this._sourceSchemaInput.set(value);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
set targetSchema(value) {
|
|
2323
|
+
if (value) {
|
|
2324
|
+
this._targetSchemaInput.set(value);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
1662
2327
|
sampleData = {};
|
|
1663
2328
|
mappingsChange = new EventEmitter();
|
|
1664
2329
|
svgContainer;
|
|
@@ -1666,6 +2331,23 @@ class DataMapperComponent {
|
|
|
1666
2331
|
mappingService = inject(MappingService);
|
|
1667
2332
|
svgConnectorService = inject(SvgConnectorService);
|
|
1668
2333
|
transformationService = inject(TransformationService);
|
|
2334
|
+
schemaParserService = inject(SchemaParserService);
|
|
2335
|
+
// Internal signals for schema inputs
|
|
2336
|
+
_sourceSchemaInput = signal(null, ...(ngDevMode ? [{ debugName: "_sourceSchemaInput" }] : []));
|
|
2337
|
+
_targetSchemaInput = signal(null, ...(ngDevMode ? [{ debugName: "_targetSchemaInput" }] : []));
|
|
2338
|
+
// Converted schemas for the tree component
|
|
2339
|
+
sourceSchemaForTree = computed(() => {
|
|
2340
|
+
const schema = this._sourceSchemaInput();
|
|
2341
|
+
if (!schema)
|
|
2342
|
+
return { name: '', fields: [] };
|
|
2343
|
+
return this.schemaParserService.parseSchema(schema, schema.title || 'Source');
|
|
2344
|
+
}, ...(ngDevMode ? [{ debugName: "sourceSchemaForTree" }] : []));
|
|
2345
|
+
targetSchemaForTree = computed(() => {
|
|
2346
|
+
const schema = this._targetSchemaInput();
|
|
2347
|
+
if (!schema)
|
|
2348
|
+
return { name: '', fields: [] };
|
|
2349
|
+
return this.schemaParserService.parseSchema(schema, schema.title || 'Target');
|
|
2350
|
+
}, ...(ngDevMode ? [{ debugName: "targetSchemaForTree" }] : []));
|
|
1669
2351
|
// Field positions from both trees
|
|
1670
2352
|
sourcePositions = new Map();
|
|
1671
2353
|
targetPositions = new Map();
|
|
@@ -1854,10 +2536,10 @@ class DataMapperComponent {
|
|
|
1854
2536
|
getExistingDefaultValue(fieldId) {
|
|
1855
2537
|
return this.mappingService.getDefaultValue(fieldId);
|
|
1856
2538
|
}
|
|
1857
|
-
onPopoverSave(
|
|
2539
|
+
onPopoverSave(transformations) {
|
|
1858
2540
|
const mappingId = this.selectedMappingId();
|
|
1859
2541
|
if (mappingId) {
|
|
1860
|
-
this.mappingService.
|
|
2542
|
+
this.mappingService.updateTransformations(mappingId, transformations);
|
|
1861
2543
|
this.mappingsChange.emit(this.mappingService.allMappings());
|
|
1862
2544
|
}
|
|
1863
2545
|
this.closePopover();
|
|
@@ -1922,7 +2604,7 @@ class DataMapperComponent {
|
|
|
1922
2604
|
paths,
|
|
1923
2605
|
midPoint,
|
|
1924
2606
|
targetPoint,
|
|
1925
|
-
hasTransformation: mapping.
|
|
2607
|
+
hasTransformation: mapping.transformations.length > 1 || mapping.transformations[0]?.type !== 'direct',
|
|
1926
2608
|
isSelected: mapping.id === selectedId,
|
|
1927
2609
|
isArrayMapping: mapping.isArrayMapping || false,
|
|
1928
2610
|
isArrayToObjectMapping: mapping.isArrayToObjectMapping || false,
|
|
@@ -1972,7 +2654,11 @@ class DataMapperComponent {
|
|
|
1972
2654
|
template: 'code',
|
|
1973
2655
|
custom: 'functions',
|
|
1974
2656
|
};
|
|
1975
|
-
|
|
2657
|
+
// For multiple transformations, show a pipeline icon
|
|
2658
|
+
if (mapping.transformations.length > 1) {
|
|
2659
|
+
return 'linear_scale';
|
|
2660
|
+
}
|
|
2661
|
+
return icons[mapping.transformations[0]?.type] || 'settings';
|
|
1976
2662
|
}
|
|
1977
2663
|
clearAllMappings() {
|
|
1978
2664
|
this.mappingService.clearAllMappings();
|
|
@@ -1991,7 +2677,7 @@ class DataMapperComponent {
|
|
|
1991
2677
|
return connection.id;
|
|
1992
2678
|
}
|
|
1993
2679
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1994
|
-
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]=\"sourceSchema\"\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]=\"targetSchema\"\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$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.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"] }] });
|
|
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"] }] });
|
|
1995
2681
|
}
|
|
1996
2682
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, decorators: [{
|
|
1997
2683
|
type: Component,
|
|
@@ -2005,7 +2691,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
2005
2691
|
ArrayFilterModalComponent,
|
|
2006
2692
|
ArraySelectorModalComponent,
|
|
2007
2693
|
DefaultValuePopoverComponent,
|
|
2008
|
-
], 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]=\"sourceSchema\"\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]=\"targetSchema\"\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"] }]
|
|
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"] }]
|
|
2009
2695
|
}], propDecorators: { sourceSchema: [{
|
|
2010
2696
|
type: Input
|
|
2011
2697
|
}], targetSchema: [{
|
|
@@ -2031,8 +2717,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
2031
2717
|
class SchemaEditorComponent {
|
|
2032
2718
|
set schema(value) {
|
|
2033
2719
|
if (value) {
|
|
2034
|
-
this.schemaName.set(value.
|
|
2035
|
-
this.fields.set(this.
|
|
2720
|
+
this.schemaName.set(value.title || 'New Schema');
|
|
2721
|
+
this.fields.set(this.jsonSchemaToEditorFields(value));
|
|
2036
2722
|
}
|
|
2037
2723
|
}
|
|
2038
2724
|
schemaChange = new EventEmitter();
|
|
@@ -2319,17 +3005,57 @@ class SchemaEditorComponent {
|
|
|
2319
3005
|
}
|
|
2320
3006
|
// Emit change event
|
|
2321
3007
|
emitChange() {
|
|
2322
|
-
this.schemaChange.emit(
|
|
2323
|
-
name: this.schemaName(),
|
|
2324
|
-
fields: this.fields(),
|
|
2325
|
-
});
|
|
3008
|
+
this.schemaChange.emit(this.toJsonSchema());
|
|
2326
3009
|
}
|
|
2327
3010
|
// Save the schema
|
|
2328
3011
|
onSave() {
|
|
2329
|
-
this.save.emit(
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
3012
|
+
this.save.emit(this.toJsonSchema());
|
|
3013
|
+
}
|
|
3014
|
+
// Convert JSON Schema to internal EditorField format
|
|
3015
|
+
jsonSchemaToEditorFields(schema, requiredFields = []) {
|
|
3016
|
+
const fields = [];
|
|
3017
|
+
if (schema.type === 'object' && schema.properties) {
|
|
3018
|
+
const required = schema.required || requiredFields;
|
|
3019
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
3020
|
+
fields.push(this.jsonSchemaPropertyToEditorField(name, propSchema, required.includes(name)));
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
return fields;
|
|
3024
|
+
}
|
|
3025
|
+
jsonSchemaPropertyToEditorField(name, schema, isRequired) {
|
|
3026
|
+
const field = {
|
|
3027
|
+
id: this.generateId(),
|
|
3028
|
+
name,
|
|
3029
|
+
type: this.jsonSchemaTypeToEditorType(schema),
|
|
3030
|
+
description: schema.description,
|
|
3031
|
+
required: isRequired,
|
|
3032
|
+
allowedValues: schema.enum,
|
|
3033
|
+
expanded: false,
|
|
3034
|
+
};
|
|
3035
|
+
if (schema.type === 'object' && schema.properties) {
|
|
3036
|
+
field.children = this.jsonSchemaToEditorFields(schema, schema.required);
|
|
3037
|
+
}
|
|
3038
|
+
else if (schema.type === 'array' && schema.items) {
|
|
3039
|
+
if (schema.items.type === 'object' && schema.items.properties) {
|
|
3040
|
+
field.children = this.jsonSchemaToEditorFields(schema.items, schema.items.required);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return field;
|
|
3044
|
+
}
|
|
3045
|
+
jsonSchemaTypeToEditorType(schema) {
|
|
3046
|
+
if (schema.format === 'date' || schema.format === 'date-time') {
|
|
3047
|
+
return 'date';
|
|
3048
|
+
}
|
|
3049
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
3050
|
+
switch (type) {
|
|
3051
|
+
case 'string': return 'string';
|
|
3052
|
+
case 'number':
|
|
3053
|
+
case 'integer': return 'number';
|
|
3054
|
+
case 'boolean': return 'boolean';
|
|
3055
|
+
case 'object': return 'object';
|
|
3056
|
+
case 'array': return 'array';
|
|
3057
|
+
default: return 'string';
|
|
3058
|
+
}
|
|
2333
3059
|
}
|
|
2334
3060
|
// Convert to internal JSON format
|
|
2335
3061
|
toJson() {
|
|
@@ -2438,7 +3164,7 @@ class SchemaEditorComponent {
|
|
|
2438
3164
|
return field.id;
|
|
2439
3165
|
}
|
|
2440
3166
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2441
|
-
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$1.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$1.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: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7$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: i7$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7$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: DragDropModule }] });
|
|
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 }] });
|
|
2442
3168
|
}
|
|
2443
3169
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, decorators: [{
|
|
2444
3170
|
type: Component,
|
|
@@ -2471,5 +3197,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
2471
3197
|
* Generated bundle index. Do not edit.
|
|
2472
3198
|
*/
|
|
2473
3199
|
|
|
2474
|
-
export { ArrayFilterModalComponent, ArraySelectorModalComponent, DataMapperComponent, DefaultValuePopoverComponent, MappingService, SchemaEditorComponent, SchemaParserService, SchemaTreeComponent, SvgConnectorService, TransformationPopoverComponent, TransformationService };
|
|
3200
|
+
export { ArrayFilterModalComponent, ArraySelectorModalComponent, DataMapperComponent, DefaultValuePopoverComponent, MappingService, SchemaEditorComponent, SchemaParserService, SchemaTreeComponent, SvgConnectorService, TransformationPopoverComponent, TransformationService, addProperty, createEmptySchema, getSchemaType, removeProperty, schemaToFields };
|
|
2475
3201
|
//# sourceMappingURL=expeed-ngx-data-mapper.mjs.map
|