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.
Files changed (64) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/LICENSE +190 -0
  3. package/PUBLISHING.md +75 -0
  4. package/README.md +214 -0
  5. package/angular.json +121 -0
  6. package/package.json +67 -0
  7. package/projects/demo-app/public/favicon.ico +0 -0
  8. package/projects/demo-app/src/app/app.config.ts +12 -0
  9. package/projects/demo-app/src/app/app.html +36 -0
  10. package/projects/demo-app/src/app/app.routes.ts +62 -0
  11. package/projects/demo-app/src/app/app.scss +65 -0
  12. package/projects/demo-app/src/app/app.ts +11 -0
  13. package/projects/demo-app/src/app/layout/app-layout.component.ts +294 -0
  14. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.html +87 -0
  15. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.scss +202 -0
  16. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.ts +192 -0
  17. package/projects/demo-app/src/app/pages/mappings-page/add-mapping-dialog.component.ts +163 -0
  18. package/projects/demo-app/src/app/pages/mappings-page/mappings-page.component.ts +306 -0
  19. package/projects/demo-app/src/app/pages/schema-creator-page/schema-creator-page.component.ts +88 -0
  20. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.html +108 -0
  21. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.scss +317 -0
  22. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.ts +129 -0
  23. package/projects/demo-app/src/app/services/app-state.service.ts +233 -0
  24. package/projects/demo-app/src/app/services/sample-data.service.ts +228 -0
  25. package/projects/demo-app/src/index.html +15 -0
  26. package/projects/demo-app/src/main.ts +6 -0
  27. package/projects/demo-app/src/styles.scss +54 -0
  28. package/projects/demo-app/tsconfig.app.json +13 -0
  29. package/projects/ngx-data-mapper/ng-package.json +7 -0
  30. package/projects/ngx-data-mapper/package.json +40 -0
  31. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.html +183 -0
  32. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.scss +352 -0
  33. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.ts +277 -0
  34. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.html +174 -0
  35. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.scss +357 -0
  36. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.ts +258 -0
  37. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.html +139 -0
  38. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.scss +213 -0
  39. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.ts +261 -0
  40. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.html +199 -0
  41. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.scss +321 -0
  42. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.ts +618 -0
  43. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.html +67 -0
  44. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.scss +97 -0
  45. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.ts +105 -0
  46. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.html +552 -0
  47. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.scss +824 -0
  48. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.ts +730 -0
  49. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.html +82 -0
  50. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.scss +352 -0
  51. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.ts +225 -0
  52. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.html +346 -0
  53. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.scss +511 -0
  54. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.ts +368 -0
  55. package/projects/ngx-data-mapper/src/lib/models/json-schema.model.ts +164 -0
  56. package/projects/ngx-data-mapper/src/lib/models/schema.model.ts +173 -0
  57. package/projects/ngx-data-mapper/src/lib/services/mapping.service.ts +615 -0
  58. package/projects/ngx-data-mapper/src/lib/services/schema-parser.service.ts +270 -0
  59. package/projects/ngx-data-mapper/src/lib/services/svg-connector.service.ts +135 -0
  60. package/projects/ngx-data-mapper/src/lib/services/transformation.service.ts +453 -0
  61. package/projects/ngx-data-mapper/src/public-api.ts +22 -0
  62. package/projects/ngx-data-mapper/tsconfig.lib.json +13 -0
  63. package/projects/ngx-data-mapper/tsconfig.lib.prod.json +9 -0
  64. package/tsconfig.json +28 -0
