ngx-dynamic-entity 2.2.0 → 2.3.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/README.md +76 -4
- package/fesm2022/ngx-dynamic-entity.mjs +320 -37
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/package.json +6 -3
- package/types/ngx-dynamic-entity.d.ts +100 -14
|
@@ -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, getTabPath, fieldRefFor,
|
|
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, setTabData, setValueByPath, 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';
|
|
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';
|
|
@@ -920,8 +920,16 @@ const DEFAULT_UI_TEXT = {
|
|
|
920
920
|
importGuessed: 'Guessed',
|
|
921
921
|
importMappedCount: '{mapped} of {total} field(s) matched.',
|
|
922
922
|
importRequiredUnmapped: '{count} required field(s) still need a column.',
|
|
923
|
+
importConstant: 'Fixed value: {value}',
|
|
924
|
+
importAddSlot: 'Add a {label} row',
|
|
925
|
+
importStaleHeading: 'Mapped to fields this form no longer has',
|
|
926
|
+
importStaleExplain: 'The import will refuse this mapping until these are removed.',
|
|
927
|
+
importRemoveEntry: 'Remove',
|
|
923
928
|
// Import wizard — review and result
|
|
924
929
|
importPreviewHeading: 'First {count} row(s), as they will be saved',
|
|
930
|
+
importDelimiterComma: 'Columns are separated by commas.',
|
|
931
|
+
importDelimiterSemicolon: 'Columns are separated by semicolons, and numbers use a decimal comma.',
|
|
932
|
+
importDelimiterTab: 'Columns are separated by tabs.',
|
|
925
933
|
importRowRejected: 'This row will not be imported: {reason}',
|
|
926
934
|
importRunImport: 'Import {count} row(s)',
|
|
927
935
|
importImporting: 'Importing…',
|
|
@@ -7825,6 +7833,12 @@ class RulesEvaluationService {
|
|
|
7825
7833
|
* The predicate is `isFieldVisible`, the same one the render filter uses, so what is on
|
|
7826
7834
|
* screen and what counts toward validity cannot drift apart.
|
|
7827
7835
|
*
|
|
7836
|
+
* A tab that is not rendered — hidden by a rule, or `visibility: false` with no rule showing
|
|
7837
|
+
* it — hides everything it owns, sub-tabs and container children included. Only fields were
|
|
7838
|
+
* checked here, so a rule hiding a tab with a required field on it left the form invalid
|
|
7839
|
+
* with the field out of sight and out of the error summary: Save did nothing, silently.
|
|
7840
|
+
* Import already relaxed a hidden tab's fields, so the form refused rows import accepted.
|
|
7841
|
+
*
|
|
7828
7842
|
* Entries must arrive parent-first, which is what `collectFieldScopes` yields: a container's
|
|
7829
7843
|
* children are skipped once the container itself is hidden, because Angular's `disable()`
|
|
7830
7844
|
* cascades to descendants and a child's `enable()` walks back up recalculating ancestors.
|
|
@@ -7832,6 +7846,7 @@ class RulesEvaluationService {
|
|
|
7832
7846
|
*/
|
|
7833
7847
|
syncHiddenFieldState(entries, ctx) {
|
|
7834
7848
|
const hiddenContainers = [];
|
|
7849
|
+
const onHiddenTab = this.fieldsOnHiddenTabs(ctx.result, ctx.config);
|
|
7835
7850
|
for (const entry of entries) {
|
|
7836
7851
|
const field = entry.field;
|
|
7837
7852
|
if (!field?.id)
|
|
@@ -7839,7 +7854,7 @@ class RulesEvaluationService {
|
|
|
7839
7854
|
const address = ctx.addressOf(field);
|
|
7840
7855
|
if (hiddenContainers.some(prefix => address.startsWith(`${prefix}.`)))
|
|
7841
7856
|
continue;
|
|
7842
|
-
const hidden = !this.isFieldVisible(ctx.result, field, ctx.namesOf(field), ctx.values);
|
|
7857
|
+
const hidden = onHiddenTab.has(field) || !this.isFieldVisible(ctx.result, field, ctx.namesOf(field), ctx.values);
|
|
7843
7858
|
if (hidden && (field.type === 'group' || field.type === 'array'))
|
|
7844
7859
|
hiddenContainers.push(address);
|
|
7845
7860
|
const ctrl = ctx.controlFor(entry);
|
|
@@ -7854,6 +7869,30 @@ class RulesEvaluationService {
|
|
|
7854
7869
|
}
|
|
7855
7870
|
}
|
|
7856
7871
|
}
|
|
7872
|
+
/**
|
|
7873
|
+
* Every field owned by a tab that does not render, under `isTabVisible`'s precedence.
|
|
7874
|
+
*
|
|
7875
|
+
* Stops descending at the first hidden tab: `fieldsUnderTab` already covers its sub-tabs,
|
|
7876
|
+
* and a sub-tab of a hidden tab is not rendered whatever its own visibility says.
|
|
7877
|
+
*/
|
|
7878
|
+
fieldsOnHiddenTabs(result, config) {
|
|
7879
|
+
const out = new Set();
|
|
7880
|
+
const visit = (tabs) => {
|
|
7881
|
+
for (const tab of Array.isArray(tabs) ? tabs : []) {
|
|
7882
|
+
if (!tab || typeof tab !== 'object' || !tab.id)
|
|
7883
|
+
continue;
|
|
7884
|
+
if (this.isTabVisible(result, tab)) {
|
|
7885
|
+
visit(tab.children);
|
|
7886
|
+
}
|
|
7887
|
+
else {
|
|
7888
|
+
for (const entry of fieldsUnderTab(config, tab.id))
|
|
7889
|
+
out.add(entry.field);
|
|
7890
|
+
}
|
|
7891
|
+
}
|
|
7892
|
+
};
|
|
7893
|
+
visit(config?.tabs);
|
|
7894
|
+
return out;
|
|
7895
|
+
}
|
|
7857
7896
|
/**
|
|
7858
7897
|
* The hidden and shown names as sets, memoised against the result they came from.
|
|
7859
7898
|
*
|
|
@@ -8063,18 +8102,11 @@ class FormStructureService {
|
|
|
8063
8102
|
* unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
|
|
8064
8103
|
*/
|
|
8065
8104
|
flattenValues(form, entries) {
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
const field = entry.field;
|
|
8069
|
-
if (!field?.id)
|
|
8070
|
-
continue;
|
|
8105
|
+
// The naming rule is core's, shared with the importer; only where values come from is ours.
|
|
8106
|
+
return flattenFieldValues(entries, entry => {
|
|
8071
8107
|
const ctrl = this.controlAt(form, entry);
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
out[field.id] = ctrl.value;
|
|
8075
|
-
out[toRefToken(refOf(field, entry.scope))] = ctrl.value;
|
|
8076
|
-
}
|
|
8077
|
-
return out;
|
|
8108
|
+
return ctrl ? { value: ctrl.value } : undefined;
|
|
8109
|
+
});
|
|
8078
8110
|
}
|
|
8079
8111
|
/**
|
|
8080
8112
|
* Find a control by reference: a bracketed path, or a bare field id.
|
|
@@ -9235,6 +9267,7 @@ class DynamicFormComponent {
|
|
|
9235
9267
|
namesOf: field => this.namesOf(field),
|
|
9236
9268
|
addressOf: field => this.addressOf(field),
|
|
9237
9269
|
controlFor: entry => this.controlForEntry(entry),
|
|
9270
|
+
config: this.config,
|
|
9238
9271
|
});
|
|
9239
9272
|
}
|
|
9240
9273
|
/** Every field in the config, including `group`/`array` children. */
|
|
@@ -10086,7 +10119,10 @@ const defaultSheetParser = async (file) => {
|
|
|
10086
10119
|
throw new Error(`Cannot read "${file.name}". The built-in parser handles CSV only — register a ` +
|
|
10087
10120
|
'sheetParser with provideNgxDynamicEntity({ sheetParser }) to accept other formats.');
|
|
10088
10121
|
}
|
|
10089
|
-
|
|
10122
|
+
// A `.tsv` says what it is. Anything else is read the way `parseCsv` reads text — the
|
|
10123
|
+
// separator its header line uses most — which is also how the server reads an upload.
|
|
10124
|
+
const tabs = /\.tsv$/i.test(file?.name ?? '');
|
|
10125
|
+
return parseCsv(await file.text(), tabs ? { delimiter: '\t' } : {});
|
|
10090
10126
|
};
|
|
10091
10127
|
|
|
10092
10128
|
/**
|
|
@@ -10134,12 +10170,18 @@ class LocalImportTransport {
|
|
|
10134
10170
|
}
|
|
10135
10171
|
async preview(file, context) {
|
|
10136
10172
|
const sheet = await this.read(file);
|
|
10137
|
-
|
|
10173
|
+
// Sized from the headers exactly as the server sizes them, so both offer the same slots.
|
|
10174
|
+
const arrayBound = arrayBoundFor(sheet.headers, context.config, null, context.lang);
|
|
10175
|
+
const { columns } = deriveImportColumns(context.config, { lang: context.lang, maxArrayRows: arrayBound });
|
|
10138
10176
|
return {
|
|
10139
10177
|
headers: sheet.headers,
|
|
10140
|
-
|
|
10178
|
+
// Rendered the way the server renders it, so a typed date shows — and previews — as the
|
|
10179
|
+
// calendar date the commit will store, not as a timestamp.
|
|
10180
|
+
sample: sheet.rows.slice(0, SAMPLE_ROWS).map(row => row.map(cellText)),
|
|
10141
10181
|
rowCount: sheet.rows.length,
|
|
10142
10182
|
suggestion: suggestMapping(sheet.headers, columns, context.config?.entity ?? ''),
|
|
10183
|
+
arrayBound,
|
|
10184
|
+
...(sheet.delimiter ? { delimiter: sheet.delimiter } : {}),
|
|
10143
10185
|
};
|
|
10144
10186
|
}
|
|
10145
10187
|
async commit(file, plan, context) {
|
|
@@ -10148,6 +10190,8 @@ class LocalImportTransport {
|
|
|
10148
10190
|
lang: context.lang,
|
|
10149
10191
|
rules: context.rules,
|
|
10150
10192
|
lookups: context.lookups,
|
|
10193
|
+
// A `;` file means `1,5` is one and a half — the same call the server makes.
|
|
10194
|
+
decimal: decimalMarkFor(sheet.delimiter),
|
|
10151
10195
|
});
|
|
10152
10196
|
}
|
|
10153
10197
|
async template(spec, format) {
|
|
@@ -10309,6 +10353,9 @@ class HttpImportTransport {
|
|
|
10309
10353
|
sample: preview.sample ?? [],
|
|
10310
10354
|
suggestion: preview.suggestion,
|
|
10311
10355
|
rowCount: preview.rowCount ?? 0,
|
|
10356
|
+
// A server older than 2.3 does not say, and always sized for the default.
|
|
10357
|
+
arrayBound: preview.arrayBound ?? DEFAULT_ARRAY_ROWS,
|
|
10358
|
+
...(preview.delimiter ? { delimiter: preview.delimiter } : {}),
|
|
10312
10359
|
};
|
|
10313
10360
|
}
|
|
10314
10361
|
async commit(file, plan, context) {
|
|
@@ -10468,6 +10515,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
10468
10515
|
type: Input
|
|
10469
10516
|
}] } });
|
|
10470
10517
|
|
|
10518
|
+
/** The select's value for "keep the plan's fixed value", which is not a column. */
|
|
10519
|
+
const CONSTANT = -1;
|
|
10471
10520
|
/**
|
|
10472
10521
|
* Match the sheet's columns to the config's fields.
|
|
10473
10522
|
*
|
|
@@ -10497,18 +10546,70 @@ class ImportMapperComponent {
|
|
|
10497
10546
|
* attachment column was never going to arrive.
|
|
10498
10547
|
*/
|
|
10499
10548
|
unsupported = signal(0, ...(ngDevMode ? [{ debugName: "unsupported" }] : /* istanbul ignore next */ []));
|
|
10549
|
+
/** A plan entry whose field this config no longer has. Kept, and emitted, until removed. */
|
|
10550
|
+
stale = signal([], ...(ngDevMode ? [{ debugName: "stale" }] : /* istanbul ignore next */ []));
|
|
10551
|
+
/** Repeating fields the user can add a row to. */
|
|
10552
|
+
arrays = signal([], ...(ngDevMode ? [{ debugName: "arrays" }] : /* istanbul ignore next */ []));
|
|
10553
|
+
constant = CONSTANT;
|
|
10554
|
+
/** Rows added by hand per repeating field, on top of what the sheet and plan imply. */
|
|
10555
|
+
extraSlots = new Map();
|
|
10556
|
+
/** The incoming plan, read through the 2.2 alias. Every entry in it survives `toPlan`. */
|
|
10557
|
+
incoming = null;
|
|
10500
10558
|
ngOnChanges() {
|
|
10501
|
-
|
|
10559
|
+
this.incoming = this.plan ? upgradeLegacyRefs(this.plan, this.config) : null;
|
|
10560
|
+
this.extraSlots.clear();
|
|
10561
|
+
this.rows.set([]);
|
|
10562
|
+
this.rebuild();
|
|
10563
|
+
}
|
|
10564
|
+
/**
|
|
10565
|
+
* Rows for every field, sized so nothing the plan or the sheet names is out of reach.
|
|
10566
|
+
*
|
|
10567
|
+
* Sizing from the default three meant a sheet with `Phone 4` could not be mapped at all,
|
|
10568
|
+
* and a stored plan reaching further lost those entries on the next change — `toPlan` only
|
|
10569
|
+
* ever emitted rows, and there were none for them.
|
|
10570
|
+
*/
|
|
10571
|
+
rebuild() {
|
|
10572
|
+
const entries = (this.incoming?.entries ?? []).filter(Boolean);
|
|
10573
|
+
const base = arrayBoundFor(this.headers, this.config, this.incoming, this.language);
|
|
10574
|
+
const extra = Math.max(0, ...this.extraSlots.values());
|
|
10575
|
+
// Readonly and system fields are derived so a plan that maps one keeps it, but only
|
|
10576
|
+
// offered as a row when the plan does — nobody maps a column onto a field the form fills.
|
|
10577
|
+
const { columns, unsupported } = deriveImportColumns(this.config, {
|
|
10578
|
+
lang: this.language,
|
|
10579
|
+
maxArrayRows: base + extra,
|
|
10580
|
+
includeReadonly: true,
|
|
10581
|
+
includeSystemDefault: true,
|
|
10582
|
+
});
|
|
10502
10583
|
this.unsupported.set(unsupported.length);
|
|
10503
|
-
const byRef = new Map(
|
|
10504
|
-
|
|
10584
|
+
const byRef = new Map(entries.map(entry => [entry.ref, entry]));
|
|
10585
|
+
const known = new Set(columns.map(column => column.ref));
|
|
10586
|
+
const previous = new Map(this.rows().map(row => [row.column.ref, row]));
|
|
10587
|
+
const offered = columns.filter(column => (byRef.has(column.ref) || (!column.field.readonly && !column.field.systemDefault)) &&
|
|
10588
|
+
(column.arrayRef === undefined ||
|
|
10589
|
+
byRef.has(column.ref) ||
|
|
10590
|
+
column.arrayIndex < base + (this.extraSlots.get(column.arrayRef) ?? 0)));
|
|
10591
|
+
this.rows.set(offered.map(column => {
|
|
10592
|
+
// A choice already made survives a rebuild for an added slot.
|
|
10593
|
+
const kept = previous.get(column.ref);
|
|
10594
|
+
if (kept)
|
|
10595
|
+
return { ...kept, column };
|
|
10505
10596
|
const entry = byRef.get(column.ref);
|
|
10506
10597
|
return {
|
|
10507
10598
|
column,
|
|
10508
|
-
source: entry?.column ?? null,
|
|
10599
|
+
source: entry?.column ?? (entry?.constant !== undefined ? CONSTANT : null),
|
|
10509
10600
|
guessed: entry?.confidence === 'guess',
|
|
10601
|
+
entry,
|
|
10602
|
+
dirty: false,
|
|
10510
10603
|
};
|
|
10511
10604
|
}));
|
|
10605
|
+
this.stale.set(entries.filter(entry => !known.has(entry.ref)));
|
|
10606
|
+
const arrays = new Map();
|
|
10607
|
+
for (const column of offered) {
|
|
10608
|
+
if (column.arrayRef !== undefined && !arrays.has(column.arrayRef)) {
|
|
10609
|
+
arrays.set(column.arrayRef, column.arrayLabel ?? column.arrayRef);
|
|
10610
|
+
}
|
|
10611
|
+
}
|
|
10612
|
+
this.arrays.set([...arrays].map(([arrayRef, label]) => ({ arrayRef, label })));
|
|
10512
10613
|
}
|
|
10513
10614
|
mappedCount() {
|
|
10514
10615
|
return this.rows().filter(row => row.source !== null).length;
|
|
@@ -10516,24 +10617,75 @@ class ImportMapperComponent {
|
|
|
10516
10617
|
missingRequired() {
|
|
10517
10618
|
return this.rows().filter(row => row.column.required && row.source === null).length;
|
|
10518
10619
|
}
|
|
10620
|
+
hasConstant(row) {
|
|
10621
|
+
return !!row.entry && row.entry.constant !== undefined && row.entry.column === undefined;
|
|
10622
|
+
}
|
|
10623
|
+
constantText(entry) {
|
|
10624
|
+
const value = entry.constant;
|
|
10625
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
10626
|
+
}
|
|
10519
10627
|
choose(row, source) {
|
|
10520
10628
|
this.rows.update(rows => rows.map(candidate => candidate.column.ref === row.column.ref
|
|
10521
|
-
? // A hand-made choice is not a guess any more, so the badge goes.
|
|
10522
|
-
|
|
10629
|
+
? // A hand-made choice is not a guess any more, so the badge goes. Going back to
|
|
10630
|
+
// the plan's fixed value is going back to its entry, untouched.
|
|
10631
|
+
{ ...candidate, source, guessed: false, dirty: source !== CONSTANT }
|
|
10523
10632
|
: candidate));
|
|
10524
10633
|
this.planChange.emit(this.toPlan());
|
|
10525
10634
|
}
|
|
10526
|
-
/**
|
|
10635
|
+
/** One more row for one repeating field, for this session. */
|
|
10636
|
+
addSlot(arrayRef) {
|
|
10637
|
+
this.extraSlots.set(arrayRef, (this.extraSlots.get(arrayRef) ?? 0) + 1);
|
|
10638
|
+
this.rebuild();
|
|
10639
|
+
}
|
|
10640
|
+
removeStale(entry) {
|
|
10641
|
+
this.stale.update(stale => stale.filter(candidate => candidate !== entry));
|
|
10642
|
+
this.planChange.emit(this.toPlan());
|
|
10643
|
+
}
|
|
10644
|
+
/**
|
|
10645
|
+
* The current selections as the plan that will be applied.
|
|
10646
|
+
*
|
|
10647
|
+
* Every entry of the incoming plan survives in its own position — a fixed value, a ref the
|
|
10648
|
+
* config no longer has — unless the user changed or removed it, and an untouched entry is
|
|
10649
|
+
* emitted exactly as it arrived. New selections follow, in field order.
|
|
10650
|
+
*/
|
|
10527
10651
|
toPlan() {
|
|
10528
|
-
const
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10652
|
+
const rowByRef = new Map(this.rows().map(row => [row.column.ref, row]));
|
|
10653
|
+
const stale = new Set(this.stale());
|
|
10654
|
+
const emitted = new Set();
|
|
10655
|
+
const entries = [];
|
|
10656
|
+
const entryOf = (row) => {
|
|
10657
|
+
if (!row.dirty && row.entry)
|
|
10658
|
+
return row.entry;
|
|
10659
|
+
if (row.source === null || row.source === CONSTANT)
|
|
10660
|
+
return null;
|
|
10661
|
+
return {
|
|
10662
|
+
ref: row.column.ref,
|
|
10663
|
+
column: row.source,
|
|
10664
|
+
header: this.headers[row.source],
|
|
10665
|
+
confidence: row.guessed ? 'guess' : 'exact',
|
|
10666
|
+
};
|
|
10667
|
+
};
|
|
10668
|
+
for (const original of this.incoming?.entries ?? []) {
|
|
10669
|
+
const row = original ? rowByRef.get(original.ref) : undefined;
|
|
10670
|
+
if (row) {
|
|
10671
|
+
emitted.add(row.column.ref);
|
|
10672
|
+
const entry = entryOf(row);
|
|
10673
|
+
if (entry)
|
|
10674
|
+
entries.push(entry);
|
|
10675
|
+
}
|
|
10676
|
+
else if (stale.has(original)) {
|
|
10677
|
+
entries.push(original);
|
|
10678
|
+
}
|
|
10679
|
+
}
|
|
10680
|
+
for (const row of this.rows()) {
|
|
10681
|
+
if (emitted.has(row.column.ref))
|
|
10682
|
+
continue;
|
|
10683
|
+
const entry = entryOf(row);
|
|
10684
|
+
if (entry)
|
|
10685
|
+
entries.push(entry);
|
|
10686
|
+
}
|
|
10536
10687
|
return {
|
|
10688
|
+
...this.incoming,
|
|
10537
10689
|
entity: this.config?.entity ?? '',
|
|
10538
10690
|
configVersion: this.config?.version,
|
|
10539
10691
|
sourceHeaders: [...this.headers],
|
|
@@ -10582,6 +10734,11 @@ class ImportMapperComponent {
|
|
|
10582
10734
|
(ngModelChange)="choose(row, $event)"
|
|
10583
10735
|
>
|
|
10584
10736
|
<option [ngValue]="null">{{ ui.text('importUnmapped', language) }}</option>
|
|
10737
|
+
@if (hasConstant(row)) {
|
|
10738
|
+
<option [ngValue]="constant">
|
|
10739
|
+
{{ ui.text('importConstant', language, { value: constantText(row.entry!) }) }}
|
|
10740
|
+
</option>
|
|
10741
|
+
}
|
|
10585
10742
|
@for (header of headers; track $index) {
|
|
10586
10743
|
<option [ngValue]="$index">{{ header || '—' }}</option>
|
|
10587
10744
|
}
|
|
@@ -10596,8 +10753,47 @@ class ImportMapperComponent {
|
|
|
10596
10753
|
}
|
|
10597
10754
|
</tbody>
|
|
10598
10755
|
</table>
|
|
10756
|
+
|
|
10757
|
+
@if (arrays().length) {
|
|
10758
|
+
<p class="ngx-import-mapper__slots">
|
|
10759
|
+
@for (array of arrays(); track array.arrayRef) {
|
|
10760
|
+
<button
|
|
10761
|
+
type="button"
|
|
10762
|
+
class="ngx-import-mapper__add-slot"
|
|
10763
|
+
[attr.data-testid]="'import-add-slot-' + array.arrayRef"
|
|
10764
|
+
(click)="addSlot(array.arrayRef)"
|
|
10765
|
+
>
|
|
10766
|
+
{{ ui.text('importAddSlot', language, { label: array.label }) }}
|
|
10767
|
+
</button>
|
|
10768
|
+
}
|
|
10769
|
+
</p>
|
|
10770
|
+
}
|
|
10771
|
+
|
|
10772
|
+
@if (stale().length) {
|
|
10773
|
+
<div class="ngx-import-mapper__stale" data-testid="import-stale" role="alert">
|
|
10774
|
+
<p>
|
|
10775
|
+
<strong>{{ ui.text('importStaleHeading', language) }}</strong>
|
|
10776
|
+
{{ ui.text('importStaleExplain', language) }}
|
|
10777
|
+
</p>
|
|
10778
|
+
<ul>
|
|
10779
|
+
@for (entry of stale(); track entry.ref) {
|
|
10780
|
+
<li [attr.data-testid]="'import-stale-' + entry.ref">
|
|
10781
|
+
<code>{{ entry.ref }}</code>
|
|
10782
|
+
<button
|
|
10783
|
+
type="button"
|
|
10784
|
+
class="ngx-import-mapper__remove"
|
|
10785
|
+
[attr.data-testid]="'import-remove-' + entry.ref"
|
|
10786
|
+
(click)="removeStale(entry)"
|
|
10787
|
+
>
|
|
10788
|
+
{{ ui.text('importRemoveEntry', language) }}
|
|
10789
|
+
</button>
|
|
10790
|
+
</li>
|
|
10791
|
+
}
|
|
10792
|
+
</ul>
|
|
10793
|
+
</div>
|
|
10794
|
+
}
|
|
10599
10795
|
</section>
|
|
10600
|
-
`, isInline: true, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;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}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10796
|
+
`, isInline: true, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;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}.ngx-import-mapper__slots{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}.ngx-import-mapper__add-slot,.ngx-import-mapper__remove{padding:4px 10px;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-mapper__remove{margin-left:8px}.ngx-import-mapper__stale{margin-top:12px;padding:8px 12px;color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2);border-radius:var(--ngx-radius-sm, 6px)}.ngx-import-mapper__stale p,.ngx-import-mapper__stale ul{margin:0 0 6px}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10601
10797
|
}
|
|
10602
10798
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportMapperComponent, decorators: [{
|
|
10603
10799
|
type: Component,
|
|
@@ -10642,6 +10838,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
10642
10838
|
(ngModelChange)="choose(row, $event)"
|
|
10643
10839
|
>
|
|
10644
10840
|
<option [ngValue]="null">{{ ui.text('importUnmapped', language) }}</option>
|
|
10841
|
+
@if (hasConstant(row)) {
|
|
10842
|
+
<option [ngValue]="constant">
|
|
10843
|
+
{{ ui.text('importConstant', language, { value: constantText(row.entry!) }) }}
|
|
10844
|
+
</option>
|
|
10845
|
+
}
|
|
10645
10846
|
@for (header of headers; track $index) {
|
|
10646
10847
|
<option [ngValue]="$index">{{ header || '—' }}</option>
|
|
10647
10848
|
}
|
|
@@ -10656,8 +10857,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
10656
10857
|
}
|
|
10657
10858
|
</tbody>
|
|
10658
10859
|
</table>
|
|
10860
|
+
|
|
10861
|
+
@if (arrays().length) {
|
|
10862
|
+
<p class="ngx-import-mapper__slots">
|
|
10863
|
+
@for (array of arrays(); track array.arrayRef) {
|
|
10864
|
+
<button
|
|
10865
|
+
type="button"
|
|
10866
|
+
class="ngx-import-mapper__add-slot"
|
|
10867
|
+
[attr.data-testid]="'import-add-slot-' + array.arrayRef"
|
|
10868
|
+
(click)="addSlot(array.arrayRef)"
|
|
10869
|
+
>
|
|
10870
|
+
{{ ui.text('importAddSlot', language, { label: array.label }) }}
|
|
10871
|
+
</button>
|
|
10872
|
+
}
|
|
10873
|
+
</p>
|
|
10874
|
+
}
|
|
10875
|
+
|
|
10876
|
+
@if (stale().length) {
|
|
10877
|
+
<div class="ngx-import-mapper__stale" data-testid="import-stale" role="alert">
|
|
10878
|
+
<p>
|
|
10879
|
+
<strong>{{ ui.text('importStaleHeading', language) }}</strong>
|
|
10880
|
+
{{ ui.text('importStaleExplain', language) }}
|
|
10881
|
+
</p>
|
|
10882
|
+
<ul>
|
|
10883
|
+
@for (entry of stale(); track entry.ref) {
|
|
10884
|
+
<li [attr.data-testid]="'import-stale-' + entry.ref">
|
|
10885
|
+
<code>{{ entry.ref }}</code>
|
|
10886
|
+
<button
|
|
10887
|
+
type="button"
|
|
10888
|
+
class="ngx-import-mapper__remove"
|
|
10889
|
+
[attr.data-testid]="'import-remove-' + entry.ref"
|
|
10890
|
+
(click)="removeStale(entry)"
|
|
10891
|
+
>
|
|
10892
|
+
{{ ui.text('importRemoveEntry', language) }}
|
|
10893
|
+
</button>
|
|
10894
|
+
</li>
|
|
10895
|
+
}
|
|
10896
|
+
</ul>
|
|
10897
|
+
</div>
|
|
10898
|
+
}
|
|
10659
10899
|
</section>
|
|
10660
|
-
`, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;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}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"] }]
|
|
10900
|
+
`, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;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}.ngx-import-mapper__slots{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}.ngx-import-mapper__add-slot,.ngx-import-mapper__remove{padding:4px 10px;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-mapper__remove{margin-left:8px}.ngx-import-mapper__stale{margin-top:12px;padding:8px 12px;color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2);border-radius:var(--ngx-radius-sm, 6px)}.ngx-import-mapper__stale p,.ngx-import-mapper__stale ul{margin:0 0 6px}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"] }]
|
|
10661
10901
|
}], propDecorators: { config: [{
|
|
10662
10902
|
type: Input,
|
|
10663
10903
|
args: [{ required: true }]
|
|
@@ -10702,13 +10942,23 @@ class ImportPreviewComponent {
|
|
|
10702
10942
|
/** The same rules the commit will apply; without them a rule-driven rejection is invisible. */
|
|
10703
10943
|
rules;
|
|
10704
10944
|
language = 'en';
|
|
10945
|
+
/** The decimal mark number cells use — the one the commit will read them with. */
|
|
10946
|
+
decimal = '.';
|
|
10705
10947
|
ui = inject(UiTextService);
|
|
10706
10948
|
/** The columns this plan fills, in the plan's own order. */
|
|
10707
10949
|
columns = signal([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
10708
10950
|
grid = signal([], ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
|
|
10709
10951
|
ngOnChanges() {
|
|
10710
|
-
|
|
10711
|
-
|
|
10952
|
+
// Sized by the plan, so a mapped sixth phone is shown rather than silently left out, and
|
|
10953
|
+
// read through the 2.2 alias, so an old ref shows the field it now means.
|
|
10954
|
+
const plan = upgradeLegacyRefs(this.plan, this.config);
|
|
10955
|
+
const byRef = new Map(deriveImportColumns(this.config, {
|
|
10956
|
+
lang: this.language,
|
|
10957
|
+
maxArrayRows: arrayBoundFor([], this.config, plan),
|
|
10958
|
+
includeReadonly: true,
|
|
10959
|
+
includeSystemDefault: true,
|
|
10960
|
+
}).columns.map(c => [c.ref, c]));
|
|
10961
|
+
const entries = (plan?.entries ?? []).filter(entry => byRef.has(entry.ref));
|
|
10712
10962
|
this.columns.set(entries.map(entry => byRef.get(entry.ref)));
|
|
10713
10963
|
// The same engine the commit runs, over the sample only. Row 0 of the sample is numbered
|
|
10714
10964
|
// 0 here rather than 2, because these row numbers index the preview, not the file.
|
|
@@ -10716,6 +10966,7 @@ class ImportPreviewComponent {
|
|
|
10716
10966
|
lang: this.language,
|
|
10717
10967
|
lookups: this.lookups,
|
|
10718
10968
|
rules: this.rules,
|
|
10969
|
+
decimal: this.decimal,
|
|
10719
10970
|
stamp: false,
|
|
10720
10971
|
firstRowNumber: 0,
|
|
10721
10972
|
});
|
|
@@ -10731,6 +10982,7 @@ class ImportPreviewComponent {
|
|
|
10731
10982
|
const outcome = coerceCell(field, raw, {
|
|
10732
10983
|
lang: this.language,
|
|
10733
10984
|
lookups: this.lookups,
|
|
10985
|
+
decimal: this.decimal,
|
|
10734
10986
|
});
|
|
10735
10987
|
if ('error' in outcome)
|
|
10736
10988
|
return { text: outcome.error, failed: true };
|
|
@@ -10742,7 +10994,7 @@ class ImportPreviewComponent {
|
|
|
10742
10994
|
})));
|
|
10743
10995
|
}
|
|
10744
10996
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10745
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportPreviewComponent, isStandalone: true, selector: "ngx-import-preview", inputs: { config: "config", plan: "plan", rows: "rows", lookups: "lookups", rules: "rules", language: "language" }, usesOnChanges: true, ngImport: i0, template: `
|
|
10997
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", 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: `
|
|
10746
10998
|
<section class="ngx-import-preview" data-testid="import-preview">
|
|
10747
10999
|
<h4 class="ngx-import-preview__heading">
|
|
10748
11000
|
{{ ui.text('importPreviewHeading', language, { count: rows.length }) }}
|
|
@@ -10833,6 +11085,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
10833
11085
|
type: Input
|
|
10834
11086
|
}], language: [{
|
|
10835
11087
|
type: Input
|
|
11088
|
+
}], decimal: [{
|
|
11089
|
+
type: Input
|
|
10836
11090
|
}] } });
|
|
10837
11091
|
|
|
10838
11092
|
/**
|
|
@@ -11049,6 +11303,8 @@ class EntityImportComponent {
|
|
|
11049
11303
|
headers = signal([], ...(ngDevMode ? [{ debugName: "headers" }] : /* istanbul ignore next */ []));
|
|
11050
11304
|
sample = signal([], ...(ngDevMode ? [{ debugName: "sample" }] : /* istanbul ignore next */ []));
|
|
11051
11305
|
rowCount = signal(0, ...(ngDevMode ? [{ debugName: "rowCount" }] : /* istanbul ignore next */ []));
|
|
11306
|
+
/** The separator a delimited file turned out to use, so the user can see how it was read. */
|
|
11307
|
+
delimiter = signal(undefined, ...(ngDevMode ? [{ debugName: "delimiter" }] : /* istanbul ignore next */ []));
|
|
11052
11308
|
fileName = signal('', ...(ngDevMode ? [{ debugName: "fileName" }] : /* istanbul ignore next */ []));
|
|
11053
11309
|
plan = signal(null, ...(ngDevMode ? [{ debugName: "plan" }] : /* istanbul ignore next */ []));
|
|
11054
11310
|
result = signal(null, ...(ngDevMode ? [{ debugName: "result" }] : /* istanbul ignore next */ []));
|
|
@@ -11133,6 +11389,7 @@ class EntityImportComponent {
|
|
|
11133
11389
|
this.headers.set(preview.headers);
|
|
11134
11390
|
this.sample.set(preview.sample);
|
|
11135
11391
|
this.rowCount.set(preview.rowCount);
|
|
11392
|
+
this.delimiter.set(preview.delimiter);
|
|
11136
11393
|
this.plan.set(preview.suggestion);
|
|
11137
11394
|
this.step.set('map');
|
|
11138
11395
|
}
|
|
@@ -11216,12 +11473,30 @@ class EntityImportComponent {
|
|
|
11216
11473
|
failedRowCount(result) {
|
|
11217
11474
|
return result.failed ?? new Set(result.errors.map(error => error.row)).size;
|
|
11218
11475
|
}
|
|
11476
|
+
/** The decimal mark the commit will read numbers with, so the preview reads them the same. */
|
|
11477
|
+
decimal() {
|
|
11478
|
+
return decimalMarkFor(this.delimiter());
|
|
11479
|
+
}
|
|
11480
|
+
/** How the file's columns were split, in words — or nothing for a workbook. */
|
|
11481
|
+
delimiterText() {
|
|
11482
|
+
switch (this.delimiter()) {
|
|
11483
|
+
case ',':
|
|
11484
|
+
return this.ui.text('importDelimiterComma', this.language);
|
|
11485
|
+
case ';':
|
|
11486
|
+
return this.ui.text('importDelimiterSemicolon', this.language);
|
|
11487
|
+
case '\t':
|
|
11488
|
+
return this.ui.text('importDelimiterTab', this.language);
|
|
11489
|
+
default:
|
|
11490
|
+
return null;
|
|
11491
|
+
}
|
|
11492
|
+
}
|
|
11219
11493
|
restart() {
|
|
11220
11494
|
this.file = null;
|
|
11221
11495
|
this.fileName.set('');
|
|
11222
11496
|
this.headers.set([]);
|
|
11223
11497
|
this.sample.set([]);
|
|
11224
11498
|
this.rowCount.set(0);
|
|
11499
|
+
this.delimiter.set(undefined);
|
|
11225
11500
|
this.plan.set(null);
|
|
11226
11501
|
this.result.set(null);
|
|
11227
11502
|
this.problem.set(null);
|
|
@@ -11284,6 +11559,9 @@ class EntityImportComponent {
|
|
|
11284
11559
|
}
|
|
11285
11560
|
|
|
11286
11561
|
@case ('review') {
|
|
11562
|
+
@if (delimiterText(); as separator) {
|
|
11563
|
+
<p class="ngx-entity-import__delimiter" data-testid="import-delimiter">{{ separator }}</p>
|
|
11564
|
+
}
|
|
11287
11565
|
@if (plan(); as current) {
|
|
11288
11566
|
<ngx-import-preview
|
|
11289
11567
|
[config]="config"
|
|
@@ -11292,6 +11570,7 @@ class EntityImportComponent {
|
|
|
11292
11570
|
[lookups]="resolvedLookups()"
|
|
11293
11571
|
[rules]="rules"
|
|
11294
11572
|
[language]="language"
|
|
11573
|
+
[decimal]="decimal()"
|
|
11295
11574
|
/>
|
|
11296
11575
|
}
|
|
11297
11576
|
<button
|
|
@@ -11344,7 +11623,7 @@ class EntityImportComponent {
|
|
|
11344
11623
|
}
|
|
11345
11624
|
}
|
|
11346
11625
|
</div>
|
|
11347
|
-
`, 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"] }, { kind: "component", type: ImportErrorsComponent, selector: "ngx-import-errors", inputs: ["errors", "language"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11626
|
+
`, 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 });
|
|
11348
11627
|
}
|
|
11349
11628
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityImportComponent, decorators: [{
|
|
11350
11629
|
type: Component,
|
|
@@ -11409,6 +11688,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
11409
11688
|
}
|
|
11410
11689
|
|
|
11411
11690
|
@case ('review') {
|
|
11691
|
+
@if (delimiterText(); as separator) {
|
|
11692
|
+
<p class="ngx-entity-import__delimiter" data-testid="import-delimiter">{{ separator }}</p>
|
|
11693
|
+
}
|
|
11412
11694
|
@if (plan(); as current) {
|
|
11413
11695
|
<ngx-import-preview
|
|
11414
11696
|
[config]="config"
|
|
@@ -11417,6 +11699,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
11417
11699
|
[lookups]="resolvedLookups()"
|
|
11418
11700
|
[rules]="rules"
|
|
11419
11701
|
[language]="language"
|
|
11702
|
+
[decimal]="decimal()"
|
|
11420
11703
|
/>
|
|
11421
11704
|
}
|
|
11422
11705
|
<button
|