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,36 @@
1
+ <div class="app-container">
2
+ <header class="app-header">
3
+ <h1>Data Mapper</h1>
4
+ <p class="subtitle">Drag fields from source to target to create mappings</p>
5
+ <div class="header-actions">
6
+ <button mat-stroked-button (click)="importMappings()">
7
+ <mat-icon>upload</mat-icon>
8
+ Import
9
+ </button>
10
+ <button mat-stroked-button (click)="exportMappings()" [disabled]="mappings().length === 0">
11
+ <mat-icon>download</mat-icon>
12
+ Export
13
+ </button>
14
+ <button mat-stroked-button (click)="clearMappings()" [disabled]="mappings().length === 0">
15
+ <mat-icon>delete_sweep</mat-icon>
16
+ Clear
17
+ </button>
18
+ </div>
19
+ <input
20
+ #fileInput
21
+ type="file"
22
+ accept=".json"
23
+ style="display: none"
24
+ (change)="onFileSelected($event)"
25
+ />
26
+ </header>
27
+
28
+ <main class="app-main">
29
+ <data-mapper
30
+ [sourceSchema]="sourceSchema()"
31
+ [targetSchema]="targetSchema()"
32
+ [sampleData]="sampleData()"
33
+ (mappingsChange)="onMappingsChange($event)"
34
+ ></data-mapper>
35
+ </main>
36
+ </div>
@@ -0,0 +1,62 @@
1
+ import { Routes } from '@angular/router';
2
+ import { AppLayoutComponent } from './layout/app-layout.component';
3
+
4
+ export const routes: Routes = [
5
+ {
6
+ path: '',
7
+ component: AppLayoutComponent,
8
+ children: [
9
+ {
10
+ path: '',
11
+ redirectTo: 'schemas',
12
+ pathMatch: 'full',
13
+ },
14
+ {
15
+ path: 'schemas',
16
+ loadComponent: () =>
17
+ import('./pages/schema-editor-page/schema-editor-page.component').then(
18
+ (m) => m.SchemaEditorPageComponent
19
+ ),
20
+ },
21
+ {
22
+ path: 'schemas/:id',
23
+ loadComponent: () =>
24
+ import('./pages/schema-editor-page/schema-editor-page.component').then(
25
+ (m) => m.SchemaEditorPageComponent
26
+ ),
27
+ },
28
+ {
29
+ path: 'mappings',
30
+ loadComponent: () =>
31
+ import('./pages/mappings-page/mappings-page.component').then(
32
+ (m) => m.MappingsPageComponent
33
+ ),
34
+ },
35
+ {
36
+ path: 'schema-creator',
37
+ loadComponent: () =>
38
+ import('./pages/schema-creator-page/schema-creator-page.component').then(
39
+ (m) => m.SchemaCreatorPageComponent
40
+ ),
41
+ },
42
+ {
43
+ path: 'mapper/:id',
44
+ loadComponent: () =>
45
+ import('./pages/mapper-page/mapper-page.component').then(
46
+ (m) => m.MapperPageComponent
47
+ ),
48
+ },
49
+ ],
50
+ },
51
+ // Legacy route redirect
52
+ {
53
+ path: 'mapper',
54
+ redirectTo: 'mappings',
55
+ pathMatch: 'full',
56
+ },
57
+ {
58
+ path: 'schema',
59
+ redirectTo: 'schemas',
60
+ pathMatch: 'full',
61
+ },
62
+ ];
@@ -0,0 +1,65 @@
1
+ .app-container {
2
+ height: 100vh;
3
+ display: flex;
4
+ flex-direction: column;
5
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
6
+ padding: 24px;
7
+ box-sizing: border-box;
8
+ overflow: hidden;
9
+ }
10
+
11
+ .app-header {
12
+ text-align: center;
13
+ margin-bottom: 24px;
14
+ color: white;
15
+ flex-shrink: 0;
16
+
17
+ h1 {
18
+ font-size: 32px;
19
+ font-weight: 700;
20
+ margin: 0 0 8px 0;
21
+ letter-spacing: -0.5px;
22
+ }
23
+
24
+ .subtitle {
25
+ font-size: 16px;
26
+ opacity: 0.9;
27
+ margin: 0 0 20px 0;
28
+ }
29
+
30
+ .header-actions {
31
+ display: flex;
32
+ justify-content: center;
33
+ gap: 12px;
34
+
35
+ button {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 8px;
39
+ }
40
+
41
+ .mat-mdc-outlined-button {
42
+ border-color: rgba(255, 255, 255, 0.5);
43
+ color: white;
44
+
45
+ &:hover {
46
+ background: rgba(255, 255, 255, 0.1);
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ .app-main {
53
+ flex: 1;
54
+ display: flex;
55
+ flex-direction: column;
56
+ min-height: 0;
57
+ overflow: hidden;
58
+
59
+ data-mapper {
60
+ flex: 1;
61
+ display: flex;
62
+ flex-direction: column;
63
+ min-height: 0;
64
+ }
65
+ }
@@ -0,0 +1,11 @@
1
+ import { Component } from '@angular/core';
2
+ import { RouterOutlet } from '@angular/router';
3
+
4
+ @Component({
5
+ selector: 'app-root',
6
+ standalone: true,
7
+ imports: [RouterOutlet],
8
+ template: '<router-outlet></router-outlet>',
9
+ styles: [':host { display: block; height: 100%; }'],
10
+ })
11
+ export class App {}
@@ -0,0 +1,294 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { RouterModule, RouterOutlet } from '@angular/router';
4
+ import { MatSidenavModule } from '@angular/material/sidenav';
5
+ import { MatListModule } from '@angular/material/list';
6
+ import { MatIconModule } from '@angular/material/icon';
7
+ import { MatToolbarModule } from '@angular/material/toolbar';
8
+ import { MatButtonModule } from '@angular/material/button';
9
+ import { MatTooltipModule } from '@angular/material/tooltip';
10
+
11
+ @Component({
12
+ selector: 'app-layout',
13
+ standalone: true,
14
+ imports: [
15
+ CommonModule,
16
+ RouterModule,
17
+ RouterOutlet,
18
+ MatSidenavModule,
19
+ MatListModule,
20
+ MatIconModule,
21
+ MatToolbarModule,
22
+ MatButtonModule,
23
+ MatTooltipModule,
24
+ ],
25
+ template: `
26
+ <div class="app-layout">
27
+ <header class="app-header">
28
+ <div class="header-left">
29
+ <button
30
+ mat-icon-button
31
+ class="menu-toggle"
32
+ (click)="toggleSidebar()"
33
+ [matTooltip]="collapsed() ? 'Expand menu' : 'Collapse menu'"
34
+ >
35
+ <mat-icon>{{ collapsed() ? 'menu' : 'menu_open' }}</mat-icon>
36
+ </button>
37
+ <div class="logo">
38
+ <div class="logo-icon">
39
+ <mat-icon>hub</mat-icon>
40
+ </div>
41
+ <div class="logo-text" [class.hidden]="collapsed()">
42
+ <span class="logo-title">DataMapper</span>
43
+ <span class="logo-badge">Studio</span>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </header>
48
+
49
+ <div class="app-body">
50
+ <nav class="sidebar" [class.collapsed]="collapsed()">
51
+ <div class="nav-section">
52
+ <span class="nav-label" [class.hidden]="collapsed()">WORKSPACE</span>
53
+ <a
54
+ class="nav-item"
55
+ routerLink="/schemas"
56
+ routerLinkActive="active"
57
+ [matTooltip]="collapsed() ? 'Schemas' : ''"
58
+ matTooltipPosition="right"
59
+ >
60
+ <mat-icon>data_object</mat-icon>
61
+ <span [class.hidden]="collapsed()">Schemas</span>
62
+ </a>
63
+ <a
64
+ class="nav-item"
65
+ routerLink="/mappings"
66
+ routerLinkActive="active"
67
+ [matTooltip]="collapsed() ? 'Mappings' : ''"
68
+ matTooltipPosition="right"
69
+ >
70
+ <mat-icon>swap_horiz</mat-icon>
71
+ <span [class.hidden]="collapsed()">Mappings</span>
72
+ </a>
73
+ <a
74
+ class="nav-item"
75
+ routerLink="/schema-creator"
76
+ routerLinkActive="active"
77
+ [matTooltip]="collapsed() ? 'Schema Creator' : ''"
78
+ matTooltipPosition="right"
79
+ >
80
+ <mat-icon>add_box</mat-icon>
81
+ <span [class.hidden]="collapsed()">Schema Creator</span>
82
+ </a>
83
+ </div>
84
+ </nav>
85
+
86
+ <main class="main-content">
87
+ <router-outlet></router-outlet>
88
+ </main>
89
+ </div>
90
+ </div>
91
+ `,
92
+ styles: [`
93
+ .app-layout {
94
+ display: flex;
95
+ flex-direction: column;
96
+ height: 100vh;
97
+ background: #0f172a;
98
+ }
99
+
100
+ .app-header {
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: space-between;
104
+ padding: 0 16px;
105
+ height: 56px;
106
+ background: #0f172a;
107
+ border-bottom: 1px solid #1e293b;
108
+ flex-shrink: 0;
109
+ }
110
+
111
+ .header-left {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 8px;
115
+ }
116
+
117
+ .menu-toggle {
118
+ color: #64748b;
119
+ width: 36px;
120
+ height: 36px;
121
+
122
+ &:hover {
123
+ color: #94a3b8;
124
+ background: #1e293b;
125
+ }
126
+
127
+ mat-icon {
128
+ font-size: 20px;
129
+ width: 20px;
130
+ height: 20px;
131
+ }
132
+ }
133
+
134
+ .logo {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 10px;
138
+ }
139
+
140
+ .logo-icon {
141
+ width: 32px;
142
+ height: 32px;
143
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
144
+ border-radius: 8px;
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+
149
+ mat-icon {
150
+ color: white;
151
+ font-size: 18px;
152
+ width: 18px;
153
+ height: 18px;
154
+ }
155
+ }
156
+
157
+ .logo-text {
158
+ display: flex;
159
+ align-items: baseline;
160
+ gap: 6px;
161
+ transition: opacity 0.15s ease, width 0.15s ease;
162
+
163
+ &.hidden {
164
+ opacity: 0;
165
+ width: 0;
166
+ overflow: hidden;
167
+ }
168
+ }
169
+
170
+ .logo-title {
171
+ font-size: 16px;
172
+ font-weight: 600;
173
+ color: white;
174
+ letter-spacing: -0.3px;
175
+ }
176
+
177
+ .logo-badge {
178
+ font-size: 10px;
179
+ font-weight: 500;
180
+ color: #6366f1;
181
+ background: rgba(99, 102, 241, 0.15);
182
+ padding: 2px 5px;
183
+ border-radius: 3px;
184
+ text-transform: uppercase;
185
+ letter-spacing: 0.5px;
186
+ }
187
+
188
+ .app-body {
189
+ display: flex;
190
+ flex: 1;
191
+ min-height: 0;
192
+ }
193
+
194
+ .sidebar {
195
+ width: 200px;
196
+ background: #0f172a;
197
+ border-right: 1px solid #1e293b;
198
+ padding: 16px 10px;
199
+ flex-shrink: 0;
200
+ transition: width 0.15s ease;
201
+
202
+ &.collapsed {
203
+ width: 56px;
204
+ padding: 16px 8px;
205
+ }
206
+ }
207
+
208
+ .nav-section {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 2px;
212
+ }
213
+
214
+ .nav-label {
215
+ font-size: 10px;
216
+ font-weight: 600;
217
+ color: #475569;
218
+ letter-spacing: 0.5px;
219
+ padding: 6px 10px;
220
+ margin-bottom: 4px;
221
+ transition: opacity 0.15s ease;
222
+
223
+ &.hidden {
224
+ opacity: 0;
225
+ height: 0;
226
+ padding: 0;
227
+ margin: 0;
228
+ overflow: hidden;
229
+ }
230
+ }
231
+
232
+ .nav-item {
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 10px;
236
+ padding: 9px 10px;
237
+ border-radius: 6px;
238
+ color: #94a3b8;
239
+ text-decoration: none;
240
+ font-size: 13px;
241
+ font-weight: 500;
242
+ transition: all 0.12s ease;
243
+ white-space: nowrap;
244
+
245
+ &:hover {
246
+ background: #1e293b;
247
+ color: #e2e8f0;
248
+ }
249
+
250
+ &.active {
251
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
252
+ color: white;
253
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25);
254
+ }
255
+
256
+ mat-icon {
257
+ font-size: 20px;
258
+ width: 20px;
259
+ height: 20px;
260
+ flex-shrink: 0;
261
+ }
262
+
263
+ span {
264
+ transition: opacity 0.15s ease;
265
+
266
+ &.hidden {
267
+ opacity: 0;
268
+ width: 0;
269
+ overflow: hidden;
270
+ }
271
+ }
272
+ }
273
+
274
+ .main-content {
275
+ flex: 1;
276
+ min-width: 0;
277
+ background: #f8fafc;
278
+ overflow: hidden;
279
+ }
280
+
281
+ .hidden {
282
+ opacity: 0;
283
+ width: 0;
284
+ overflow: hidden;
285
+ }
286
+ `],
287
+ })
288
+ export class AppLayoutComponent {
289
+ collapsed = signal(false);
290
+
291
+ toggleSidebar(): void {
292
+ this.collapsed.update(v => !v);
293
+ }
294
+ }
@@ -0,0 +1,87 @@
1
+ <!--
2
+ Mapper Page - Example usage of data-mapper component
3
+
4
+ This example shows:
5
+ - Providing source and target schemas
6
+ - Handling mapping change events
7
+ - Export/import functionality
8
+ - Custom styling via CSS variables (see .scss file)
9
+ -->
10
+ <div class="page-container">
11
+ <header class="page-header">
12
+ <div class="header-title">
13
+ <button mat-icon-button (click)="goBack()" matTooltip="Back to Mappings">
14
+ <mat-icon>arrow_back</mat-icon>
15
+ </button>
16
+ <div class="title-text">
17
+ @if (currentMapping()) {
18
+ @if (isEditingName()) {
19
+ <div class="name-edit">
20
+ <input
21
+ #nameInput
22
+ type="text"
23
+ [value]="currentMapping()!.name"
24
+ (keyup.enter)="saveName(nameInput.value)"
25
+ (keyup.escape)="cancelEditName()"
26
+ (blur)="saveName(nameInput.value)"
27
+ class="name-input"
28
+ />
29
+ </div>
30
+ } @else {
31
+ <div class="name-display">
32
+ <h1>{{ currentMapping()!.name }}</h1>
33
+ <button mat-icon-button (click)="startEditName()" matTooltip="Edit name" class="edit-btn">
34
+ <mat-icon>edit</mat-icon>
35
+ </button>
36
+ </div>
37
+ }
38
+ <p class="subtitle">
39
+ {{ appState.getSchemaName(currentMapping()!.sourceSchemaId) }}
40
+ <mat-icon class="flow-icon">arrow_forward</mat-icon>
41
+ {{ appState.getSchemaName(currentMapping()!.targetSchemaId) }}
42
+ </p>
43
+ } @else {
44
+ <h1>Data Mapper</h1>
45
+ <p class="subtitle">Drag fields from source to target to create mappings</p>
46
+ }
47
+ </div>
48
+ </div>
49
+ <div class="header-actions">
50
+ <button mat-stroked-button (click)="importMappings()">
51
+ <mat-icon>upload</mat-icon>
52
+ Import
53
+ </button>
54
+ <button mat-stroked-button (click)="exportMappings()" [disabled]="mappings().length === 0">
55
+ <mat-icon>download</mat-icon>
56
+ Export
57
+ </button>
58
+ <button mat-stroked-button (click)="clearMappings()" [disabled]="mappings().length === 0">
59
+ <mat-icon>delete_sweep</mat-icon>
60
+ Clear
61
+ </button>
62
+ </div>
63
+ <input
64
+ #fileInput
65
+ type="file"
66
+ accept=".json"
67
+ style="display: none"
68
+ (change)="onFileSelected($event)"
69
+ />
70
+ </header>
71
+
72
+ <main class="page-main">
73
+ <!--
74
+ data-mapper component usage:
75
+ - [sourceSchema]: The source JSON schema to map from
76
+ - [targetSchema]: The target JSON schema to map to
77
+ - [sampleData]: Optional sample data for preview
78
+ - (mappingsChange): Emits when mappings are created/modified/deleted
79
+ -->
80
+ <data-mapper
81
+ [sourceSchema]="sourceSchema()"
82
+ [targetSchema]="targetSchema()"
83
+ [sampleData]="sampleData()"
84
+ (mappingsChange)="onMappingsChange($event)"
85
+ ></data-mapper>
86
+ </main>
87
+ </div>