@@ -0,0 +1,306 @@
1
+ import { Component, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { Router } from '@angular/router';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import { MatIconModule } from '@angular/material/icon';
6
+ import { MatMenuModule } from '@angular/material/menu';
7
+ import { MatDialogModule, MatDialog } from '@angular/material/dialog';
8
+ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
9
+ import { MatTooltipModule } from '@angular/material/tooltip';
10
+ import { AppStateService, StoredMapping } from '../../services/app-state.service';
11
+ import { AddMappingDialogComponent } from './add-mapping-dialog.component';
12
+
13
+ @Component({
14
+ selector: 'mappings-page',
15
+ standalone: true,
16
+ imports: [
17
+ CommonModule,
18
+ MatButtonModule,
19
+ MatIconModule,
20
+ MatMenuModule,
21
+ MatDialogModule,
22
+ MatSnackBarModule,
23
+ MatTooltipModule,
24
+ ],
25
+ template: `
26
+ <div class="mappings-page">
27
+ <div class="page-header">
28
+ <div class="header-content">
29
+ <h1>Data Mappings</h1>
30
+ </div>
31
+ <button mat-flat-button color="primary" (click)="openAddDialog()">
32
+ <mat-icon>add</mat-icon>
33
+ New Mapping
34
+ </button>
35
+ </div>
36
+
37
+ <div class="mappings-content">
38
+ @if (appState.mappings().length === 0) {
39
+ <div class="empty-state">
40
+ <mat-icon>account_tree</mat-icon>
41
+ <h3>No mappings yet</h3>
42
+ <p>Create a mapping to define how data transforms between schemas</p>
43
+ <button mat-flat-button color="primary" (click)="openAddDialog()">
44
+ <mat-icon>add</mat-icon>
45
+ New Mapping
46
+ </button>
47
+ </div>
48
+ } @else {
49
+ <div class="mappings-list">
50
+ @for (mapping of appState.mappings(); track mapping.id) {
51
+ <div class="mapping-card">
52
+ <div class="mapping-info">
53
+ <div class="mapping-name">{{ mapping.name }}</div>
54
+ <div class="mapping-flow">
55
+ <span class="schema-name source">{{ appState.getSchemaName(mapping.sourceSchemaId) }}</span>
56
+ <mat-icon class="flow-arrow">arrow_forward</mat-icon>
57
+ <span class="schema-name target">{{ appState.getSchemaName(mapping.targetSchemaId) }}</span>
58
+ </div>
59
+ <div class="mapping-meta">
60
+ Updated {{ formatDate(mapping.updatedAt) }}
61
+ </div>
62
+ </div>
63
+ <div class="mapping-actions">
64
+ <button mat-icon-button (click)="editMapping(mapping)" matTooltip="Edit mapping">
65
+ <mat-icon>edit</mat-icon>
66
+ </button>
67
+ <button mat-icon-button [matMenuTriggerFor]="mappingMenu">
68
+ <mat-icon>more_vert</mat-icon>
69
+ </button>
70
+ <mat-menu #mappingMenu="matMenu">
71
+ <button mat-menu-item (click)="duplicateMapping(mapping)">
72
+ <mat-icon>content_copy</mat-icon>
73
+ <span>Duplicate</span>
74
+ </button>
75
+ <button mat-menu-item (click)="deleteMapping(mapping)" class="delete-action">
76
+ <mat-icon>delete</mat-icon>
77
+ <span>Delete</span>
78
+ </button>
79
+ </mat-menu>
80
+ </div>
81
+ </div>
82
+ }
83
+ </div>
84
+ }
85
+ </div>
86
+ </div>
87
+ `,
88
+ styles: [`
89
+ .mappings-page {
90
+ height: 100%;
91
+ display: flex;
92
+ flex-direction: column;
93
+ background: #f8fafc;
94
+ }
95
+
96
+ .page-header {
97
+ display: flex;
98
+ justify-content: space-between;
99
+ align-items: center;
100
+ padding: 20px 32px;
101
+ background: white;
102
+ border-bottom: 1px solid #e2e8f0;
103
+
104
+ h1 {
105
+ margin: 0;
106
+ font-size: 20px;
107
+ font-weight: 600;
108
+ color: #0f172a;
109
+ letter-spacing: -0.3px;
110
+ }
111
+ }
112
+
113
+ .mappings-content {
114
+ flex: 1;
115
+ overflow-y: auto;
116
+ padding: 24px 32px;
117
+ }
118
+
119
+ .empty-state {
120
+ display: flex;
121
+ flex-direction: column;
122
+ align-items: center;
123
+ justify-content: center;
124
+ padding: 80px 24px;
125
+ color: #94a3b8;
126
+ text-align: center;
127
+
128
+ > mat-icon {
129
+ font-size: 48px;
130
+ width: 48px;
131
+ height: 48px;
132
+ margin-bottom: 16px;
133
+ opacity: 0.3;
134
+ }
135
+
136
+ h3 {
137
+ margin: 0 0 8px;
138
+ font-size: 16px;
139
+ font-weight: 600;
140
+ color: #475569;
141
+ }
142
+
143
+ p {
144
+ margin: 0 0 20px;
145
+ font-size: 13px;
146
+ color: #64748b;
147
+ }
148
+ }
149
+
150
+ .mappings-list {
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: 8px;
154
+ }
155
+
156
+ .mapping-card {
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: space-between;
160
+ padding: 16px 20px;
161
+ background: white;
162
+ border-radius: 10px;
163
+ border: 1px solid #e2e8f0;
164
+ transition: all 0.12s ease;
165
+
166
+ &:hover {
167
+ border-color: #cbd5e1;
168
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
169
+ }
170
+ }
171
+
172
+ .mapping-info {
173
+ flex: 1;
174
+ }
175
+
176
+ .mapping-name {
177
+ font-size: 14px;
178
+ font-weight: 600;
179
+ color: #0f172a;
180
+ margin-bottom: 6px;
181
+ }
182
+
183
+ .mapping-flow {
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 8px;
187
+ margin-bottom: 4px;
188
+
189
+ .schema-name {
190
+ padding: 3px 8px;
191
+ border-radius: 4px;
192
+ font-size: 12px;
193
+ font-weight: 500;
194
+
195
+ &.source {
196
+ background: #eff6ff;
197
+ color: #2563eb;
198
+ }
199
+
200
+ &.target {
201
+ background: #f0fdf4;
202
+ color: #16a34a;
203
+ }
204
+ }
205
+
206
+ .flow-arrow {
207
+ font-size: 16px;
208
+ width: 16px;
209
+ height: 16px;
210
+ color: #cbd5e1;
211
+ }
212
+ }
213
+
214
+ .mapping-meta {
215
+ font-size: 11px;
216
+ color: #94a3b8;
217
+ }
218
+
219
+ .mapping-actions {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 2px;
223
+
224
+ button {
225
+ color: #94a3b8;
226
+ width: 32px;
227
+ height: 32px;
228
+
229
+ mat-icon {
230
+ font-size: 18px;
231
+ width: 18px;
232
+ height: 18px;
233
+ }
234
+
235
+ &:hover {
236
+ color: #475569;
237
+ }
238
+ }
239
+ }
240
+
241
+ .delete-action {
242
+ color: #ef4444 !important;
243
+
244
+ mat-icon {
245
+ color: #ef4444;
246
+ }
247
+ }
248
+ `],
249
+ })
250
+ export class MappingsPageComponent {
251
+ appState = inject(AppStateService);
252
+ private router = inject(Router);
253
+ private dialog = inject(MatDialog);
254
+ private snackBar = inject(MatSnackBar);
255
+
256
+ openAddDialog(): void {
257
+ const dialogRef = this.dialog.open(AddMappingDialogComponent, {
258
+ width: '450px',
259
+ data: { schemas: this.appState.schemas() },
260
+ });
261
+
262
+ dialogRef.afterClosed().subscribe(result => {
263
+ if (result) {
264
+ const newMapping = this.appState.addMapping({
265
+ name: result.name,
266
+ sourceSchemaId: result.sourceSchemaId,
267
+ targetSchemaId: result.targetSchemaId,
268
+ });
269
+ this.snackBar.open('Mapping created', 'Close', { duration: 2000 });
270
+ // Navigate to edit the new mapping
271
+ this.router.navigate(['/mapper', newMapping.id]);
272
+ }
273
+ });
274
+ }
275
+
276
+ editMapping(mapping: StoredMapping): void {
277
+ this.router.navigate(['/mapper', mapping.id]);
278
+ }
279
+
280
+ duplicateMapping(mapping: StoredMapping): void {
281
+ this.appState.addMapping({
282
+ name: mapping.name + ' (copy)',
283
+ sourceSchemaId: mapping.sourceSchemaId,
284
+ targetSchemaId: mapping.targetSchemaId,
285
+ mappingData: mapping.mappingData,
286
+ });
287
+ this.snackBar.open('Mapping duplicated', 'Close', { duration: 2000 });
288
+ }
289
+
290
+ deleteMapping(mapping: StoredMapping): void {
291
+ this.appState.deleteMapping(mapping.id);
292
+ this.snackBar.open('Mapping deleted', 'Close', { duration: 2000 });
293
+ }
294
+
295
+ formatDate(dateStr: string): string {
296
+ const date = new Date(dateStr);
297
+ const now = new Date();
298
+ const diff = now.getTime() - date.getTime();
299
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
300
+
301
+ if (days === 0) return 'today';
302
+ if (days === 1) return 'yesterday';
303
+ if (days < 7) return `${days} days ago`;
304
+ return date.toLocaleDateString();
305
+ }
306
+ }
@@ -0,0 +1,88 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { MatButtonModule } from '@angular/material/button';
4
+ import { MatIconModule } from '@angular/material/icon';
5
+ import { SchemaEditorComponent, JsonSchema } from '@expeed/ngx-data-mapper';
6
+
7
+ @Component({
8
+ selector: 'schema-creator-page',
9
+ standalone: true,
10
+ imports: [
11
+ CommonModule,
12
+ MatButtonModule,
13
+ MatIconModule,
14
+ SchemaEditorComponent,
15
+ ],
16
+ template: `
17
+ <div class="page-container">
18
+ <header class="page-header">
19
+ <h1>Schema Creator</h1>
20
+ <p class="subtitle">Create schemas without schema name field</p>
21
+ </header>
22
+
23
+ <main class="page-main">
24
+ <div class="editor-wrapper">
25
+ <schema-editor
26
+ [schema]="currentSchema()"
27
+ [showSchemaName]="false"
28
+ (schemaChange)="onSchemaChange($event)"
29
+ ></schema-editor>
30
+ </div>
31
+ </main>
32
+ </div>
33
+ `,
34
+ styles: [`
35
+ .page-container {
36
+ display: flex;
37
+ flex-direction: column;
38
+ height: 100%;
39
+ background: #f8fafc;
40
+ }
41
+
42
+ .page-header {
43
+ padding: 24px 32px;
44
+ background: white;
45
+ border-bottom: 1px solid #e2e8f0;
46
+
47
+ h1 {
48
+ margin: 0;
49
+ font-size: 24px;
50
+ font-weight: 600;
51
+ color: #1e293b;
52
+ }
53
+
54
+ .subtitle {
55
+ margin: 4px 0 0;
56
+ font-size: 14px;
57
+ color: #64748b;
58
+ }
59
+ }
60
+
61
+ .page-main {
62
+ flex: 1;
63
+ padding: 24px 32px;
64
+ overflow: auto;
65
+ }
66
+
67
+ .editor-wrapper {
68
+ background: white;
69
+ border-radius: 12px;
70
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
71
+ padding: 24px;
72
+ max-width: 1200px;
73
+ }
74
+ `],
75
+ })
76
+ export class SchemaCreatorPageComponent {
77
+ currentSchema = signal<JsonSchema>({
78
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
79
+ type: 'object',
80
+ title: 'NewSchema',
81
+ properties: {},
82
+ required: [],
83
+ });
84
+
85
+ onSchemaChange(updated: JsonSchema): void {
86
+ this.currentSchema.set(updated);
87
+ }
88
+ }
@@ -0,0 +1,108 @@
1
+ <!--
2
+ Schema Editor Page - Example usage of schema-editor component
3
+
4
+ This example shows:
5
+ - Managing a list of schemas with selection
6
+ - Binding to the schema editor's inputs/outputs
7
+ - Custom styling via CSS variables (see .scss file)
8
+ -->
9
+ <div class="page-container">
10
+ <header class="page-header">
11
+ <div class="header-left">
12
+ <h1>Schema Editor</h1>
13
+ </div>
14
+ <p class="subtitle">Create and manage reusable object schemas</p>
15
+ </header>
16
+
17
+ <main class="page-main">
18
+ <!-- Schema List Panel -->
19
+ <div class="list-panel">
20
+ <div class="list-header">
21
+ <span class="list-title">Schemas</span>
22
+ <button mat-flat-button color="primary" class="add-schema-btn" (click)="createNewSchema()">
23
+ + Add Schema
24
+ </button>
25
+ </div>
26
+
27
+ <div class="list-content">
28
+ @if (appState.schemas().length === 0) {
29
+ <div class="empty-list">
30
+ <mat-icon>folder_open</mat-icon>
31
+ <p>No schemas yet</p>
32
+ <button mat-stroked-button (click)="createNewSchema()">
33
+ <mat-icon>add</mat-icon>
34
+ Create First Schema
35
+ </button>
36
+ </div>
37
+ } @else {
38
+ <div class="schema-list">
39
+ @for (schema of appState.schemas(); track schema.id) {
40
+ <div
41
+ class="schema-item"
42
+ [class.active]="selectedSchemaId() === schema.id"
43
+ (click)="selectSchema(schema.id)"
44
+ >
45
+ <mat-icon class="schema-icon">data_object</mat-icon>
46
+ <div class="schema-info">
47
+ <span class="schema-name">{{ schema.title }}</span>
48
+ <span class="schema-meta">{{ getPropertyCount(schema) }} field(s)</span>
49
+ </div>
50
+ <button
51
+ mat-icon-button
52
+ class="schema-menu-btn"
53
+ [matMenuTriggerFor]="schemaMenu"
54
+ (click)="$event.stopPropagation()"
55
+ >
56
+ <mat-icon>more_vert</mat-icon>
57
+ </button>
58
+ <mat-menu #schemaMenu="matMenu">
59
+ <button mat-menu-item (click)="duplicateSchema(schema)">
60
+ <mat-icon>content_copy</mat-icon>
61
+ <span>Duplicate</span>
62
+ </button>
63
+ <button mat-menu-item (click)="exportSchema(schema)">
64
+ <mat-icon>download</mat-icon>
65
+ <span>Export JSON Schema</span>
66
+ </button>
67
+ <button mat-menu-item (click)="deleteSchema(schema.id)" class="delete-action">
68
+ <mat-icon>delete</mat-icon>
69
+ <span>Delete</span>
70
+ </button>
71
+ </mat-menu>
72
+ </div>
73
+ }
74
+ </div>
75
+ }
76
+ </div>
77
+
78
+ <div class="list-footer">
79
+ <input
80
+ #importInput
81
+ type="file"
82
+ accept=".json"
83
+ style="display: none"
84
+ (change)="importSchema($event)"
85
+ />
86
+ <button mat-stroked-button (click)="importInput.click()" class="import-btn">
87
+ <mat-icon>upload</mat-icon>
88
+ Import
89
+ </button>
90
+ </div>
91
+ </div>
92
+
93
+ <!-- Editor Panel -->
94
+ <div class="editor-panel">
95
+ @if (selectedSchema()) {
96
+ <schema-editor
97
+ [schema]="selectedSchema()!"
98
+ (schemaChange)="onSchemaChange($event)"
99
+ ></schema-editor>
100
+ } @else {
101
+ <div class="no-selection">
102
+ <mat-icon>touch_app</mat-icon>
103
+ <p>Select a schema to edit or create a new one</p>
104
+ </div>
105
+ }
106
+ </div>
107
+ </main>
108
+ </div>