ngx-dynamic-entity 2.4.1 → 3.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/fesm2022/ngx-dynamic-entity.mjs +190 -15
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/package.json +2 -2
- package/types/ngx-dynamic-entity.d.ts +107 -26
package/README.md
CHANGED
|
@@ -258,6 +258,7 @@ rendering decision, not an access-control boundary.
|
|
|
258
258
|
| `rules` | Pass the same rules the form uses, or a rule-hidden required field rejects rows the form would save. |
|
|
259
259
|
| `lookups` | Option lists already resolved, merged over what `LOOKUP_REGISTRY` provides. |
|
|
260
260
|
| `language` | Default `'en'`. |
|
|
261
|
+
| `dateOrder` | `'DMY'` or `'MDY'`: the order of a date written in digits, such as `05/09/2026`. Without it, a date that reads two ways is an error, never a guess. With a server transport the server's order is the one applied: state it on the router, and the wizard reads its review with what the server reports. If you state one here too and it differs, the wizard refuses to commit. |
|
|
261
262
|
| `templateFormat` | `'csv'` (default) or `'xlsx'`. `xlsx` needs a server transport. |
|
|
262
263
|
|
|
263
264
|
| Output | Payload |
|
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, ChangeDetectorRef, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, Optional, Inject, EventEmitter, ElementRef, computed, afterNextRender, HostListener, ViewChildren, Output } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { ReactiveFormsModule, Validators, FormBuilder, FormGroup, FormArray, FormsModule } from '@angular/forms';
|
|
5
|
-
import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, isValidUrl, isValidPhone, fileRefName, sliderBounds, ratingScale, normalizeHexColor, normalizeTags, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, fieldsUnderTab, getTabPath, fieldRefFor, flattenFieldValues, parseFieldRef, placeTabFields, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds, toRefToken, collectFieldScopes, applyAutoPatch, refOf, isUnsafePath, valuesEqual, parseCsv, arrayBoundFor, deriveImportColumns, suggestMapping, cellText, applyMapping, decimalMarkFor, toCsv, stripIndices, CORE_VERSION, DEFAULT_ARRAY_ROWS, upgradeLegacyRefs, coerceCell, buildTemplateSpec, collectLeafTargets, formatConfigProblems } from '@dynamic-entity/core';
|
|
5
|
+
import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, isValidUrl, isValidPhone, fileRefName, sliderBounds, ratingScale, normalizeHexColor, normalizeTags, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, fieldsUnderTab, getTabPath, fieldRefFor, flattenFieldValues, parseFieldRef, placeTabFields, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds, toRefToken, collectFieldScopes, applyAutoPatch, refOf, isUnsafePath, valuesEqual, parseCsv, arrayBoundFor, deriveImportColumns, suggestMapping, cellText, applyMapping, decimalMarkFor, toCsv, stripIndices, CORE_VERSION, DEFAULT_ARRAY_ROWS, planForServer, upgradeLegacyRefs, coerceCell, buildTemplateSpec, collectLeafTargets, formatConfigProblems } from '@dynamic-entity/core';
|
|
6
6
|
export { formatDisplayValue, normalizeConfig, normalizeOption, resolveLabel, resolveOptionLabel, resolveOptionValue, valuesMatch } from '@dynamic-entity/core';
|
|
7
7
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
8
|
import { Subject, isObservable, firstValueFrom } from 'rxjs';
|
|
@@ -930,6 +930,8 @@ const DEFAULT_UI_TEXT = {
|
|
|
930
930
|
importDelimiterComma: 'Columns are separated by commas.',
|
|
931
931
|
importDelimiterSemicolon: 'Columns are separated by semicolons, and numbers use a decimal comma.',
|
|
932
932
|
importDelimiterTab: 'Columns are separated by tabs.',
|
|
933
|
+
importDateOrderMismatch: 'This page reads dates as {stated} but the import reads them as {reported}. Nothing can be imported until both use the same date order.',
|
|
934
|
+
importDateOrderUnconfirmed: 'The import did not confirm the date order {stated}, so it is not applied. A date such as 05/09/2026 is refused, or read month first by a server older than 2.5.0.',
|
|
933
935
|
importRowRejected: 'This row will not be imported: {reason}',
|
|
934
936
|
importRunImport: 'Import {count} row(s)',
|
|
935
937
|
importImporting: 'Importing…',
|
|
@@ -10086,6 +10088,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
|
|
|
10086
10088
|
* on a server because their files have fifty thousand rows. Collapsing them into one option
|
|
10087
10089
|
* would make each answer imply the other.
|
|
10088
10090
|
*/
|
|
10091
|
+
/**
|
|
10092
|
+
* How the order the host states compares with the one the transport reports it reads with.
|
|
10093
|
+
*
|
|
10094
|
+
* - `mismatch`: both state one and they differ. Committing would store dates read one way
|
|
10095
|
+
* after a review that showed them the other, so nothing may be committed.
|
|
10096
|
+
* - `unconfirmed`: the host states one and the transport reports none. The transport reads the
|
|
10097
|
+
* file, so the host's order is not applied: worth a warning, not a refusal.
|
|
10098
|
+
* - `agreed`: anything else. Only the transport stating one is fine: it is the one that reads.
|
|
10099
|
+
*/
|
|
10100
|
+
function compareDateOrders(stated, reported) {
|
|
10101
|
+
if (!stated || stated === reported)
|
|
10102
|
+
return 'agreed';
|
|
10103
|
+
return reported ? 'mismatch' : 'unconfirmed';
|
|
10104
|
+
}
|
|
10089
10105
|
|
|
10090
10106
|
/**
|
|
10091
10107
|
* sheet-parser.ts — the built-in CSV reader, and the reason it is the only one.
|
|
@@ -10162,6 +10178,10 @@ class LocalImportTransport {
|
|
|
10162
10178
|
pending.catch(() => this.parsed.delete(file));
|
|
10163
10179
|
return pending;
|
|
10164
10180
|
}
|
|
10181
|
+
/**
|
|
10182
|
+
* The suggestion made here is always plan v2, and the return type says so. The contract's is
|
|
10183
|
+
* a plan in either shape, because a 2.x server sends the older one.
|
|
10184
|
+
*/
|
|
10165
10185
|
async preview(file, context) {
|
|
10166
10186
|
const sheet = await this.read(file);
|
|
10167
10187
|
// Sized from the headers exactly as the server sizes them, so both offer the same slots.
|
|
@@ -10176,6 +10196,8 @@ class LocalImportTransport {
|
|
|
10176
10196
|
suggestion: suggestMapping(sheet.headers, columns, context.config?.entity ?? ''),
|
|
10177
10197
|
arrayBound,
|
|
10178
10198
|
...(sheet.delimiter ? { delimiter: sheet.delimiter } : {}),
|
|
10199
|
+
// What `commit` below will read with, so the review step reads its sample the same way.
|
|
10200
|
+
...(context.dateOrder ? { dateOrder: context.dateOrder } : {}),
|
|
10179
10201
|
};
|
|
10180
10202
|
}
|
|
10181
10203
|
async commit(file, plan, context) {
|
|
@@ -10186,6 +10208,8 @@ class LocalImportTransport {
|
|
|
10186
10208
|
lookups: context.lookups,
|
|
10187
10209
|
// A `;` file means `1,5` is one and a half — the same call the server makes.
|
|
10188
10210
|
decimal: decimalMarkFor(sheet.delimiter),
|
|
10211
|
+
// Stated by the host, never inferred: a date that reads two ways fails without it.
|
|
10212
|
+
dateOrder: context.dateOrder,
|
|
10189
10213
|
});
|
|
10190
10214
|
}
|
|
10191
10215
|
async template(spec, format) {
|
|
@@ -10294,11 +10318,52 @@ function selectionOf(spec, context, fields) {
|
|
|
10294
10318
|
* to resolve — and a consumer who wants two of them, pointed at two backends, gets that for
|
|
10295
10319
|
* free.
|
|
10296
10320
|
*/
|
|
10321
|
+
/** What a Dynamic Entity server calls its adapter in the `adapter` stamp on every answer. */
|
|
10322
|
+
const ADAPTER_NAME = 'dynamic-entity';
|
|
10323
|
+
/** The first release whose server says which date order it reads with, and refuses to guess one. */
|
|
10324
|
+
const DATE_ORDER_SINCE = [2, 5];
|
|
10325
|
+
/** Whether an engine version is from before the server stopped reading dates month first. */
|
|
10326
|
+
function readsMonthFirst(version) {
|
|
10327
|
+
if (!version)
|
|
10328
|
+
return true;
|
|
10329
|
+
const [major, minor] = version.split('.').map(Number);
|
|
10330
|
+
return major < DATE_ORDER_SINCE[0] || (major === DATE_ORDER_SINCE[0] && minor < DATE_ORDER_SINCE[1]);
|
|
10331
|
+
}
|
|
10297
10332
|
class HttpImportTransport {
|
|
10298
10333
|
options;
|
|
10334
|
+
/**
|
|
10335
|
+
* The date order each previewed file's server reported, for `commit` to compare against.
|
|
10336
|
+
* Keyed on the `File`, so it lives exactly as long as the file does.
|
|
10337
|
+
*/
|
|
10338
|
+
reported = new WeakMap();
|
|
10339
|
+
/**
|
|
10340
|
+
* The plan versions each previewed file's server said it reads. A file with no entry was
|
|
10341
|
+
* never previewed here, and one whose server said nothing is from before plan v2: either
|
|
10342
|
+
* way `commit` sends nothing that only a v2 reader would understand.
|
|
10343
|
+
*/
|
|
10344
|
+
planVersions = new WeakMap();
|
|
10299
10345
|
constructor(options) {
|
|
10300
10346
|
this.options = options;
|
|
10301
10347
|
}
|
|
10348
|
+
warn(message) {
|
|
10349
|
+
(this.options.warn ?? ((text) => console.warn(text)))(message);
|
|
10350
|
+
}
|
|
10351
|
+
/**
|
|
10352
|
+
* The server reads the file, so its date order is the one that is applied. When the host
|
|
10353
|
+
* stated one and the server confirms none, the host's order does nothing there: say so, and
|
|
10354
|
+
* say what the server will do instead, which depends on how old it is.
|
|
10355
|
+
*/
|
|
10356
|
+
checkDateOrder(stated, reported, version) {
|
|
10357
|
+
if (compareDateOrders(stated, reported) !== 'unconfirmed')
|
|
10358
|
+
return;
|
|
10359
|
+
this.warn(`[ngx-dynamic-entity] This app states the date order ${stated}, but the import server did not confirm one, ` +
|
|
10360
|
+
(readsMonthFirst(version)
|
|
10361
|
+
? `and runs @dynamic-entity/core ${version ?? '(unreported)'}, from before 2.5.0. A server that old reads a ` +
|
|
10362
|
+
'date such as 05/09/2026 month first, whatever this app states. Upgrade the server and state ' +
|
|
10363
|
+
'the same dateOrder on its router.'
|
|
10364
|
+
: 'so it will refuse a date such as 05/09/2026 that could be day first or month first. ' +
|
|
10365
|
+
'State the same dateOrder on its router.'));
|
|
10366
|
+
}
|
|
10302
10367
|
get base() {
|
|
10303
10368
|
return trimSlash(this.options.baseUrl);
|
|
10304
10369
|
}
|
|
@@ -10325,11 +10390,26 @@ class HttpImportTransport {
|
|
|
10325
10390
|
// scroll past the warning that does matter.
|
|
10326
10391
|
if (!version || sameLine(version, CORE_VERSION))
|
|
10327
10392
|
return;
|
|
10328
|
-
|
|
10329
|
-
warn(`[ngx-dynamic-entity] The import server runs @dynamic-entity/core ${version}; this app ` +
|
|
10393
|
+
this.warn(`[ngx-dynamic-entity] The import server runs @dynamic-entity/core ${version}; this app ` +
|
|
10330
10394
|
`was built against ${CORE_VERSION}. The two halves of an import are supposed to be the ` +
|
|
10331
10395
|
'same engine — bring them into step before trusting a difference in what they produce.');
|
|
10332
10396
|
}
|
|
10397
|
+
/**
|
|
10398
|
+
* Warn when the server runs another adapter, or this one at another major version.
|
|
10399
|
+
*
|
|
10400
|
+
* Since 3.0 a server says which adapter turned its rows into records. A server from before
|
|
10401
|
+
* that says nothing, and `checkEngine` has already compared the one version it does report.
|
|
10402
|
+
*/
|
|
10403
|
+
checkAdapter(adapter) {
|
|
10404
|
+
if (!adapter)
|
|
10405
|
+
return;
|
|
10406
|
+
const major = (version) => String(version ?? '').split('.')[0];
|
|
10407
|
+
if (adapter.name === ADAPTER_NAME && major(adapter.version) === major(CORE_VERSION))
|
|
10408
|
+
return;
|
|
10409
|
+
this.warn(`[ngx-dynamic-entity] The import server reads rows with the adapter ${adapter.name} ${adapter.version}; ` +
|
|
10410
|
+
`this app expects ${ADAPTER_NAME} ${major(CORE_VERSION)}.x. What the server imports may differ from ` +
|
|
10411
|
+
'what the review step showed. Bring the two into step.');
|
|
10412
|
+
}
|
|
10333
10413
|
async preview(file, context) {
|
|
10334
10414
|
const body = new FormData();
|
|
10335
10415
|
body.append('file', file, file.name || 'upload');
|
|
@@ -10342,6 +10422,10 @@ class HttpImportTransport {
|
|
|
10342
10422
|
throw await failureOf(response);
|
|
10343
10423
|
const preview = (await response.json());
|
|
10344
10424
|
this.checkEngine(preview.engineVersion);
|
|
10425
|
+
this.checkAdapter(preview.adapter);
|
|
10426
|
+
this.planVersions.set(file, Array.isArray(preview.planVersions) ? preview.planVersions : undefined);
|
|
10427
|
+
this.reported.set(file, preview.dateOrder);
|
|
10428
|
+
this.checkDateOrder(context?.dateOrder, preview.dateOrder, preview.engineVersion);
|
|
10345
10429
|
return {
|
|
10346
10430
|
headers: preview.headers ?? [],
|
|
10347
10431
|
sample: preview.sample ?? [],
|
|
@@ -10350,14 +10434,32 @@ class HttpImportTransport {
|
|
|
10350
10434
|
// A server older than 2.3 does not say, and always sized for the default.
|
|
10351
10435
|
arrayBound: preview.arrayBound ?? DEFAULT_ARRAY_ROWS,
|
|
10352
10436
|
...(preview.delimiter ? { delimiter: preview.delimiter } : {}),
|
|
10437
|
+
...(preview.dateOrder ? { dateOrder: preview.dateOrder } : {}),
|
|
10353
10438
|
};
|
|
10354
10439
|
}
|
|
10355
10440
|
async commit(file, plan, context) {
|
|
10441
|
+
// Before a byte is sent. The review this user approved read dates one way and the server
|
|
10442
|
+
// would store them another, and a streaming import cannot be taken back.
|
|
10443
|
+
const reported = this.reported.get(file);
|
|
10444
|
+
if (compareDateOrders(context?.dateOrder, reported) === 'mismatch') {
|
|
10445
|
+
throw new Error(`This app reads dates as ${context.dateOrder} but the import server reads them as ${reported}. ` +
|
|
10446
|
+
'Nothing was imported. Set the same dateOrder on the wizard and on the server\'s router.');
|
|
10447
|
+
}
|
|
10448
|
+
// Also before a byte is sent. A server from before plan v2 ignores what it does not know,
|
|
10449
|
+
// so a plan that groups rows would import one record per row there and report success.
|
|
10450
|
+
const sendable = planForServer(plan, this.planVersions.get(file));
|
|
10451
|
+
if (sendable.refused) {
|
|
10452
|
+
throw new Error(`This mapping uses ${sendable.refused.join(', ')}, which the import server does not read: it is from ` +
|
|
10453
|
+
'before mapping plan v2 and would ignore it. Nothing was imported. Upgrade the server to ' +
|
|
10454
|
+
'@dynamic-entity/server 3.0 or later.');
|
|
10455
|
+
}
|
|
10356
10456
|
const body = new FormData();
|
|
10357
10457
|
// **The plan first, and the file second.** The server starts importing while the file is
|
|
10358
10458
|
// still arriving — that is the whole reason it exists — so the plan has to have arrived
|
|
10359
10459
|
// already. `FormData` preserves append order, so this ordering is the contract.
|
|
10360
|
-
|
|
10460
|
+
// As written for a server that reads plan v2, and in 2.x form (`entity`, `configVersion`)
|
|
10461
|
+
// for one that does not: the same mapping, in the words that server knows.
|
|
10462
|
+
body.append('plan', JSON.stringify(sendable.plan));
|
|
10361
10463
|
body.append('file', file, file.name || 'upload');
|
|
10362
10464
|
const response = await this.send(`${this.base}/${this.entityOf(context)}/import`, {
|
|
10363
10465
|
method: 'POST',
|
|
@@ -10368,6 +10470,7 @@ class HttpImportTransport {
|
|
|
10368
10470
|
throw await failureOf(response);
|
|
10369
10471
|
const result = (await response.json());
|
|
10370
10472
|
this.checkEngine(result.engineVersion);
|
|
10473
|
+
this.checkAdapter(result.adapter);
|
|
10371
10474
|
return {
|
|
10372
10475
|
// Empty, and not because nothing was imported. The server streamed the file precisely so
|
|
10373
10476
|
// that fifty thousand records never had to exist at once; sending them back would undo
|
|
@@ -10381,6 +10484,8 @@ class HttpImportTransport {
|
|
|
10381
10484
|
errorCount: result.errorCount ?? result.errors?.length ?? 0,
|
|
10382
10485
|
truncated: result.truncated ?? false,
|
|
10383
10486
|
planProblems: result.planProblems ?? [],
|
|
10487
|
+
// Only a 3.0 server sends them, and only a run with something to warn about has any.
|
|
10488
|
+
...(result.warnings?.length ? { warnings: result.warnings } : {}),
|
|
10384
10489
|
};
|
|
10385
10490
|
}
|
|
10386
10491
|
async template(spec, format, context, fields) {
|
|
@@ -10525,10 +10630,10 @@ const CONSTANT = -1;
|
|
|
10525
10630
|
class ImportMapperComponent {
|
|
10526
10631
|
config;
|
|
10527
10632
|
headers = [];
|
|
10528
|
-
/** The starting point, normally `suggestMapping`'s output. */
|
|
10633
|
+
/** The starting point, normally `suggestMapping`'s output. A stored 2.x plan is taken too. */
|
|
10529
10634
|
plan = null;
|
|
10530
10635
|
language = 'en';
|
|
10531
|
-
/** Emitted whenever the user changes a selection. */
|
|
10636
|
+
/** Emitted whenever the user changes a selection. Always plan v2, whichever shape came in. */
|
|
10532
10637
|
planChange = new EventEmitter();
|
|
10533
10638
|
ui = inject(UiTextService);
|
|
10534
10639
|
rows = signal([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
@@ -10641,6 +10746,10 @@ class ImportMapperComponent {
|
|
|
10641
10746
|
* Every entry of the incoming plan survives in its own position — a fixed value, a ref the
|
|
10642
10747
|
* config no longer has — unless the user changed or removed it, and an untouched entry is
|
|
10643
10748
|
* emitted exactly as it arrived. New selections follow, in field order.
|
|
10749
|
+
*
|
|
10750
|
+
* What is emitted is plan v2, whichever shape came in: `target` and `schemaVersion`, where a
|
|
10751
|
+
* 2.x plan said `entity` and `configVersion`. Anything else the incoming plan carried, such
|
|
10752
|
+
* as its lists and its grouping, is kept.
|
|
10644
10753
|
*/
|
|
10645
10754
|
toPlan() {
|
|
10646
10755
|
const rowByRef = new Map(this.rows().map(row => [row.column.ref, row]));
|
|
@@ -10678,10 +10787,13 @@ class ImportMapperComponent {
|
|
|
10678
10787
|
if (entry)
|
|
10679
10788
|
entries.push(entry);
|
|
10680
10789
|
}
|
|
10790
|
+
// The 2.x names go: a plan carrying both is one the reader has to warn about.
|
|
10791
|
+
const kept = Object.fromEntries(Object.entries(this.incoming ?? {}).filter(([key]) => key !== 'entity' && key !== 'configVersion'));
|
|
10681
10792
|
return {
|
|
10682
|
-
...
|
|
10683
|
-
|
|
10684
|
-
|
|
10793
|
+
...kept,
|
|
10794
|
+
planVersion: 2,
|
|
10795
|
+
target: this.config?.entity ?? '',
|
|
10796
|
+
schemaVersion: this.config?.version,
|
|
10685
10797
|
sourceHeaders: [...this.headers],
|
|
10686
10798
|
entries,
|
|
10687
10799
|
};
|
|
@@ -10938,6 +11050,8 @@ class ImportPreviewComponent {
|
|
|
10938
11050
|
language = 'en';
|
|
10939
11051
|
/** The decimal mark number cells use — the one the commit will read them with. */
|
|
10940
11052
|
decimal = '.';
|
|
11053
|
+
/** The order of a date written in digits — the one the commit will read them in. */
|
|
11054
|
+
dateOrder;
|
|
10941
11055
|
ui = inject(UiTextService);
|
|
10942
11056
|
/** The columns this plan fills, in the plan's own order. */
|
|
10943
11057
|
columns = signal([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
@@ -10961,6 +11075,7 @@ class ImportPreviewComponent {
|
|
|
10961
11075
|
lookups: this.lookups,
|
|
10962
11076
|
rules: this.rules,
|
|
10963
11077
|
decimal: this.decimal,
|
|
11078
|
+
dateOrder: this.dateOrder,
|
|
10964
11079
|
stamp: false,
|
|
10965
11080
|
firstRowNumber: 0,
|
|
10966
11081
|
});
|
|
@@ -10977,6 +11092,8 @@ class ImportPreviewComponent {
|
|
|
10977
11092
|
lang: this.language,
|
|
10978
11093
|
lookups: this.lookups,
|
|
10979
11094
|
decimal: this.decimal,
|
|
11095
|
+
// The column's own order when its plan entry states one, as the commit reads it.
|
|
11096
|
+
dateOrder: entry.dateOrder ?? this.dateOrder,
|
|
10980
11097
|
});
|
|
10981
11098
|
if ('error' in outcome)
|
|
10982
11099
|
return { text: outcome.error, failed: true };
|
|
@@ -10988,7 +11105,7 @@ class ImportPreviewComponent {
|
|
|
10988
11105
|
})));
|
|
10989
11106
|
}
|
|
10990
11107
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10991
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ImportPreviewComponent, isStandalone: true, selector: "ngx-import-preview", inputs: { config: "config", plan: "plan", rows: "rows", lookups: "lookups", rules: "rules", language: "language", decimal: "decimal" }, usesOnChanges: true, ngImport: i0, template: `
|
|
11108
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ImportPreviewComponent, isStandalone: true, selector: "ngx-import-preview", inputs: { config: "config", plan: "plan", rows: "rows", lookups: "lookups", rules: "rules", language: "language", decimal: "decimal", dateOrder: "dateOrder" }, usesOnChanges: true, ngImport: i0, template: `
|
|
10992
11109
|
<section class="ngx-import-preview" data-testid="import-preview">
|
|
10993
11110
|
<h4 class="ngx-import-preview__heading">
|
|
10994
11111
|
{{ ui.text('importPreviewHeading', language, { count: rows.length }) }}
|
|
@@ -11081,6 +11198,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
|
|
|
11081
11198
|
type: Input
|
|
11082
11199
|
}], decimal: [{
|
|
11083
11200
|
type: Input
|
|
11201
|
+
}], dateOrder: [{
|
|
11202
|
+
type: Input
|
|
11084
11203
|
}] } });
|
|
11085
11204
|
|
|
11086
11205
|
/**
|
|
@@ -11283,6 +11402,16 @@ class EntityImportComponent {
|
|
|
11283
11402
|
*/
|
|
11284
11403
|
lookups;
|
|
11285
11404
|
language = 'en';
|
|
11405
|
+
/**
|
|
11406
|
+
* The order of a date written in digits in the sheets this wizard imports: `DMY` for day
|
|
11407
|
+
* first, `MDY` for month first. Without it, a date that reads two ways (`05/09/2026`) is
|
|
11408
|
+
* shown, and imported, as an error rather than guessed.
|
|
11409
|
+
*
|
|
11410
|
+
* The transport reads the file, so the order it reports at preview is the one applied, and
|
|
11411
|
+
* the one the review step shows. If that differs from this, the wizard refuses to commit;
|
|
11412
|
+
* if the transport reports none, the wizard warns that this order is not being applied.
|
|
11413
|
+
*/
|
|
11414
|
+
dateOrder;
|
|
11286
11415
|
/** Format asked of the transport when a template is downloaded. */
|
|
11287
11416
|
templateFormat = 'csv';
|
|
11288
11417
|
/** The finished import, once it has run. */
|
|
@@ -11299,6 +11428,8 @@ class EntityImportComponent {
|
|
|
11299
11428
|
rowCount = signal(0, ...(ngDevMode ? [{ debugName: "rowCount" }] : /* istanbul ignore next */ []));
|
|
11300
11429
|
/** The separator a delimited file turned out to use, so the user can see how it was read. */
|
|
11301
11430
|
delimiter = signal(undefined, ...(ngDevMode ? [{ debugName: "delimiter" }] : /* istanbul ignore next */ []));
|
|
11431
|
+
/** The date order the transport said it will read with at commit, from its preview. */
|
|
11432
|
+
reportedDateOrder = signal(undefined, ...(ngDevMode ? [{ debugName: "reportedDateOrder" }] : /* istanbul ignore next */ []));
|
|
11302
11433
|
fileName = signal('', ...(ngDevMode ? [{ debugName: "fileName" }] : /* istanbul ignore next */ []));
|
|
11303
11434
|
plan = signal(null, ...(ngDevMode ? [{ debugName: "plan" }] : /* istanbul ignore next */ []));
|
|
11304
11435
|
result = signal(null, ...(ngDevMode ? [{ debugName: "result" }] : /* istanbul ignore next */ []));
|
|
@@ -11317,6 +11448,7 @@ class EntityImportComponent {
|
|
|
11317
11448
|
rules: this.rules,
|
|
11318
11449
|
lookups: this.resolvedLookups(),
|
|
11319
11450
|
lang: this.language,
|
|
11451
|
+
dateOrder: this.dateOrder,
|
|
11320
11452
|
};
|
|
11321
11453
|
}
|
|
11322
11454
|
/**
|
|
@@ -11384,6 +11516,7 @@ class EntityImportComponent {
|
|
|
11384
11516
|
this.sample.set(preview.sample);
|
|
11385
11517
|
this.rowCount.set(preview.rowCount);
|
|
11386
11518
|
this.delimiter.set(preview.delimiter);
|
|
11519
|
+
this.reportedDateOrder.set(preview.dateOrder);
|
|
11387
11520
|
this.plan.set(preview.suggestion);
|
|
11388
11521
|
this.step.set('map');
|
|
11389
11522
|
}
|
|
@@ -11402,6 +11535,12 @@ class EntityImportComponent {
|
|
|
11402
11535
|
this.problem.set(this.ui.text('importNeedFile', this.language));
|
|
11403
11536
|
return;
|
|
11404
11537
|
}
|
|
11538
|
+
// Never on a mismatch: the review showed dates read one way and the commit would store
|
|
11539
|
+
// them read another. The button is disabled too; this is for whatever calls it anyway.
|
|
11540
|
+
if (this.dateOrderCheck() === 'mismatch') {
|
|
11541
|
+
this.problem.set(this.ui.text('importDateOrderMismatch', this.language, this.dateOrders()));
|
|
11542
|
+
return;
|
|
11543
|
+
}
|
|
11405
11544
|
this.busy.set(true);
|
|
11406
11545
|
this.problem.set(null);
|
|
11407
11546
|
try {
|
|
@@ -11467,6 +11606,14 @@ class EntityImportComponent {
|
|
|
11467
11606
|
failedRowCount(result) {
|
|
11468
11607
|
return result.failed ?? new Set(result.errors.map(error => error.row)).size;
|
|
11469
11608
|
}
|
|
11609
|
+
/** How the order the host states compares with the one the transport reads with. */
|
|
11610
|
+
dateOrderCheck() {
|
|
11611
|
+
return compareDateOrders(this.dateOrder, this.reportedDateOrder());
|
|
11612
|
+
}
|
|
11613
|
+
/** The two orders, for a message that names them. Only asked for when they do not agree. */
|
|
11614
|
+
dateOrders() {
|
|
11615
|
+
return { stated: String(this.dateOrder), reported: String(this.reportedDateOrder()) };
|
|
11616
|
+
}
|
|
11470
11617
|
/** The decimal mark the commit will read numbers with, so the preview reads them the same. */
|
|
11471
11618
|
decimal() {
|
|
11472
11619
|
return decimalMarkFor(this.delimiter());
|
|
@@ -11497,7 +11644,7 @@ class EntityImportComponent {
|
|
|
11497
11644
|
this.step.set('upload');
|
|
11498
11645
|
}
|
|
11499
11646
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityImportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11500
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: EntityImportComponent, isStandalone: true, selector: "ngx-entity-import", inputs: { config: "config", rules: "rules", lookups: "lookups", language: "language", templateFormat: "templateFormat" }, outputs: { importComplete: "importComplete", templateReady: "templateReady" }, ngImport: i0, template: `
|
|
11647
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: EntityImportComponent, isStandalone: true, selector: "ngx-entity-import", inputs: { config: "config", rules: "rules", lookups: "lookups", language: "language", dateOrder: "dateOrder", templateFormat: "templateFormat" }, outputs: { importComplete: "importComplete", templateReady: "templateReady" }, ngImport: i0, template: `
|
|
11501
11648
|
<div class="ngx-import" data-testid="entity-import">
|
|
11502
11649
|
<ol class="ngx-import__steps" data-testid="import-steps">
|
|
11503
11650
|
@for (name of steps; track name) {
|
|
@@ -11556,6 +11703,18 @@ class EntityImportComponent {
|
|
|
11556
11703
|
@if (delimiterText(); as separator) {
|
|
11557
11704
|
<p class="ngx-entity-import__delimiter" data-testid="import-delimiter">{{ separator }}</p>
|
|
11558
11705
|
}
|
|
11706
|
+
@switch (dateOrderCheck()) {
|
|
11707
|
+
@case ('mismatch') {
|
|
11708
|
+
<p class="ngx-entity-import__problem" role="alert" data-testid="import-date-order-mismatch">
|
|
11709
|
+
{{ ui.text('importDateOrderMismatch', language, dateOrders()) }}
|
|
11710
|
+
</p>
|
|
11711
|
+
}
|
|
11712
|
+
@case ('unconfirmed') {
|
|
11713
|
+
<p class="ngx-entity-import__delimiter" data-testid="import-date-order-unconfirmed">
|
|
11714
|
+
{{ ui.text('importDateOrderUnconfirmed', language, dateOrders()) }}
|
|
11715
|
+
</p>
|
|
11716
|
+
}
|
|
11717
|
+
}
|
|
11559
11718
|
@if (plan(); as current) {
|
|
11560
11719
|
<ngx-import-preview
|
|
11561
11720
|
[config]="config"
|
|
@@ -11565,12 +11724,13 @@ class EntityImportComponent {
|
|
|
11565
11724
|
[rules]="rules"
|
|
11566
11725
|
[language]="language"
|
|
11567
11726
|
[decimal]="decimal()"
|
|
11727
|
+
[dateOrder]="reportedDateOrder()"
|
|
11568
11728
|
/>
|
|
11569
11729
|
}
|
|
11570
11730
|
<button
|
|
11571
11731
|
type="button"
|
|
11572
11732
|
data-testid="import-commit"
|
|
11573
|
-
[disabled]="busy()"
|
|
11733
|
+
[disabled]="busy() || dateOrderCheck() === 'mismatch'"
|
|
11574
11734
|
(click)="commit()"
|
|
11575
11735
|
>
|
|
11576
11736
|
{{
|
|
@@ -11617,7 +11777,7 @@ class EntityImportComponent {
|
|
|
11617
11777
|
}
|
|
11618
11778
|
}
|
|
11619
11779
|
</div>
|
|
11620
|
-
`, isInline: true, styles: [".ngx-import{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);color:var(--ngx-color-text, #1f2937);font-size:var(--ngx-font-size, 14px)}.ngx-import__steps{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0;padding:0}.ngx-import__step{padding:4px 12px;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);color:var(--ngx-color-muted, #6b7280);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-import__step--current{color:var(--ngx-color-accent, #4f46e5);border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff);font-weight:600}.ngx-import__problem{margin:0;padding:10px 12px;border:1px solid var(--ngx-color-border, #fecaca);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-error-soft, #fef2f2);color:var(--ngx-color-error, #b91c1c);overflow-wrap:anywhere}.ngx-import__file{display:flex;flex-direction:column;gap:6px;padding:14px;border:1px dashed var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff)}.ngx-import button{align-self:flex-start;padding:8px 16px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import button:hover:not(:disabled){border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff)}.ngx-import button:disabled{opacity:.55;cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: ImportTemplateComponent, selector: "ngx-import-template", inputs: ["config", "language"], outputs: ["download"] }, { kind: "component", type: ImportMapperComponent, selector: "ngx-import-mapper", inputs: ["config", "headers", "plan", "language"], outputs: ["planChange"] }, { kind: "component", type: ImportPreviewComponent, selector: "ngx-import-preview", inputs: ["config", "plan", "rows", "lookups", "rules", "language", "decimal"] }, { kind: "component", type: ImportErrorsComponent, selector: "ngx-import-errors", inputs: ["errors", "language"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11780
|
+
`, isInline: true, styles: [".ngx-import{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);color:var(--ngx-color-text, #1f2937);font-size:var(--ngx-font-size, 14px)}.ngx-import__steps{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0;padding:0}.ngx-import__step{padding:4px 12px;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);color:var(--ngx-color-muted, #6b7280);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-import__step--current{color:var(--ngx-color-accent, #4f46e5);border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff);font-weight:600}.ngx-import__problem{margin:0;padding:10px 12px;border:1px solid var(--ngx-color-border, #fecaca);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-error-soft, #fef2f2);color:var(--ngx-color-error, #b91c1c);overflow-wrap:anywhere}.ngx-import__file{display:flex;flex-direction:column;gap:6px;padding:14px;border:1px dashed var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff)}.ngx-import button{align-self:flex-start;padding:8px 16px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import button:hover:not(:disabled){border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff)}.ngx-import button:disabled{opacity:.55;cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: ImportTemplateComponent, selector: "ngx-import-template", inputs: ["config", "language"], outputs: ["download"] }, { kind: "component", type: ImportMapperComponent, selector: "ngx-import-mapper", inputs: ["config", "headers", "plan", "language"], outputs: ["planChange"] }, { kind: "component", type: ImportPreviewComponent, selector: "ngx-import-preview", inputs: ["config", "plan", "rows", "lookups", "rules", "language", "decimal", "dateOrder"] }, { kind: "component", type: ImportErrorsComponent, selector: "ngx-import-errors", inputs: ["errors", "language"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11621
11781
|
}
|
|
11622
11782
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityImportComponent, decorators: [{
|
|
11623
11783
|
type: Component,
|
|
@@ -11685,6 +11845,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
|
|
|
11685
11845
|
@if (delimiterText(); as separator) {
|
|
11686
11846
|
<p class="ngx-entity-import__delimiter" data-testid="import-delimiter">{{ separator }}</p>
|
|
11687
11847
|
}
|
|
11848
|
+
@switch (dateOrderCheck()) {
|
|
11849
|
+
@case ('mismatch') {
|
|
11850
|
+
<p class="ngx-entity-import__problem" role="alert" data-testid="import-date-order-mismatch">
|
|
11851
|
+
{{ ui.text('importDateOrderMismatch', language, dateOrders()) }}
|
|
11852
|
+
</p>
|
|
11853
|
+
}
|
|
11854
|
+
@case ('unconfirmed') {
|
|
11855
|
+
<p class="ngx-entity-import__delimiter" data-testid="import-date-order-unconfirmed">
|
|
11856
|
+
{{ ui.text('importDateOrderUnconfirmed', language, dateOrders()) }}
|
|
11857
|
+
</p>
|
|
11858
|
+
}
|
|
11859
|
+
}
|
|
11688
11860
|
@if (plan(); as current) {
|
|
11689
11861
|
<ngx-import-preview
|
|
11690
11862
|
[config]="config"
|
|
@@ -11694,12 +11866,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
|
|
|
11694
11866
|
[rules]="rules"
|
|
11695
11867
|
[language]="language"
|
|
11696
11868
|
[decimal]="decimal()"
|
|
11869
|
+
[dateOrder]="reportedDateOrder()"
|
|
11697
11870
|
/>
|
|
11698
11871
|
}
|
|
11699
11872
|
<button
|
|
11700
11873
|
type="button"
|
|
11701
11874
|
data-testid="import-commit"
|
|
11702
|
-
[disabled]="busy()"
|
|
11875
|
+
[disabled]="busy() || dateOrderCheck() === 'mismatch'"
|
|
11703
11876
|
(click)="commit()"
|
|
11704
11877
|
>
|
|
11705
11878
|
{{
|
|
@@ -11756,6 +11929,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
|
|
|
11756
11929
|
type: Input
|
|
11757
11930
|
}], language: [{
|
|
11758
11931
|
type: Input
|
|
11932
|
+
}], dateOrder: [{
|
|
11933
|
+
type: Input
|
|
11759
11934
|
}], templateFormat: [{
|
|
11760
11935
|
type: Input
|
|
11761
11936
|
}], importComplete: [{
|
|
@@ -11793,5 +11968,5 @@ function saveBlob(blob, filename) {
|
|
|
11793
11968
|
* Generated bundle index. Do not edit.
|
|
11794
11969
|
*/
|
|
11795
11970
|
|
|
11796
|
-
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ColorFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_UI_TEXT, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityImportComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, HttpImportTransport, IMPORT_TRANSPORT, ImageFieldComponent, ImportErrorsComponent, ImportMapperComponent, ImportPreviewComponent, ImportTemplateComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LocalImportTransport, LookupRegistryService, MARKDOWN_RENDERER, MASKED_PLACEHOLDER, MASKED_ROLES, MarkdownFieldComponent, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, PhoneFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RatingFieldComponent, RbacService, RulesEvaluationService, SHEET_PARSER, SYSTEM_DEFAULT_CAN_EDIT, SliderFieldComponent, TagsFieldComponent, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UI_TEXT, UPLOAD_HANDLER, UiTextService, UrlFieldComponent, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, defaultSheetParser, fieldDescribedBy, fieldDomId, nextFieldInstanceId, phoneValidator, provideBuiltInFieldTypes, provideFieldTypes, provideHttpImportTransport, provideNgxDynamicEntity, refreshChoiceOptions, resolveUiText, urlValidator };
|
|
11971
|
+
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ColorFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_UI_TEXT, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityImportComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, HttpImportTransport, IMPORT_TRANSPORT, ImageFieldComponent, ImportErrorsComponent, ImportMapperComponent, ImportPreviewComponent, ImportTemplateComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LocalImportTransport, LookupRegistryService, MARKDOWN_RENDERER, MASKED_PLACEHOLDER, MASKED_ROLES, MarkdownFieldComponent, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, PhoneFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RatingFieldComponent, RbacService, RulesEvaluationService, SHEET_PARSER, SYSTEM_DEFAULT_CAN_EDIT, SliderFieldComponent, TagsFieldComponent, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UI_TEXT, UPLOAD_HANDLER, UiTextService, UrlFieldComponent, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, compareDateOrders, defaultSheetParser, fieldDescribedBy, fieldDomId, nextFieldInstanceId, phoneValidator, provideBuiltInFieldTypes, provideFieldTypes, provideHttpImportTransport, provideNgxDynamicEntity, refreshChoiceOptions, resolveUiText, urlValidator };
|
|
11797
11972
|
//# sourceMappingURL=ngx-dynamic-entity.mjs.map
|