@smartbit4all/ng-client 7.3.36 → 7.3.38
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/HTML-WIDGET.md +3 -2
- package/fesm2022/smartbit4all-ng-client.mjs +224 -73
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-7.3.38.tgz +0 -0
- package/types/smartbit4all-ng-client.d.ts +21 -0
- package/smartbit4all-ng-client-7.3.36.tgz +0 -0
|
@@ -4320,6 +4320,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
4320
4320
|
args: [{ selector: '[smartEmbeddedSlot]' }]
|
|
4321
4321
|
}], propDecorators: { containerId: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartEmbeddedSlot", required: true }] }], containerUuid: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerUuid", required: false }] }] } });
|
|
4322
4322
|
|
|
4323
|
+
/** Uri text as RFC 3986 gives it, the URI_PATTERN of UriUtils on the server. */
|
|
4324
|
+
const URI_PATTERN = new RegExp('^(?:(?:[a-zA-Z][a-zA-Z0-9+.-]*):)' + // scheme
|
|
4325
|
+
"(?://(?:[\\w\\-._~%!$&'()*+,;=:]+@)?" + // userinfo
|
|
4326
|
+
'(?:\\[[0-9A-Fa-f:.]+\\]|[\\w\\-._~%]+)' + // host (IP-literal / IPv4 / name)
|
|
4327
|
+
'(?::\\d{2,5})?)?' + // port
|
|
4328
|
+
"(?:/[\\w\\-._~%!$&'()*+,;=:@]*)*" + // path
|
|
4329
|
+
"(?:\\?[\\w\\-._~%!$&'()*+,;=:@/?]*)?" + // query
|
|
4330
|
+
"(?:#[\\w\\-._~%!$&'()*+,;=:@/?]*)?$" // fragment
|
|
4331
|
+
);
|
|
4332
|
+
/** The version suffix of the storage at the end of a path. */
|
|
4333
|
+
const VERSION_SUFFIX = /\.v(?:0|[1-9]\d*)$/;
|
|
4323
4334
|
/**
|
|
4324
4335
|
* The key and display property of a valueSet's values: the preferred name if the values carry it,
|
|
4325
4336
|
* then the GenericValue name (`uri` / `name`), then the Value name (`objectUri` / `displayValue`).
|
|
@@ -4331,26 +4342,66 @@ function valueSetKeyProperty(values, preferred) {
|
|
|
4331
4342
|
function valueSetDisplayProperty(values, preferred) {
|
|
4332
4343
|
return firstExisting(values, [preferred ?? 'displayValue', 'name', 'displayValue']);
|
|
4333
4344
|
}
|
|
4345
|
+
/**
|
|
4346
|
+
* The property of a value: a key the value has as it is, else a dotted path navigated part by
|
|
4347
|
+
* part (`data.name`); undefined when a part is missing.
|
|
4348
|
+
*/
|
|
4349
|
+
function readValueSetProperty(value, property) {
|
|
4350
|
+
if (value === null || typeof value !== 'object' || property in value) {
|
|
4351
|
+
return value?.[property];
|
|
4352
|
+
}
|
|
4353
|
+
return property.split('.').reduce((part, name) => part?.[name], value);
|
|
4354
|
+
}
|
|
4334
4355
|
/** The label of a code in a valueSet, or undefined when the valueSet does not know the code. */
|
|
4335
4356
|
function valueSetLabelOf(valueSetData, code) {
|
|
4336
4357
|
const values = valueSetData.values ?? [];
|
|
4337
4358
|
const keyProperty = valueSetKeyProperty(values, valueSetData.keyProperty);
|
|
4338
|
-
const hit = values.find((value) =>
|
|
4339
|
-
return hit
|
|
4359
|
+
const hit = values.find((value) => isSameKey(readValueSetProperty(value, keyProperty), code, valueSetData.versionedKeys));
|
|
4360
|
+
return hit && readValueSetProperty(hit, valueSetDisplayProperty(values));
|
|
4340
4361
|
}
|
|
4341
4362
|
function firstExisting(values, candidates) {
|
|
4342
4363
|
if (!values?.length) {
|
|
4343
4364
|
return candidates[0];
|
|
4344
4365
|
}
|
|
4345
|
-
|
|
4366
|
+
// A candidate exists when the first value reads it, also as a dotted path, or has it as a key.
|
|
4367
|
+
return (candidates.find((c) => readValueSetProperty(values[0], c) !== undefined || c in values[0]) ??
|
|
4368
|
+
candidates[candidates.length - 1]);
|
|
4346
4369
|
}
|
|
4347
4370
|
/** Whether a field holds no value: `''`, null, undefined or an empty list. */
|
|
4348
4371
|
function isEmptyValue(value) {
|
|
4349
4372
|
return value === '' || value == null || (Array.isArray(value) && value.length === 0);
|
|
4350
4373
|
}
|
|
4351
|
-
/**
|
|
4352
|
-
|
|
4353
|
-
|
|
4374
|
+
/**
|
|
4375
|
+
* The key as the keys of a set compare: as text, a uri text (RFC 3986) without the version suffix
|
|
4376
|
+
* at the end of its path unless the set has Versioned Keys. Any other text stays as it is, a code
|
|
4377
|
+
* ending in a version too.
|
|
4378
|
+
*/
|
|
4379
|
+
function createReferenceKey(key, versionedKeys) {
|
|
4380
|
+
const text = String(key);
|
|
4381
|
+
return versionedKeys || !URI_PATTERN.test(text) ? text : removeVersion(text);
|
|
4382
|
+
}
|
|
4383
|
+
/**
|
|
4384
|
+
* The uri text without the version suffix at the end of its path, as removeVersion of UriUtils on
|
|
4385
|
+
* the server; the scheme, the authority, the query and the fragment stay as they are.
|
|
4386
|
+
*/
|
|
4387
|
+
function removeVersion(uri) {
|
|
4388
|
+
const schemeEnd = uri.indexOf(':');
|
|
4389
|
+
const queryOrFragment = uri.slice(schemeEnd + 1).search(/[?#]/);
|
|
4390
|
+
const pathEnd = queryOrFragment < 0 ? uri.length : schemeEnd + 1 + queryOrFragment;
|
|
4391
|
+
let pathStart = schemeEnd + 1;
|
|
4392
|
+
if (uri.startsWith('//', pathStart)) {
|
|
4393
|
+
const slash = uri.indexOf('/', pathStart + 2);
|
|
4394
|
+
pathStart = slash < 0 || slash > pathEnd ? pathEnd : slash;
|
|
4395
|
+
}
|
|
4396
|
+
const version = VERSION_SUFFIX.exec(uri.slice(pathStart, pathEnd));
|
|
4397
|
+
return version ? uri.slice(0, pathStart + version.index) + uri.slice(pathEnd) : uri;
|
|
4398
|
+
}
|
|
4399
|
+
/**
|
|
4400
|
+
* Whether two keys of a set are equal as {@link createReferenceKey} gives them; as text, as a
|
|
4401
|
+
* control holds a boolean key as text.
|
|
4402
|
+
*/
|
|
4403
|
+
function isSameKey(key1, key2, versionedKeys) {
|
|
4404
|
+
return createReferenceKey(key1, versionedKeys) === createReferenceKey(key2, versionedKeys);
|
|
4354
4405
|
}
|
|
4355
4406
|
|
|
4356
4407
|
/**
|
|
@@ -4456,7 +4507,7 @@ class SmartValueSetMemoryStore extends SmartValueSetStore {
|
|
|
4456
4507
|
}
|
|
4457
4508
|
const held = this.viewEntries.get(viewUuid)?.get(key);
|
|
4458
4509
|
const extras = {
|
|
4459
|
-
extras: addReceivedExtras(held?.extras, valueSet.extraValues),
|
|
4510
|
+
extras: addReceivedExtras(held?.extras, valueSet.extraValues, this.findVersionedKeys(viewUuid, key, valueSet, held)),
|
|
4460
4511
|
excludedKeys: valueSet.excludedKeys,
|
|
4461
4512
|
};
|
|
4462
4513
|
// A version alone keeps the values held on screen until the fetch brings the new ones.
|
|
@@ -4879,6 +4930,18 @@ class SmartValueSetMemoryStore extends SmartValueSetStore {
|
|
|
4879
4930
|
});
|
|
4880
4931
|
changedNames.forEach((name) => this.changeSource.next(name));
|
|
4881
4932
|
}
|
|
4933
|
+
/**
|
|
4934
|
+
* The Versioned Keys of the view's set of the key: of the values it receives or holds, else of
|
|
4935
|
+
* the explicit set its selection reads; undefined while the client has none of them.
|
|
4936
|
+
*/
|
|
4937
|
+
findVersionedKeys(viewUuid, key, valueSet, held) {
|
|
4938
|
+
const selection = this.viewSelections.get(viewUuid)?.get(key);
|
|
4939
|
+
const data = valueSet.valueSetData ??
|
|
4940
|
+
held?.data ??
|
|
4941
|
+
(selection &&
|
|
4942
|
+
this.findExplicitEntry(viewUuid, selection.valueSetNamespace, key)?.data);
|
|
4943
|
+
return data ? !!data.versionedKeys : undefined;
|
|
4944
|
+
}
|
|
4882
4945
|
mergeViewExtras(data, viewEntry, selection) {
|
|
4883
4946
|
return viewEntry?.extras?.length || viewEntry?.excludedKeys?.length
|
|
4884
4947
|
? mergeExtraValues(data, viewEntry, selection)
|
|
@@ -4911,31 +4974,46 @@ class SmartValueSetMemoryStore extends SmartValueSetStore {
|
|
|
4911
4974
|
this.changeSource.next(key);
|
|
4912
4975
|
}
|
|
4913
4976
|
}
|
|
4914
|
-
/**
|
|
4915
|
-
|
|
4916
|
-
|
|
4977
|
+
/**
|
|
4978
|
+
* The held extras followed by the received ones of a code not held yet, compared as
|
|
4979
|
+
* {@link createReferenceKey} gives it. While the Versioned Keys of the set are unknown the codes
|
|
4980
|
+
* compare exactly, the merge with the set drops the second version of an object then.
|
|
4981
|
+
*/
|
|
4982
|
+
function addReceivedExtras(held, received, versionedKeys) {
|
|
4983
|
+
const keepsVersion = versionedKeys !== false;
|
|
4984
|
+
const heldCodes = new Set((held ?? []).map((extra) => createReferenceKey(extra.value.code, keepsVersion)));
|
|
4917
4985
|
const added = (received ?? [])
|
|
4918
|
-
.filter((value) => !heldCodes.has(value.code))
|
|
4986
|
+
.filter((value) => !heldCodes.has(createReferenceKey(value.code, keepsVersion)))
|
|
4919
4987
|
.map((value) => ({ value }));
|
|
4920
4988
|
return [...(held ?? []), ...added];
|
|
4921
4989
|
}
|
|
4922
4990
|
/**
|
|
4923
|
-
* The set without the values whose
|
|
4924
|
-
* view's extras keyed by their code under the selection's key property, none the set
|
|
4991
|
+
* The set without the values whose key at the set's key property is an excluded key, then the
|
|
4992
|
+
* view's extras keyed by their code under the selection's key property, none the set or an extra
|
|
4993
|
+
* before has; the keys compare as {@link createReferenceKey} gives them.
|
|
4925
4994
|
*/
|
|
4926
4995
|
function mergeExtraValues(data, extras, selection) {
|
|
4927
4996
|
const values = data.values ?? [];
|
|
4928
4997
|
const keyProperty = valueSetKeyProperty(values, selection.keyProperty ?? data.keyProperty);
|
|
4929
4998
|
const displayProperty = valueSetDisplayProperty(values, selection.displayProperty);
|
|
4930
|
-
const
|
|
4999
|
+
const versionedKeys = data.versionedKeys;
|
|
5000
|
+
const excludedKeys = new Set((extras.excludedKeys ?? []).map((key) => createReferenceKey(key, versionedKeys)));
|
|
4931
5001
|
const keptValues = values.filter((value) => {
|
|
4932
|
-
const key = data.keyProperty
|
|
4933
|
-
|
|
5002
|
+
const key = data.keyProperty &&
|
|
5003
|
+
findReferenceKey(readValueSetProperty(value, data.keyProperty), versionedKeys);
|
|
5004
|
+
return key === undefined || !excludedKeys.has(key);
|
|
4934
5005
|
});
|
|
4935
|
-
const keys = new Set(keptValues.map((value) =>
|
|
5006
|
+
const keys = new Set(keptValues.map((value) => findReferenceKey(readValueSetProperty(value, keyProperty), versionedKeys)));
|
|
4936
5007
|
const extraValues = (extras.extras ?? [])
|
|
4937
5008
|
.map((extra) => extra.value)
|
|
4938
|
-
.filter((extra) =>
|
|
5009
|
+
.filter((extra) => {
|
|
5010
|
+
const key = findReferenceKey(extra.code, versionedKeys);
|
|
5011
|
+
if (key === undefined || keys.has(key)) {
|
|
5012
|
+
return false;
|
|
5013
|
+
}
|
|
5014
|
+
keys.add(key);
|
|
5015
|
+
return true;
|
|
5016
|
+
})
|
|
4939
5017
|
.map((extra) => {
|
|
4940
5018
|
const merged = {
|
|
4941
5019
|
...extra,
|
|
@@ -4953,8 +5031,9 @@ const mergedExtras = new WeakSet();
|
|
|
4953
5031
|
function isMergedExtra(value) {
|
|
4954
5032
|
return typeof value === "object" && value !== null && mergedExtras.has(value);
|
|
4955
5033
|
}
|
|
4956
|
-
|
|
4957
|
-
|
|
5034
|
+
/** The key as {@link createReferenceKey} gives it, undefined for no key. */
|
|
5035
|
+
function findReferenceKey(key, versionedKeys) {
|
|
5036
|
+
return key === undefined || key === null ? undefined : createReferenceKey(key, versionedKeys);
|
|
4958
5037
|
}
|
|
4959
5038
|
function createExplicitKey(module, namespace, name, branchUri) {
|
|
4960
5039
|
return JSON.stringify([module ?? "", namespace ?? "", name, branchUri ?? ""]);
|
|
@@ -8045,6 +8124,13 @@ class SmartFormService {
|
|
|
8045
8124
|
* mutating. See ADR-0007, decision 4.
|
|
8046
8125
|
*/
|
|
8047
8126
|
this.revision = this.revisionState.asReadonly();
|
|
8127
|
+
this.controlRevisionState = signal(0, /* @ts-ignore */
|
|
8128
|
+
...(ngDevMode ? [{ debugName: "controlRevisionState" }] : /* istanbul ignore next */ []));
|
|
8129
|
+
/**
|
|
8130
|
+
* Counts the value changes of the form's controls, which reach the data only at submit. A
|
|
8131
|
+
* widget showing the model as edited (the html widget) reads it to follow the user's edits.
|
|
8132
|
+
*/
|
|
8133
|
+
this.controlRevision = this.controlRevisionState.asReadonly();
|
|
8048
8134
|
this.emptyKey = 'e#m%p@t*y';
|
|
8049
8135
|
this.group = {};
|
|
8050
8136
|
/** The previous build's controls during a rebuild: a widget still shown keeps its control. */
|
|
@@ -8080,6 +8166,10 @@ class SmartFormService {
|
|
|
8080
8166
|
widgetsChanged() {
|
|
8081
8167
|
this.revisionState.update((revision) => revision + 1);
|
|
8082
8168
|
}
|
|
8169
|
+
/** Whether the set the selection widget lists has Versioned Keys. */
|
|
8170
|
+
hasVersionedKeys(widget) {
|
|
8171
|
+
return !!this.filledValueSets.get(widget)?.versionedKeys;
|
|
8172
|
+
}
|
|
8083
8173
|
/** Forgets the agreed values; the form is being rebuilt from the model. */
|
|
8084
8174
|
resetExchangedValues() {
|
|
8085
8175
|
this.exchanged.clear();
|
|
@@ -8318,8 +8408,9 @@ class SmartFormService {
|
|
|
8318
8408
|
}
|
|
8319
8409
|
/**
|
|
8320
8410
|
* The values of the set the widget's parent fields admit: each dependency compares the parent
|
|
8321
|
-
* control's value with the value object's property
|
|
8322
|
-
* every value. An extra of the view is listed only
|
|
8411
|
+
* control's value with the value object's property, by the Versioned Keys of the widget's set. A
|
|
8412
|
+
* parent with no control in the form admits every value. An extra of the view is listed only
|
|
8413
|
+
* while the parents hold the keys of its tag.
|
|
8323
8414
|
*/
|
|
8324
8415
|
narrowValueSetData(widget, valueSetData, form) {
|
|
8325
8416
|
const dependencies = this.getDependencies(widget);
|
|
@@ -8327,16 +8418,18 @@ class SmartFormService {
|
|
|
8327
8418
|
return valueSetData;
|
|
8328
8419
|
}
|
|
8329
8420
|
const keyProperty = this.readKeyProperty(widget, valueSetData);
|
|
8421
|
+
const versionedKeys = valueSetData.versionedKeys;
|
|
8330
8422
|
const extras = this.viewExtras.get(widget) ?? [];
|
|
8331
8423
|
const parentKeys = this.readDependencyKeys(widget, form);
|
|
8332
8424
|
const values = valueSetData.values.filter((value) => {
|
|
8425
|
+
const key = readValueSetProperty(value, keyProperty);
|
|
8333
8426
|
const sameKeyExtras = isMergedExtra(value)
|
|
8334
|
-
? extras.filter((extra) => isSameKey(extra.value.code,
|
|
8427
|
+
? extras.filter((extra) => isSameKey(extra.value.code, key, versionedKeys))
|
|
8335
8428
|
: [];
|
|
8336
8429
|
if (sameKeyExtras.length) {
|
|
8337
|
-
return sameKeyExtras.some((extra) => !!extra.tag && this.isTaggedWith(extra.tag, parentKeys));
|
|
8430
|
+
return sameKeyExtras.some((extra) => !!extra.tag && this.isTaggedWith(extra.tag, parentKeys, versionedKeys));
|
|
8338
8431
|
}
|
|
8339
|
-
return dependencies.every((dependency) => this.isAdmittedBy(dependency, value, form));
|
|
8432
|
+
return dependencies.every((dependency) => this.isAdmittedBy(dependency, value, form, versionedKeys));
|
|
8340
8433
|
});
|
|
8341
8434
|
return { ...valueSetData, values };
|
|
8342
8435
|
}
|
|
@@ -8349,17 +8442,17 @@ class SmartFormService {
|
|
|
8349
8442
|
: this.readParentKeys(dependency.modelPath, control.value);
|
|
8350
8443
|
});
|
|
8351
8444
|
}
|
|
8352
|
-
isTaggedWith(tag, parentKeys) {
|
|
8445
|
+
isTaggedWith(tag, parentKeys, versionedKeys) {
|
|
8353
8446
|
return (tag.length === parentKeys.length &&
|
|
8354
8447
|
tag.every((keys, index) => keys.length === parentKeys[index].length &&
|
|
8355
|
-
keys.every((key, keyIndex) => isSameKey(key, parentKeys[index][keyIndex]))));
|
|
8448
|
+
keys.every((key, keyIndex) => isSameKey(key, parentKeys[index][keyIndex], versionedKeys))));
|
|
8356
8449
|
}
|
|
8357
|
-
isAdmittedBy(dependency, value, form) {
|
|
8450
|
+
isAdmittedBy(dependency, value, form, versionedKeys) {
|
|
8358
8451
|
const control = form.controls[dependency.modelPath];
|
|
8359
8452
|
if (!control || isEmptyValue(control.value)) {
|
|
8360
8453
|
return !this.isBlockedByEmptyParent(dependency, form);
|
|
8361
8454
|
}
|
|
8362
|
-
return this.readParentKeys(dependency.modelPath, control.value).some((key) => isSameKey(key, value[dependency.property]));
|
|
8455
|
+
return this.readParentKeys(dependency.modelPath, control.value).some((key) => isSameKey(key, value[dependency.property], versionedKeys));
|
|
8363
8456
|
}
|
|
8364
8457
|
/**
|
|
8365
8458
|
* The keys a parent field holds: an object value, the whole entry of an OBJECT selection, gives
|
|
@@ -8371,7 +8464,7 @@ class SmartFormService {
|
|
|
8371
8464
|
return value;
|
|
8372
8465
|
}
|
|
8373
8466
|
const parent = this.findWidgetByKey(modelPath);
|
|
8374
|
-
return value
|
|
8467
|
+
return readValueSetProperty(value, valueSetKeyProperty([value], parent?.selection?.keyProperty));
|
|
8375
8468
|
});
|
|
8376
8469
|
}
|
|
8377
8470
|
/**
|
|
@@ -8403,7 +8496,7 @@ class SmartFormService {
|
|
|
8403
8496
|
}
|
|
8404
8497
|
const keyProperty = this.readKeyProperty(widget, valueSetData);
|
|
8405
8498
|
const readKey = this.createKeyReader(widget, valueSetData);
|
|
8406
|
-
const isAdmitted = (item) => valueSetData.values.some((admitted) => isSameKey(admitted
|
|
8499
|
+
const isAdmitted = (item) => valueSetData.values.some((admitted) => isSameKey(readValueSetProperty(admitted, keyProperty), readKey(item), valueSetData.versionedKeys));
|
|
8407
8500
|
if (Array.isArray(value)) {
|
|
8408
8501
|
return value.filter(isAdmitted);
|
|
8409
8502
|
}
|
|
@@ -8413,7 +8506,7 @@ class SmartFormService {
|
|
|
8413
8506
|
createKeyReader(widget, valueSetData) {
|
|
8414
8507
|
const keyProperty = this.readKeyProperty(widget, valueSetData);
|
|
8415
8508
|
return (item) => widget.selection.type === SelectionDefinitionTypeEnum.OBJECT
|
|
8416
|
-
? item
|
|
8509
|
+
? readValueSetProperty(item, keyProperty)
|
|
8417
8510
|
: item;
|
|
8418
8511
|
}
|
|
8419
8512
|
readKeyProperty(widget, valueSetData) {
|
|
@@ -8445,11 +8538,12 @@ class SmartFormService {
|
|
|
8445
8538
|
!selection.mapping?.length) {
|
|
8446
8539
|
let keyProperty = valueSetKeyProperty(values, selection.keyProperty ?? valueSetData?.keyProperty);
|
|
8447
8540
|
select.valueList = values.map((value) => {
|
|
8541
|
+
const key = readValueSetProperty(value, keyProperty);
|
|
8448
8542
|
return {
|
|
8449
|
-
key
|
|
8450
|
-
label: value
|
|
8543
|
+
key,
|
|
8544
|
+
label: readValueSetProperty(value, displayProperty),
|
|
8451
8545
|
type: SmartFormWidgetType.ITEM,
|
|
8452
|
-
value:
|
|
8546
|
+
value: key,
|
|
8453
8547
|
};
|
|
8454
8548
|
});
|
|
8455
8549
|
if (!isRequired(select)) {
|
|
@@ -8461,7 +8555,7 @@ class SmartFormService {
|
|
|
8461
8555
|
select.valueList = values.map((value, index) => {
|
|
8462
8556
|
return {
|
|
8463
8557
|
key: `${index}`,
|
|
8464
|
-
label: value
|
|
8558
|
+
label: readValueSetProperty(value, displayProperty),
|
|
8465
8559
|
type: SmartFormWidgetType.ITEM,
|
|
8466
8560
|
value: value,
|
|
8467
8561
|
};
|
|
@@ -8493,6 +8587,8 @@ class SmartFormService {
|
|
|
8493
8587
|
}
|
|
8494
8588
|
else {
|
|
8495
8589
|
this.formGroup = new UntypedFormGroup(this.group);
|
|
8590
|
+
// The group lives as long as this service: a rebuild reuses it, so one subscription holds.
|
|
8591
|
+
this.formGroup.valueChanges.subscribe(() => this.controlRevisionState.update((revision) => revision + 1));
|
|
8496
8592
|
}
|
|
8497
8593
|
if (this.smartForm.validators && this.smartForm.validators.length) {
|
|
8498
8594
|
this.formGroup.setValidators(this.smartForm.validators.map((validator) => {
|
|
@@ -8638,24 +8734,40 @@ class SmartFormService {
|
|
|
8638
8734
|
this.copyValueFromFormToWidgetBeans(this.smartForm.widgets, group);
|
|
8639
8735
|
const data = this.smartForm.componentModel?.data;
|
|
8640
8736
|
if (data) {
|
|
8641
|
-
|
|
8642
|
-
if (!group.controls[key]) {
|
|
8643
|
-
continue;
|
|
8644
|
-
}
|
|
8645
|
-
const agreed = this.exchanged.has(key)
|
|
8646
|
-
? this.exchanged.get(key)
|
|
8647
|
-
: this.getValueDeeply(data, key);
|
|
8648
|
-
const value = coerceToTypeOf(agreed, group.controls[key].value);
|
|
8649
|
-
if (this.exchanged.has(key) && deepEqual(value, agreed)) {
|
|
8650
|
-
continue;
|
|
8651
|
-
}
|
|
8652
|
-
this.setValueDeeply(data, key, value);
|
|
8653
|
-
this.exchanged.set(key, structuredClone(this.getValueDeeply(data, key)));
|
|
8654
|
-
}
|
|
8737
|
+
this.copyControlsIntoData(data, group, this.exchanged);
|
|
8655
8738
|
}
|
|
8656
8739
|
}
|
|
8657
8740
|
return this.smartForm;
|
|
8658
8741
|
}
|
|
8742
|
+
/**
|
|
8743
|
+
* The form's data with the controls' edits applied as the next submit would copy them (a
|
|
8744
|
+
* MATRIX widget excepted: its submit changes the data through the widget beans). The result is
|
|
8745
|
+
* read-only: it is a copy, but the live data itself when the form has no controls; undefined
|
|
8746
|
+
* without a data.
|
|
8747
|
+
*/
|
|
8748
|
+
buildModelWithEdits() {
|
|
8749
|
+
const data = this.smartForm?.componentModel?.data;
|
|
8750
|
+
if (!data || !this.formGroup) {
|
|
8751
|
+
return data;
|
|
8752
|
+
}
|
|
8753
|
+
const model = structuredClone(data);
|
|
8754
|
+
this.copyControlsIntoData(model, this.formGroup, new Map(this.exchanged));
|
|
8755
|
+
return model;
|
|
8756
|
+
}
|
|
8757
|
+
copyControlsIntoData(data, group, exchanged) {
|
|
8758
|
+
for (const key of this.getPropertyNamesDeeply(data)) {
|
|
8759
|
+
if (!group.controls[key]) {
|
|
8760
|
+
continue;
|
|
8761
|
+
}
|
|
8762
|
+
const agreed = exchanged.has(key) ? exchanged.get(key) : this.getValueDeeply(data, key);
|
|
8763
|
+
const value = coerceToTypeOf(agreed, group.controls[key].value);
|
|
8764
|
+
if (exchanged.has(key) && deepEqual(value, agreed)) {
|
|
8765
|
+
continue;
|
|
8766
|
+
}
|
|
8767
|
+
this.setValueDeeply(data, key, value);
|
|
8768
|
+
exchanged.set(key, structuredClone(this.getValueDeeply(data, key)));
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8659
8771
|
getModel(group) {
|
|
8660
8772
|
if (!this.smartForm?.componentModel) {
|
|
8661
8773
|
return {};
|
|
@@ -9075,11 +9187,11 @@ class SmartfileuploaderComponent {
|
|
|
9075
9187
|
};
|
|
9076
9188
|
}
|
|
9077
9189
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9078
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--
|
|
9190
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
|
|
9079
9191
|
}
|
|
9080
9192
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, decorators: [{
|
|
9081
9193
|
type: Component,
|
|
9082
|
-
args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--
|
|
9194
|
+
args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
|
|
9083
9195
|
}], propDecorators: { fileInput: [{
|
|
9084
9196
|
type: ViewChild,
|
|
9085
9197
|
args: ['fileInput']
|
|
@@ -9620,6 +9732,7 @@ class SmartFileEditorComponent {
|
|
|
9620
9732
|
// ---- ControlValueAccessor implementation ----
|
|
9621
9733
|
this.onChange = (value) => { };
|
|
9622
9734
|
this.onTouched = () => { };
|
|
9735
|
+
/** The editor's disabled state: the widget's at creation, then the form control's. */
|
|
9623
9736
|
this.isDisabled = false;
|
|
9624
9737
|
}
|
|
9625
9738
|
get widgetInstance() {
|
|
@@ -9635,6 +9748,7 @@ class SmartFileEditorComponent {
|
|
|
9635
9748
|
typeof structuredClone === 'function'
|
|
9636
9749
|
? structuredClone(value)
|
|
9637
9750
|
: JSON.parse(JSON.stringify(value));
|
|
9751
|
+
this.isDisabled = !!value.isDisabled;
|
|
9638
9752
|
if (!this._widgetInstance.fileUploaderProperties?.undoIcon) {
|
|
9639
9753
|
this._widgetInstance.fileUploaderProperties = {
|
|
9640
9754
|
...(this._widgetInstance.fileUploaderProperties ?? {}),
|
|
@@ -9658,6 +9772,17 @@ class SmartFileEditorComponent {
|
|
|
9658
9772
|
});
|
|
9659
9773
|
this.onChange(this._widgetInstance.value);
|
|
9660
9774
|
this.fileToChange = undefined;
|
|
9775
|
+
this.clearedFile = undefined;
|
|
9776
|
+
}
|
|
9777
|
+
clear() {
|
|
9778
|
+
this.clearedFile = this._widgetInstance.value;
|
|
9779
|
+
this._widgetInstance.value = null;
|
|
9780
|
+
this.onChange(null);
|
|
9781
|
+
}
|
|
9782
|
+
undoClear() {
|
|
9783
|
+
this._widgetInstance.value = this.clearedFile;
|
|
9784
|
+
this.onChange(this.clearedFile);
|
|
9785
|
+
this.clearedFile = undefined;
|
|
9661
9786
|
}
|
|
9662
9787
|
change(file) {
|
|
9663
9788
|
this.fileToChange = file;
|
|
@@ -9701,6 +9826,10 @@ class SmartFileEditorComponent {
|
|
|
9701
9826
|
writeValue(value) {
|
|
9702
9827
|
if (value !== undefined && this._widgetInstance) {
|
|
9703
9828
|
this._widgetInstance.value = value;
|
|
9829
|
+
// A file written over a clear makes the undo obsolete; a written null or '' is the clear itself.
|
|
9830
|
+
if (value) {
|
|
9831
|
+
this.clearedFile = undefined;
|
|
9832
|
+
}
|
|
9704
9833
|
this.cdr.markForCheck();
|
|
9705
9834
|
}
|
|
9706
9835
|
}
|
|
@@ -9712,8 +9841,17 @@ class SmartFileEditorComponent {
|
|
|
9712
9841
|
}
|
|
9713
9842
|
setDisabledState(isDisabled) {
|
|
9714
9843
|
this.isDisabled = isDisabled;
|
|
9844
|
+
// A disable ends the editing (Save or Discard), so a clear is no longer undoable.
|
|
9845
|
+
if (isDisabled) {
|
|
9846
|
+
this.clearedFile = undefined;
|
|
9847
|
+
}
|
|
9848
|
+
this.cdr.markForCheck();
|
|
9715
9849
|
}
|
|
9716
9850
|
// ---- UI getters ----
|
|
9851
|
+
/** An absent clearEnabled offers the clear, as its default in the form definition. */
|
|
9852
|
+
get isClearEnabled() {
|
|
9853
|
+
return this._widgetInstance.fileUploaderProperties?.clearEnabled !== false;
|
|
9854
|
+
}
|
|
9717
9855
|
get fileIcon() {
|
|
9718
9856
|
return 'insert_drive_file';
|
|
9719
9857
|
}
|
|
@@ -9730,6 +9868,17 @@ class SmartFileEditorComponent {
|
|
|
9730
9868
|
};
|
|
9731
9869
|
return changeDescriptor;
|
|
9732
9870
|
}
|
|
9871
|
+
/** The delete icon's own descriptor: nothing of the change or upload action, so no tooltip. */
|
|
9872
|
+
get deleteButton() {
|
|
9873
|
+
var changeButton = this.changeButton;
|
|
9874
|
+
return {
|
|
9875
|
+
title: '',
|
|
9876
|
+
color: 'warn',
|
|
9877
|
+
type: changeButton.type,
|
|
9878
|
+
icon: 'delete',
|
|
9879
|
+
iconPosition: changeButton.iconPosition,
|
|
9880
|
+
};
|
|
9881
|
+
}
|
|
9733
9882
|
get removeButton() {
|
|
9734
9883
|
var changeButton = this.changeButton;
|
|
9735
9884
|
return {
|
|
@@ -9750,7 +9899,7 @@ class SmartFileEditorComponent {
|
|
|
9750
9899
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
9751
9900
|
multi: true,
|
|
9752
9901
|
},
|
|
9753
|
-
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--
|
|
9902
|
+
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
|
|
9754
9903
|
}
|
|
9755
9904
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFileEditorComponent, decorators: [{
|
|
9756
9905
|
type: Component,
|
|
@@ -9760,7 +9909,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
9760
9909
|
useExisting: forwardRef(() => SmartFileEditorComponent),
|
|
9761
9910
|
multi: true,
|
|
9762
9911
|
},
|
|
9763
|
-
], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--
|
|
9912
|
+
], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"] }]
|
|
9764
9913
|
}], propDecorators: { widgetInstance: [{
|
|
9765
9914
|
type: Input
|
|
9766
9915
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
|
|
@@ -10607,9 +10756,10 @@ function injectSmartHtmlRenderer(container) {
|
|
|
10607
10756
|
|
|
10608
10757
|
/**
|
|
10609
10758
|
* The HTML widget: server-sent HTML whose client placeholders are resolved against the model of
|
|
10610
|
-
* the form it sits on, the widget's `root` applied. It
|
|
10611
|
-
* its own; it follows the form's revision
|
|
10612
|
-
* rewrites the html only when the
|
|
10759
|
+
* the form it sits on, its controls' unsubmitted edits included, the widget's `root` applied. It
|
|
10760
|
+
* has no control, no label and no value of its own; it follows the form's revision, every edit of
|
|
10761
|
+
* a control and the value sets it reads and re-resolves, which rewrites the html only when the
|
|
10762
|
+
* result differs. See HTML-WIDGET.md.
|
|
10613
10763
|
*/
|
|
10614
10764
|
class SmartHtmlWidgetComponent {
|
|
10615
10765
|
constructor() {
|
|
@@ -10622,13 +10772,14 @@ class SmartHtmlWidgetComponent {
|
|
|
10622
10772
|
...(ngDevMode ? [{ debugName: "widgetInstance" }] : /* istanbul ignore next */ []));
|
|
10623
10773
|
effect(() => {
|
|
10624
10774
|
this.service.revision();
|
|
10775
|
+
this.service.controlRevision();
|
|
10625
10776
|
this.valueSetReads.changed();
|
|
10626
10777
|
const properties = this.widgetInstance().htmlProperties;
|
|
10627
10778
|
const model = this.service.smartForm?.componentModel;
|
|
10628
10779
|
this.warnUnloaded(properties);
|
|
10629
10780
|
this.renderer.useCss(properties?.css);
|
|
10630
10781
|
this.renderer.render(properties?.html ?? '', {
|
|
10631
|
-
data: this.scope(
|
|
10782
|
+
data: this.scope(this.service.buildModelWithEdits(), properties?.root, properties?.html),
|
|
10632
10783
|
findValueSetData: this.valueSetReads.track(createValueSetLookup(model?.valueSets, this.valueSetStore, this.smartComponent?.uuid)),
|
|
10633
10784
|
icons: properties?.icons,
|
|
10634
10785
|
});
|
|
@@ -10672,6 +10823,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10672
10823
|
// ---- ControlValueAccessor implementation ----
|
|
10673
10824
|
this.onChange = (value) => { };
|
|
10674
10825
|
this.onTouched = () => { };
|
|
10826
|
+
/** The editor's disabled state: the widget's at creation, then the form control's. */
|
|
10675
10827
|
this.isDisabled = false;
|
|
10676
10828
|
}
|
|
10677
10829
|
get widgetInstance() {
|
|
@@ -10687,6 +10839,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10687
10839
|
typeof structuredClone === 'function'
|
|
10688
10840
|
? structuredClone(value)
|
|
10689
10841
|
: JSON.parse(JSON.stringify(value));
|
|
10842
|
+
this.isDisabled = !!value.isDisabled;
|
|
10690
10843
|
this.cdr.markForCheck();
|
|
10691
10844
|
}
|
|
10692
10845
|
upload(event) {
|
|
@@ -10773,6 +10926,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10773
10926
|
}
|
|
10774
10927
|
setDisabledState(isDisabled) {
|
|
10775
10928
|
this.isDisabled = isDisabled;
|
|
10929
|
+
this.cdr.markForCheck();
|
|
10776
10930
|
}
|
|
10777
10931
|
// ---- UI getters ----
|
|
10778
10932
|
get removeIcon() {
|
|
@@ -10791,7 +10945,7 @@ class SmartMultiFileEditorComponent {
|
|
|
10791
10945
|
useExisting: forwardRef(() => SmartMultiFileEditorComponent),
|
|
10792
10946
|
multi: true,
|
|
10793
10947
|
},
|
|
10794
|
-
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--
|
|
10948
|
+
], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }] }); }
|
|
10795
10949
|
}
|
|
10796
10950
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartMultiFileEditorComponent, decorators: [{
|
|
10797
10951
|
type: Component,
|
|
@@ -10801,7 +10955,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10801
10955
|
useExisting: forwardRef(() => SmartMultiFileEditorComponent),
|
|
10802
10956
|
multi: true,
|
|
10803
10957
|
},
|
|
10804
|
-
], imports: [NgClass, NgStyle, UiActionToolbarComponent, SmartIconComponent, UploadWidgetComponent], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--
|
|
10958
|
+
], imports: [NgClass, NgStyle, UiActionToolbarComponent, SmartIconComponent, UploadWidgetComponent], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"] }]
|
|
10805
10959
|
}], propDecorators: { widgetInstance: [{
|
|
10806
10960
|
type: Input
|
|
10807
10961
|
}], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }], fileEvent: [{ type: i0.Output, args: ["fileEvent"] }] } });
|
|
@@ -11190,6 +11344,16 @@ class SmartformwidgetComponent {
|
|
|
11190
11344
|
this.addOnBlur = true;
|
|
11191
11345
|
this.separatorKeysCodes = [ENTER, COMMA];
|
|
11192
11346
|
this.emptySeparatorKeysCodes = [];
|
|
11347
|
+
/**
|
|
11348
|
+
* The mat-select comparison of the widget's keys, by the Versioned Keys of its set. An arrow
|
|
11349
|
+
* property, as the template hands it to mat-select unbound.
|
|
11350
|
+
*/
|
|
11351
|
+
this.compareItems = (o1, o2) => {
|
|
11352
|
+
const versionedKeys = this.service.hasVersionedKeys(this.widgetInstance);
|
|
11353
|
+
const v1 = o1 && typeof o1 === 'string' ? createReferenceKey(o1, versionedKeys) : o1;
|
|
11354
|
+
const v2 = o2 && typeof o2 === 'string' ? createReferenceKey(o2, versionedKeys) : o2;
|
|
11355
|
+
return deepEqual(v1, v2);
|
|
11356
|
+
};
|
|
11193
11357
|
// The form changes the widget objects it has already handed to this template — values,
|
|
11194
11358
|
// constraints, the touched state of the controls — and a mutation of a plain object is
|
|
11195
11359
|
// invisible to change detection. Registering here rather than being found by a view query
|
|
@@ -12036,19 +12200,6 @@ class SmartformwidgetComponent {
|
|
|
12036
12200
|
}
|
|
12037
12201
|
this.setDateFormControl(control, yearMonthValue, '00:00');
|
|
12038
12202
|
}
|
|
12039
|
-
compareItems(o1, o2) {
|
|
12040
|
-
function convert(obj) {
|
|
12041
|
-
if (obj && typeof obj === 'string') {
|
|
12042
|
-
return obj.replace(/\.v[0-9]+$/, '');
|
|
12043
|
-
}
|
|
12044
|
-
return obj;
|
|
12045
|
-
}
|
|
12046
|
-
// calling this.truncateUri didn't work
|
|
12047
|
-
// TODO: use widget.selection.parameter for ignoreUriVersion / strictUriComparison;
|
|
12048
|
-
let v1 = convert(o1);
|
|
12049
|
-
let v2 = convert(o2);
|
|
12050
|
-
return deepEqual(v1, v2);
|
|
12051
|
-
}
|
|
12052
12203
|
truncateUri(uri) {
|
|
12053
12204
|
if (uri && typeof uri === 'string') {
|
|
12054
12205
|
return uri.replace(/\.v[0-9]+$/, '');
|