angular-data-mapper 1.0.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.
- package/.claude/settings.local.json +10 -0
- package/LICENSE +190 -0
- package/PUBLISHING.md +75 -0
- package/README.md +214 -0
- package/angular.json +121 -0
- package/package.json +67 -0
- package/projects/demo-app/public/favicon.ico +0 -0
- package/projects/demo-app/src/app/app.config.ts +12 -0
- package/projects/demo-app/src/app/app.html +36 -0
- package/projects/demo-app/src/app/app.routes.ts +62 -0
- package/projects/demo-app/src/app/app.scss +65 -0
- package/projects/demo-app/src/app/app.ts +11 -0
- package/projects/demo-app/src/app/layout/app-layout.component.ts +294 -0
- package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.html +87 -0
- package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.scss +202 -0
- package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.ts +192 -0
- package/projects/demo-app/src/app/pages/mappings-page/add-mapping-dialog.component.ts +163 -0
- package/projects/demo-app/src/app/pages/mappings-page/mappings-page.component.ts +306 -0
- package/projects/demo-app/src/app/pages/schema-creator-page/schema-creator-page.component.ts +88 -0
- package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.html +108 -0
- package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.scss +317 -0
- package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.ts +129 -0
- package/projects/demo-app/src/app/services/app-state.service.ts +233 -0
- package/projects/demo-app/src/app/services/sample-data.service.ts +228 -0
- package/projects/demo-app/src/index.html +15 -0
- package/projects/demo-app/src/main.ts +6 -0
- package/projects/demo-app/src/styles.scss +54 -0
- package/projects/demo-app/tsconfig.app.json +13 -0
- package/projects/ngx-data-mapper/ng-package.json +7 -0
- package/projects/ngx-data-mapper/package.json +40 -0
- package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.html +183 -0
- package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.scss +352 -0
- package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.ts +277 -0
- package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.html +174 -0
- package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.scss +357 -0
- package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.ts +258 -0
- package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.html +139 -0
- package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.scss +213 -0
- package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.ts +261 -0
- package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.html +199 -0
- package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.scss +321 -0
- package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.ts +618 -0
- package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.html +67 -0
- package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.scss +97 -0
- package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.ts +105 -0
- package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.html +552 -0
- package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.scss +824 -0
- package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.ts +730 -0
- package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.html +82 -0
- package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.scss +352 -0
- package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.ts +225 -0
- package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.html +346 -0
- package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.scss +511 -0
- package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.ts +368 -0
- package/projects/ngx-data-mapper/src/lib/models/json-schema.model.ts +164 -0
- package/projects/ngx-data-mapper/src/lib/models/schema.model.ts +173 -0
- package/projects/ngx-data-mapper/src/lib/services/mapping.service.ts +615 -0
- package/projects/ngx-data-mapper/src/lib/services/schema-parser.service.ts +270 -0
- package/projects/ngx-data-mapper/src/lib/services/svg-connector.service.ts +135 -0
- package/projects/ngx-data-mapper/src/lib/services/transformation.service.ts +453 -0
- package/projects/ngx-data-mapper/src/public-api.ts +22 -0
- package/projects/ngx-data-mapper/tsconfig.lib.json +13 -0
- package/projects/ngx-data-mapper/tsconfig.lib.prod.json +9 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapper Page Styles
|
|
3
|
+
*
|
|
4
|
+
* Theme Examples:
|
|
5
|
+
*
|
|
6
|
+
* DARK THEME (currently applied below):
|
|
7
|
+
* data-mapper {
|
|
8
|
+
* --data-mapper-bg: #1e293b;
|
|
9
|
+
* --data-mapper-panel-bg: #334155;
|
|
10
|
+
* --data-mapper-text-primary: #f1f5f9;
|
|
11
|
+
* --data-mapper-accent-primary: #818cf8;
|
|
12
|
+
* --data-mapper-connector-color: #818cf8;
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* LIGHT THEME:
|
|
16
|
+
* data-mapper {
|
|
17
|
+
* --data-mapper-bg: #f8fafc;
|
|
18
|
+
* --data-mapper-panel-bg: #ffffff;
|
|
19
|
+
* --data-mapper-panel-header-bg: #f1f5f9;
|
|
20
|
+
* --data-mapper-text-primary: #1e293b;
|
|
21
|
+
* --data-mapper-text-secondary: #64748b;
|
|
22
|
+
* --data-mapper-text-muted: #94a3b8;
|
|
23
|
+
* --data-mapper-accent-primary: #6366f1;
|
|
24
|
+
* --data-mapper-connector-color: #6366f1;
|
|
25
|
+
* --data-mapper-border-color: #e2e8f0;
|
|
26
|
+
* --data-mapper-toolbar-bg: #ffffff;
|
|
27
|
+
* --data-mapper-toolbar-border: #e2e8f0;
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* GREEN ACCENT THEME:
|
|
31
|
+
* data-mapper {
|
|
32
|
+
* --data-mapper-bg: #f0fdf4;
|
|
33
|
+
* --data-mapper-panel-bg: #ffffff;
|
|
34
|
+
* --data-mapper-accent-primary: #10b981;
|
|
35
|
+
* --data-mapper-connector-color: #10b981;
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
.page-container {
|
|
40
|
+
height: 100%;
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
background: #f8fafc;
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.page-header {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
padding: 20px 32px;
|
|
53
|
+
background: white;
|
|
54
|
+
border-bottom: 1px solid #e2e8f0;
|
|
55
|
+
color: #1e293b;
|
|
56
|
+
flex-shrink: 0;
|
|
57
|
+
|
|
58
|
+
.header-title {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: flex-start;
|
|
61
|
+
gap: 12px;
|
|
62
|
+
|
|
63
|
+
button {
|
|
64
|
+
color: #64748b;
|
|
65
|
+
margin-top: 4px;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.title-text {
|
|
70
|
+
.name-display {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 4px;
|
|
74
|
+
|
|
75
|
+
h1 {
|
|
76
|
+
font-size: 20px;
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
margin: 0;
|
|
79
|
+
color: #0f172a;
|
|
80
|
+
letter-spacing: -0.3px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.edit-btn {
|
|
84
|
+
opacity: 0;
|
|
85
|
+
transition: opacity 0.15s;
|
|
86
|
+
width: 28px;
|
|
87
|
+
height: 28px;
|
|
88
|
+
color: #94a3b8;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
|
|
93
|
+
mat-icon {
|
|
94
|
+
font-size: 16px;
|
|
95
|
+
width: 16px;
|
|
96
|
+
height: 16px;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&:hover {
|
|
103
|
+
color: #64748b;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&:hover .edit-btn {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.name-edit {
|
|
113
|
+
.name-input {
|
|
114
|
+
font-size: 20px;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
color: #0f172a;
|
|
117
|
+
letter-spacing: -0.3px;
|
|
118
|
+
border: 1px solid #6366f1;
|
|
119
|
+
border-radius: 6px;
|
|
120
|
+
padding: 4px 8px;
|
|
121
|
+
outline: none;
|
|
122
|
+
background: white;
|
|
123
|
+
|
|
124
|
+
&:focus {
|
|
125
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
h1 {
|
|
131
|
+
font-size: 20px;
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
margin: 0;
|
|
134
|
+
color: #0f172a;
|
|
135
|
+
letter-spacing: -0.3px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.subtitle {
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: 6px;
|
|
142
|
+
font-size: 13px;
|
|
143
|
+
color: #64748b;
|
|
144
|
+
margin: 2px 0 0;
|
|
145
|
+
|
|
146
|
+
.flow-icon {
|
|
147
|
+
font-size: 14px;
|
|
148
|
+
width: 14px;
|
|
149
|
+
height: 14px;
|
|
150
|
+
color: #cbd5e1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.header-actions {
|
|
156
|
+
display: flex;
|
|
157
|
+
gap: 12px;
|
|
158
|
+
flex-wrap: wrap;
|
|
159
|
+
|
|
160
|
+
button {
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: 8px;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.page-main {
|
|
169
|
+
flex: 1;
|
|
170
|
+
display: flex;
|
|
171
|
+
flex-direction: column;
|
|
172
|
+
min-height: 0;
|
|
173
|
+
overflow: hidden;
|
|
174
|
+
padding: 24px;
|
|
175
|
+
|
|
176
|
+
data-mapper {
|
|
177
|
+
flex: 1;
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-direction: column;
|
|
180
|
+
min-height: 0;
|
|
181
|
+
background: white;
|
|
182
|
+
border-radius: 12px;
|
|
183
|
+
border: 1px solid #e2e8f0;
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
|
|
186
|
+
// Light theme customization
|
|
187
|
+
--data-mapper-bg: white;
|
|
188
|
+
--data-mapper-panel-bg: white;
|
|
189
|
+
--data-mapper-panel-header-bg: #f8fafc;
|
|
190
|
+
--data-mapper-panel-border: 1px solid #e2e8f0;
|
|
191
|
+
--data-mapper-panel-border-radius: 10px;
|
|
192
|
+
--data-mapper-panel-shadow: none;
|
|
193
|
+
--data-mapper-text-primary: #1e293b;
|
|
194
|
+
--data-mapper-text-secondary: #64748b;
|
|
195
|
+
--data-mapper-text-muted: #94a3b8;
|
|
196
|
+
--data-mapper-accent-primary: #6366f1;
|
|
197
|
+
--data-mapper-connector-color: #6366f1;
|
|
198
|
+
--data-mapper-border-color: #e2e8f0;
|
|
199
|
+
--data-mapper-toolbar-bg: white;
|
|
200
|
+
--data-mapper-toolbar-border: #e2e8f0;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapper Page - Example usage of the DataMapperComponent
|
|
3
|
+
*
|
|
4
|
+
* This page demonstrates how to:
|
|
5
|
+
* 1. Use the data-mapper component
|
|
6
|
+
* 2. Provide source and target schemas
|
|
7
|
+
* 3. Handle mapping changes
|
|
8
|
+
* 4. Export/import mappings
|
|
9
|
+
* 5. Apply custom styling via CSS variables
|
|
10
|
+
*/
|
|
11
|
+
import { Component, inject, OnInit, signal, computed, ViewChild, ElementRef } from '@angular/core';
|
|
12
|
+
import { Router, ActivatedRoute } from '@angular/router';
|
|
13
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
14
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
15
|
+
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
|
16
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
17
|
+
import { DataMapperComponent, SchemaDocument, FieldMapping } from '@expeed/ngx-data-mapper';
|
|
18
|
+
import { AppStateService } from '../../services/app-state.service';
|
|
19
|
+
import { SampleDataService } from '../../services/sample-data.service';
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'mapper-page',
|
|
23
|
+
standalone: true,
|
|
24
|
+
imports: [
|
|
25
|
+
DataMapperComponent, // Import the reusable component
|
|
26
|
+
MatButtonModule,
|
|
27
|
+
MatIconModule,
|
|
28
|
+
MatSnackBarModule,
|
|
29
|
+
MatTooltipModule,
|
|
30
|
+
],
|
|
31
|
+
templateUrl: './mapper-page.component.html',
|
|
32
|
+
styleUrl: './mapper-page.component.scss',
|
|
33
|
+
})
|
|
34
|
+
export class MapperPageComponent implements OnInit {
|
|
35
|
+
private sampleDataService = inject(SampleDataService);
|
|
36
|
+
private snackBar = inject(MatSnackBar);
|
|
37
|
+
private router = inject(Router);
|
|
38
|
+
private route = inject(ActivatedRoute);
|
|
39
|
+
appState = inject(AppStateService);
|
|
40
|
+
|
|
41
|
+
@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;
|
|
42
|
+
@ViewChild(DataMapperComponent) dataMapper!: DataMapperComponent;
|
|
43
|
+
|
|
44
|
+
// Current mapping ID from route
|
|
45
|
+
mappingId = signal<string | null>(null);
|
|
46
|
+
|
|
47
|
+
// Computed: current mapping
|
|
48
|
+
currentMapping = computed(() => {
|
|
49
|
+
const id = this.mappingId();
|
|
50
|
+
return this.appState.mappings().find(m => m.id === id) || null;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// State: schemas and mappings
|
|
54
|
+
sourceSchema = signal<SchemaDocument>({ type: 'object', title: '', properties: {} });
|
|
55
|
+
targetSchema = signal<SchemaDocument>({ type: 'object', title: '', properties: {} });
|
|
56
|
+
sampleData = signal<Record<string, unknown>>({});
|
|
57
|
+
mappings = signal<FieldMapping[]>([]);
|
|
58
|
+
|
|
59
|
+
// Name editing
|
|
60
|
+
isEditingName = signal(false);
|
|
61
|
+
|
|
62
|
+
startEditName(): void {
|
|
63
|
+
this.isEditingName.set(true);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
cancelEditName(): void {
|
|
67
|
+
this.isEditingName.set(false);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
saveName(newName: string): void {
|
|
71
|
+
const trimmed = newName.trim();
|
|
72
|
+
const id = this.mappingId();
|
|
73
|
+
if (id && trimmed) {
|
|
74
|
+
this.appState.updateMapping(id, { name: trimmed });
|
|
75
|
+
}
|
|
76
|
+
this.isEditingName.set(false);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ngOnInit(): void {
|
|
80
|
+
// Get mapping ID from route
|
|
81
|
+
const id = this.route.snapshot.paramMap.get('id');
|
|
82
|
+
this.mappingId.set(id);
|
|
83
|
+
|
|
84
|
+
if (id) {
|
|
85
|
+
// Load schemas based on the mapping's source and target schema IDs
|
|
86
|
+
const mapping = this.appState.mappings().find(m => m.id === id);
|
|
87
|
+
if (mapping) {
|
|
88
|
+
const sourceSchema = this.appState.schemas().find(s => s.id === mapping.sourceSchemaId);
|
|
89
|
+
const targetSchema = this.appState.schemas().find(s => s.id === mapping.targetSchemaId);
|
|
90
|
+
|
|
91
|
+
if (sourceSchema) {
|
|
92
|
+
const { id: _, ...schema } = sourceSchema;
|
|
93
|
+
this.sourceSchema.set(schema as SchemaDocument);
|
|
94
|
+
}
|
|
95
|
+
if (targetSchema) {
|
|
96
|
+
const { id: _, ...schema } = targetSchema;
|
|
97
|
+
this.targetSchema.set(schema as SchemaDocument);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Load existing mapping data if available
|
|
101
|
+
if (mapping.mappingData) {
|
|
102
|
+
this.mappings.set(mapping.mappingData as FieldMapping[]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Fallback to sample data for demo
|
|
107
|
+
this.sourceSchema.set(this.sampleDataService.getSourceSchema());
|
|
108
|
+
this.targetSchema.set(this.sampleDataService.getTargetSchema());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.sampleData.set(this.sampleDataService.getSampleData());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Handle mapping changes from the data mapper component
|
|
116
|
+
* This is called whenever the user creates, modifies, or deletes a mapping
|
|
117
|
+
*/
|
|
118
|
+
onMappingsChange(mappings: FieldMapping[]): void {
|
|
119
|
+
this.mappings.set(mappings);
|
|
120
|
+
|
|
121
|
+
// Save mapping data to AppStateService
|
|
122
|
+
const id = this.mappingId();
|
|
123
|
+
if (id) {
|
|
124
|
+
this.appState.updateMappingData(id, mappings);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- Navigation ---
|
|
129
|
+
|
|
130
|
+
goBack(): void {
|
|
131
|
+
this.router.navigate(['/mappings']);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Export/Import ---
|
|
135
|
+
|
|
136
|
+
/** Export mappings to JSON file */
|
|
137
|
+
exportMappings(): void {
|
|
138
|
+
const json = this.dataMapper.exportMappings();
|
|
139
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
140
|
+
const url = URL.createObjectURL(blob);
|
|
141
|
+
|
|
142
|
+
const link = document.createElement('a');
|
|
143
|
+
link.href = url;
|
|
144
|
+
link.download = 'mappings.json';
|
|
145
|
+
link.click();
|
|
146
|
+
|
|
147
|
+
URL.revokeObjectURL(url);
|
|
148
|
+
|
|
149
|
+
this.snackBar.open('Mappings exported to file', 'Close', {
|
|
150
|
+
duration: 3000,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Open file dialog for import */
|
|
155
|
+
importMappings(): void {
|
|
156
|
+
this.fileInput.nativeElement.click();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Handle file selection for import */
|
|
160
|
+
onFileSelected(event: Event): void {
|
|
161
|
+
const input = event.target as HTMLInputElement;
|
|
162
|
+
if (!input.files?.length) return;
|
|
163
|
+
|
|
164
|
+
const file = input.files[0];
|
|
165
|
+
const reader = new FileReader();
|
|
166
|
+
|
|
167
|
+
reader.onload = () => {
|
|
168
|
+
try {
|
|
169
|
+
const json = reader.result as string;
|
|
170
|
+
this.dataMapper.importMappings(json);
|
|
171
|
+
this.snackBar.open('Mappings imported successfully', 'Close', {
|
|
172
|
+
duration: 3000,
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
this.snackBar.open('Failed to import mappings: invalid file', 'Close', {
|
|
176
|
+
duration: 3000,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
reader.readAsText(file);
|
|
182
|
+
input.value = '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Clear all mappings */
|
|
186
|
+
clearMappings(): void {
|
|
187
|
+
this.dataMapper.clearAllMappings();
|
|
188
|
+
this.snackBar.open('All mappings cleared', 'Close', {
|
|
189
|
+
duration: 2000,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Component, inject } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
|
5
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
6
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
7
|
+
import { MatInputModule } from '@angular/material/input';
|
|
8
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
9
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
10
|
+
import { StoredSchema } from '../../services/app-state.service';
|
|
11
|
+
|
|
12
|
+
export interface AddMappingDialogData {
|
|
13
|
+
schemas: StoredSchema[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AddMappingResult {
|
|
17
|
+
name: string;
|
|
18
|
+
sourceSchemaId: string;
|
|
19
|
+
targetSchemaId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'add-mapping-dialog',
|
|
24
|
+
standalone: true,
|
|
25
|
+
imports: [
|
|
26
|
+
CommonModule,
|
|
27
|
+
FormsModule,
|
|
28
|
+
MatDialogModule,
|
|
29
|
+
MatButtonModule,
|
|
30
|
+
MatFormFieldModule,
|
|
31
|
+
MatInputModule,
|
|
32
|
+
MatSelectModule,
|
|
33
|
+
MatIconModule,
|
|
34
|
+
],
|
|
35
|
+
template: `
|
|
36
|
+
<h2 mat-dialog-title>Create New Mapping</h2>
|
|
37
|
+
|
|
38
|
+
<mat-dialog-content>
|
|
39
|
+
<div class="form-fields">
|
|
40
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
41
|
+
<mat-label>Mapping Name</mat-label>
|
|
42
|
+
<input matInput [(ngModel)]="name" placeholder="e.g., Contact to Salesforce Lead">
|
|
43
|
+
</mat-form-field>
|
|
44
|
+
|
|
45
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
46
|
+
<mat-label>Source Schema</mat-label>
|
|
47
|
+
<mat-select [(ngModel)]="sourceSchemaId">
|
|
48
|
+
@for (schema of data.schemas; track schema.id) {
|
|
49
|
+
<mat-option [value]="schema.id">
|
|
50
|
+
<mat-icon>schema</mat-icon>
|
|
51
|
+
{{ schema.title }}
|
|
52
|
+
</mat-option>
|
|
53
|
+
}
|
|
54
|
+
</mat-select>
|
|
55
|
+
<mat-hint>The schema to map from</mat-hint>
|
|
56
|
+
</mat-form-field>
|
|
57
|
+
|
|
58
|
+
<div class="arrow-divider">
|
|
59
|
+
<mat-icon>arrow_downward</mat-icon>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
63
|
+
<mat-label>Target Schema</mat-label>
|
|
64
|
+
<mat-select [(ngModel)]="targetSchemaId">
|
|
65
|
+
@for (schema of data.schemas; track schema.id) {
|
|
66
|
+
<mat-option [value]="schema.id">
|
|
67
|
+
<mat-icon>schema</mat-icon>
|
|
68
|
+
{{ schema.title }}
|
|
69
|
+
</mat-option>
|
|
70
|
+
}
|
|
71
|
+
</mat-select>
|
|
72
|
+
<mat-hint>The schema to map to</mat-hint>
|
|
73
|
+
</mat-form-field>
|
|
74
|
+
|
|
75
|
+
@if (data.schemas.length === 0) {
|
|
76
|
+
<div class="no-schemas-warning">
|
|
77
|
+
<mat-icon>warning</mat-icon>
|
|
78
|
+
<span>No schemas available. Please create schemas first.</span>
|
|
79
|
+
</div>
|
|
80
|
+
}
|
|
81
|
+
</div>
|
|
82
|
+
</mat-dialog-content>
|
|
83
|
+
|
|
84
|
+
<mat-dialog-actions align="end">
|
|
85
|
+
<button mat-button mat-dialog-close>Cancel</button>
|
|
86
|
+
<button
|
|
87
|
+
mat-flat-button
|
|
88
|
+
color="primary"
|
|
89
|
+
[disabled]="!isValid()"
|
|
90
|
+
(click)="create()"
|
|
91
|
+
>
|
|
92
|
+
Create Mapping
|
|
93
|
+
</button>
|
|
94
|
+
</mat-dialog-actions>
|
|
95
|
+
`,
|
|
96
|
+
styles: [`
|
|
97
|
+
.form-fields {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
min-width: 380px;
|
|
102
|
+
padding-top: 8px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.full-width {
|
|
106
|
+
width: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.arrow-divider {
|
|
110
|
+
display: flex;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
padding: 8px 0;
|
|
113
|
+
color: #94a3b8;
|
|
114
|
+
|
|
115
|
+
mat-icon {
|
|
116
|
+
font-size: 24px;
|
|
117
|
+
width: 24px;
|
|
118
|
+
height: 24px;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.no-schemas-warning {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 8px;
|
|
126
|
+
padding: 12px 16px;
|
|
127
|
+
background: #fef3c7;
|
|
128
|
+
border-radius: 8px;
|
|
129
|
+
color: #92400e;
|
|
130
|
+
font-size: 14px;
|
|
131
|
+
|
|
132
|
+
mat-icon {
|
|
133
|
+
color: #f59e0b;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
mat-dialog-actions {
|
|
138
|
+
padding: 16px 24px;
|
|
139
|
+
}
|
|
140
|
+
`],
|
|
141
|
+
})
|
|
142
|
+
export class AddMappingDialogComponent {
|
|
143
|
+
private dialogRef = inject(MatDialogRef<AddMappingDialogComponent>);
|
|
144
|
+
data = inject<AddMappingDialogData>(MAT_DIALOG_DATA);
|
|
145
|
+
|
|
146
|
+
name = '';
|
|
147
|
+
sourceSchemaId = '';
|
|
148
|
+
targetSchemaId = '';
|
|
149
|
+
|
|
150
|
+
isValid(): boolean {
|
|
151
|
+
return !!(this.name.trim() && this.sourceSchemaId && this.targetSchemaId);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
create(): void {
|
|
155
|
+
if (this.isValid()) {
|
|
156
|
+
this.dialogRef.close({
|
|
157
|
+
name: this.name.trim(),
|
|
158
|
+
sourceSchemaId: this.sourceSchemaId,
|
|
159
|
+
targetSchemaId: this.targetSchemaId,
|
|
160
|
+
} as AddMappingResult);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|