@mintplayer/ng-spark 22.23.0 → 22.25.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 +196 -0
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +100 -47
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs +310 -329
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs +175 -135
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-contributions.mjs +721 -0
- package/fesm2022/mintplayer-ng-spark-contributions.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-grid.mjs +2385 -1118
- package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-history.mjs +598 -0
- package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-icon.mjs +64 -27
- package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs +405 -762
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-moderation.mjs +1212 -0
- package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-panels.mjs +60 -0
- package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +1158 -765
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +273 -169
- package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +795 -410
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +1196 -280
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +2244 -1224
- package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +571 -303
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-renderers.mjs +44 -55
- package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +216 -177
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-routes.mjs +24 -28
- package/fesm2022/mintplayer-ng-spark-routes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-services.mjs +625 -640
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-shell.mjs +1031 -397
- package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +294 -0
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark.mjs +14 -48
- package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
- package/package.json +23 -2
- package/types/mintplayer-ng-spark-attribute-description.d.ts +25 -18
- package/types/mintplayer-ng-spark-client-operations.d.ts +139 -160
- package/types/mintplayer-ng-spark-column-filter.d.ts +73 -55
- package/types/mintplayer-ng-spark-contributions.d.ts +231 -0
- package/types/mintplayer-ng-spark-grid.d.ts +917 -448
- package/types/mintplayer-ng-spark-history.d.ts +216 -0
- package/types/mintplayer-ng-spark-icon.d.ts +15 -13
- package/types/mintplayer-ng-spark-models.d.ts +681 -573
- package/types/mintplayer-ng-spark-moderation.d.ts +402 -0
- package/types/mintplayer-ng-spark-panels.d.ts +143 -0
- package/types/mintplayer-ng-spark-pipes.d.ts +131 -149
- package/types/mintplayer-ng-spark-po-create.d.ts +46 -37
- package/types/mintplayer-ng-spark-po-detail.d.ts +180 -117
- package/types/mintplayer-ng-spark-po-edit.d.ts +367 -64
- package/types/mintplayer-ng-spark-po-form.d.ts +558 -373
- package/types/mintplayer-ng-spark-query-list.d.ts +143 -82
- package/types/mintplayer-ng-spark-renderers.d.ts +121 -85
- package/types/mintplayer-ng-spark-retry-action-modal.d.ts +31 -34
- package/types/mintplayer-ng-spark-routes.d.ts +7 -11
- package/types/mintplayer-ng-spark-services.d.ts +377 -261
- package/types/mintplayer-ng-spark-shell.d.ts +228 -132
- package/types/mintplayer-ng-spark-soft-delete.d.ts +90 -0
- package/types/mintplayer-ng-spark.d.ts +19 -12
|
@@ -1,65 +1,368 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import * as
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
private allEntityTypes;
|
|
27
|
-
generalErrors: _angular_core.Signal<ValidationError[]>;
|
|
28
|
-
constructor();
|
|
29
|
-
private onParamsChange;
|
|
30
|
-
initFormData(): void;
|
|
31
|
-
/**
|
|
32
|
-
* Resolves a nested AsDetail type by CLR name, for rebuilding the nested PO wire shape on save.
|
|
33
|
-
*
|
|
34
|
-
* ⚠️ <b>`detailTypes` first, catalogue second.</b> The catalogue from `/spark/types` is
|
|
35
|
-
* <b>Query-gated</b>, and an AsDetail row type usually has no rights of its own — nobody grants
|
|
36
|
-
* `Query/GateSettings`, because a gate is edited through the Repository that owns it. So the
|
|
37
|
-
* catalogue does not contain it, the resolver returned undefined, and the save quietly took the
|
|
38
|
-
* scalar branch: the attribute went out as a raw dict under `value` instead of a nested PO under
|
|
39
|
-
* `object`, the server could not map it, and `EntityMapper`'s conversion `catch` swallowed the
|
|
40
|
-
* failure. The save reported success and wrote nothing.
|
|
41
|
-
* <para>
|
|
42
|
-
* `detailTypes` is the parent's own copy of its row types, carried on the type definition for
|
|
43
|
-
* exactly this reason (#385 fixed the rendering half; this is the save half). It is gated on the
|
|
44
|
-
* parent's right, which is the right that governs editing the row anyway.
|
|
45
|
-
* </para>
|
|
46
|
-
*/
|
|
47
|
-
private resolveEntityType;
|
|
48
|
-
/**
|
|
49
|
-
* The attributes this page will read values from when it builds the save.
|
|
50
|
-
*
|
|
51
|
-
* ⚠️ <b>Overlaid before filtering, not after loading.</b> A refresh hook can reveal an attribute
|
|
52
|
-
* that was hidden when the object was loaded, and the ordinary reason it does so is that the
|
|
53
|
-
* attribute has just become required. Filtering on the loaded state left such an attribute out of
|
|
54
|
-
* both {@link initFormData} and the save payload, so the user filled in a field whose value was
|
|
55
|
-
* then dropped on the floor and refused by the server as missing — with no way out of the form.
|
|
56
|
-
* The overlay is the form's, bound two-way, so the two halves cannot drift again.
|
|
57
|
-
*/
|
|
58
|
-
getEditableAttributes(): _mintplayer_ng_spark_models.EntityAttributeDefinition[];
|
|
59
|
-
onSave(): Promise<void>;
|
|
60
|
-
onCancel(): void;
|
|
61
|
-
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkPoEditComponent, never>;
|
|
62
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkPoEditComponent, "spark-po-edit", never, {}, { "saved": "saved"; "cancelled": "cancelled"; }, never, never, true, never>;
|
|
1
|
+
import { Color } from "@mintplayer/ng-bootstrap";
|
|
2
|
+
import { EntityAttributeDefinition, EntityType, EntityTypeResolver, PersistentObject, QueryColumn, QueryResultItem, RefreshOverlay, ValidationError } from "@mintplayer/ng-spark/models";
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
import { ReferenceChip } from "@mintplayer/ng-spark/pipes";
|
|
5
|
+
/**
|
|
6
|
+
* Three-way merge of an edit form after a 409 (contributions PRD §5, Q10).
|
|
7
|
+
*
|
|
8
|
+
* The three sides are all in the form's own flat-dict shape (`nestedPoToDict`): **base** is the
|
|
9
|
+
* object as this page loaded it, **mine** is the form now, **theirs** is the object re-fetched after
|
|
10
|
+
* the server refused the save. Comparing in the form's shape rather than on the wire matters: the
|
|
11
|
+
* form coerces while it loads (an unset string becomes `''`, a date becomes a wall clock), so a
|
|
12
|
+
* wire-shaped base would report every such field as "changed by me" and turn an edit the other
|
|
13
|
+
* user made to it into a false conflict. All three sides go through the same flattening, so only
|
|
14
|
+
* real edits differ.
|
|
15
|
+
*
|
|
16
|
+
* Pure: no Angular, no I/O. Values are compared with `formValuesEqual`, which ignores the per-read
|
|
17
|
+
* fields a flattened row carries (row key, breadcrumb, original dates).
|
|
18
|
+
*/
|
|
19
|
+
/** Which side a conflict is resolved to. */
|
|
20
|
+
export type ConflictSide = 'mine' | 'theirs';
|
|
21
|
+
export interface MergeSchema {
|
|
22
|
+
/** The attributes being merged: the ones the form edits. */
|
|
23
|
+
attributes: EntityAttributeDefinition[];
|
|
24
|
+
/** Resolves AsDetail row types by CLR name, to merge rows per attribute. */
|
|
25
|
+
resolve: EntityTypeResolver;
|
|
63
26
|
}
|
|
64
|
-
|
|
65
|
-
|
|
27
|
+
export interface MergeConflict {
|
|
28
|
+
/**
|
|
29
|
+
* Where the conflict sits, and the key its choice is given under: `Title`, `Address.Street`,
|
|
30
|
+
* `Lyrics[ko/Kore]` for a whole row, `Lyrics[ko/Kore].Text` for one attribute of a row.
|
|
31
|
+
*/
|
|
32
|
+
path: string;
|
|
33
|
+
/** `value`: both sides changed one attribute differently. `row`: a row keep/remove or add/add clash. */
|
|
34
|
+
kind: 'value' | 'row';
|
|
35
|
+
/** The attribute the values belong to — for a row conflict, the AsDetail list attribute. */
|
|
36
|
+
attribute: EntityAttributeDefinition;
|
|
37
|
+
/** The top-level attribute this sits under (the attribute itself at the root). */
|
|
38
|
+
rootAttribute: EntityAttributeDefinition;
|
|
39
|
+
/** The path of the object this sits in: `''` at the root, `Lyrics[ko/Kore]` inside a row. */
|
|
40
|
+
group: string;
|
|
41
|
+
/** A readable name for the row this sits in, when it is inside one. */
|
|
42
|
+
rowLabel?: string;
|
|
43
|
+
/**
|
|
44
|
+
* For a row conflict, or a value conflict on an embedded (AsDetail) object: the row type's
|
|
45
|
+
* attributes, so the values can be shown cell by cell. Absent when the row type did not resolve.
|
|
46
|
+
*/
|
|
47
|
+
rowAttributes?: EntityAttributeDefinition[];
|
|
48
|
+
/** The row type's name, to filter the server's type-name breadcrumb placeholder. */
|
|
49
|
+
rowTypeName?: string;
|
|
50
|
+
/** The values, in the form's shape. For a row conflict, the row dict, or `undefined` when absent. */
|
|
51
|
+
base: unknown;
|
|
52
|
+
mine: unknown;
|
|
53
|
+
theirs: unknown;
|
|
54
|
+
}
|
|
55
|
+
/** Something the other side changed that the merge took over. */
|
|
56
|
+
export interface TheirChange {
|
|
57
|
+
path: string;
|
|
58
|
+
attribute: EntityAttributeDefinition;
|
|
59
|
+
rootAttribute: EntityAttributeDefinition;
|
|
60
|
+
}
|
|
61
|
+
export interface MergeResult {
|
|
62
|
+
/** The form values to continue editing with. Unresolved conflicts hold THEIR value. */
|
|
63
|
+
merged: Record<string, any>;
|
|
64
|
+
/** Only true conflicts: both sides changed the same thing to different content. */
|
|
65
|
+
conflicts: MergeConflict[];
|
|
66
|
+
/** What the other side changed that was taken over without a conflict. */
|
|
67
|
+
theirChanges: TheirChange[];
|
|
68
|
+
/**
|
|
69
|
+
* Contribution rows (a `[Contribution]` property, contributions M5b) that I changed but that another
|
|
70
|
+
* contributor changed too: theirs won, my edit of them was dropped — say so.
|
|
71
|
+
*/
|
|
72
|
+
contributionNotices: ContributionNotice[];
|
|
73
|
+
}
|
|
74
|
+
/** A contribution row taken from theirs over my own edit, because another contributor wrote it. */
|
|
75
|
+
export interface ContributionNotice {
|
|
76
|
+
/** `Lyrics[en/Latn]`. */
|
|
77
|
+
path: string;
|
|
78
|
+
/** The list attribute. */
|
|
79
|
+
attribute: EntityAttributeDefinition;
|
|
80
|
+
/** A readable name for the row (its breadcrumb, else its key). */
|
|
81
|
+
rowLabel: string;
|
|
82
|
+
}
|
|
83
|
+
/** The key under which the server marks a contribution row (`metadata.contribution`). */
|
|
84
|
+
export declare const CONTRIBUTION_ROW_METADATA = "contribution";
|
|
85
|
+
/**
|
|
86
|
+
* Whether a flattened row is a contribution row whose shown version the caller wrote: `true` / `false`
|
|
87
|
+
* for a contribution row, `undefined` for any other row (the server marks contribution rows on load).
|
|
88
|
+
*/
|
|
89
|
+
export declare function contributionRowIsOwn(row: Record<string, any> | undefined): boolean | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Merges `mine` onto `theirs` against `base`.
|
|
92
|
+
*
|
|
93
|
+
* - An attribute changed only by me keeps mine; changed only by them takes theirs; changed by both
|
|
94
|
+
* to the same content is no conflict; changed by both differently is a conflict.
|
|
95
|
+
* - A read-only attribute always takes theirs, silently: the user cannot have edited it.
|
|
96
|
+
* - AsDetail lists are matched per row by the row key (`[ValueKey]`). A row added, removed or changed
|
|
97
|
+
* on one side only comes from that side. The same key added on both sides with different content,
|
|
98
|
+
* or a row removed on one side and edited on the other, is a row conflict. A row changed on both
|
|
99
|
+
* sides is merged per attribute. The order is theirs, then the rows only I have.
|
|
100
|
+
*
|
|
101
|
+
* `choices` resolves conflicts by path; a conflict without a choice resolves to theirs. Run it once
|
|
102
|
+
* without choices to find the conflicts, and again with the user's choices to build the result —
|
|
103
|
+
* the conflicts reported do not depend on the choices.
|
|
104
|
+
*/
|
|
105
|
+
export declare function mergeThreeWay(base: Record<string, any>, mine: Record<string, any>, theirs: Record<string, any>, schema: MergeSchema, choices?: Readonly<Record<string, ConflictSide>>): MergeResult;
|
|
106
|
+
/**
|
|
107
|
+
* Reference labels by referenced id, for rendering a Reference in the conflict dialog.
|
|
108
|
+
*
|
|
109
|
+
* The form keeps only a reference's id (`nestedPoToDict` drops the breadcrumbs), so a conflict value
|
|
110
|
+
* on its own renders as the raw id. The labels live elsewhere: on the objects as read — the object as
|
|
111
|
+
* loaded and the re-fetched one each carry the server-resolved breadcrumb of every reference they
|
|
112
|
+
* hold, rows included — and, for a reference the user just picked, in the candidate list the picker
|
|
113
|
+
* chose it from (a picker only ever selects from those options). Ids are document ids, unique across
|
|
114
|
+
* collections, so one flat map serves every attribute and every row.
|
|
115
|
+
*/
|
|
116
|
+
export type ReferenceLabels = Record<string, string>;
|
|
117
|
+
/** Adds the labels of every reference in `po` (recursing into AsDetail rows). The first label for an id wins. */
|
|
118
|
+
export declare function addReferenceLabelsOf(po: PersistentObject | null | undefined, into: ReferenceLabels): ReferenceLabels;
|
|
119
|
+
/** Adds the labels of reference candidates (`QueryResultItem.breadcrumb` by `id`). The first label for an id wins. */
|
|
120
|
+
export declare function addReferenceLabelsOfOptions(options: Iterable<readonly QueryResultItem[] | undefined>, into: ReferenceLabels): ReferenceLabels;
|
|
121
|
+
/** A 409 whose merge found true conflicts: everything needed to re-run it with the user's choices. */
|
|
122
|
+
interface PendingConflict {
|
|
123
|
+
/** The object as loaded, for its reference labels. */
|
|
124
|
+
loaded: PersistentObject;
|
|
125
|
+
theirs: PersistentObject;
|
|
126
|
+
base: Record<string, any>;
|
|
127
|
+
mine: Record<string, any>;
|
|
128
|
+
theirsForm: Record<string, any>;
|
|
129
|
+
schema: MergeSchema;
|
|
130
|
+
result: MergeResult;
|
|
131
|
+
}
|
|
132
|
+
export declare class SparkPoEditComponent {
|
|
133
|
+
private readonly route;
|
|
134
|
+
private readonly router;
|
|
135
|
+
private readonly sparkService;
|
|
136
|
+
private readonly language;
|
|
137
|
+
/** `SparkConfig.conflictDialog.showChangedBy`: whether the conflict notice may name a user at all. */
|
|
138
|
+
private readonly showChangedBy;
|
|
139
|
+
saved: import("@angular/core").OutputEmitterRef<PersistentObject>;
|
|
140
|
+
cancelled: import("@angular/core").OutputEmitterRef<void>;
|
|
141
|
+
colors: typeof Color;
|
|
142
|
+
entityType: import("@angular/core").WritableSignal<EntityType | null>;
|
|
143
|
+
item: import("@angular/core").WritableSignal<PersistentObject | null>;
|
|
144
|
+
/**
|
|
145
|
+
* The type as this form edits it: the loaded object can be stricter than the type, because a
|
|
146
|
+
* server hook may mark an attribute read-only for this row and this caller (QnA: a translator who
|
|
147
|
+
* is not the question's author edits only its translations; the server drops the rest from the
|
|
148
|
+
* save anyway). Shared by the form and {@link getEditableAttributes}, so what is drawn, what is
|
|
149
|
+
* initialised and what is saved agree. A refresh overlay can still lift it.
|
|
150
|
+
*/
|
|
151
|
+
formEntityType: import("@angular/core").Signal<EntityType | null>;
|
|
152
|
+
type: string;
|
|
153
|
+
id: string;
|
|
154
|
+
formData: import("@angular/core").WritableSignal<Record<string, any>>;
|
|
155
|
+
/**
|
|
156
|
+
* Bound two-way to the form, which is where refreshes land. Read by
|
|
157
|
+
* {@link getEditableAttributes} so the save sees the object the user was actually shown.
|
|
158
|
+
*/
|
|
159
|
+
refreshOverlay: import("@angular/core").WritableSignal<RefreshOverlay>;
|
|
160
|
+
validationErrors: import("@angular/core").WritableSignal<ValidationError[]>;
|
|
161
|
+
isSaving: import("@angular/core").WritableSignal<boolean>;
|
|
162
|
+
private allEntityTypes;
|
|
163
|
+
generalErrors: import("@angular/core").Signal<ValidationError[]>;
|
|
164
|
+
/** After a 409 was merged: what they changed, and that nothing has been saved yet. */
|
|
165
|
+
private readonly conflictNoticeText;
|
|
166
|
+
/** The notice with its "Changed by X at T", which fills in the name once History resolved it. */
|
|
167
|
+
readonly conflictNotice: import("@angular/core").Signal<string | null>;
|
|
168
|
+
/** A 409 whose merge has true conflicts, waiting on the dialog. */
|
|
169
|
+
pendingConflict: import("@angular/core").WritableSignal<PendingConflict | null>;
|
|
170
|
+
protected readonly pendingConflicts: import("@angular/core").Signal<import("@mintplayer/ng-spark/po-edit").MergeConflict[]>;
|
|
171
|
+
/**
|
|
172
|
+
* The name History resolved for whoever wrote the version with `etag`, or null. Only ever set from
|
|
173
|
+
* the revision list, so it is there only when `SparkConfig.conflictDialog.showChangedBy` is true and
|
|
174
|
+
* the caller holds `History/T` (see {@link lookUpChangedBy}).
|
|
175
|
+
*/
|
|
176
|
+
private readonly changedByName;
|
|
177
|
+
private readonly form;
|
|
178
|
+
private readonly locale;
|
|
179
|
+
constructor();
|
|
180
|
+
private onParamsChange;
|
|
181
|
+
initFormData(): void;
|
|
182
|
+
/**
|
|
183
|
+
* The form's flat-dict shape of `currentItem`. Also how a conflict merge brings the object as
|
|
184
|
+
* loaded and as re-fetched into the form's shape, so all three sides went through the same
|
|
185
|
+
* coercions and only real edits differ.
|
|
186
|
+
*/
|
|
187
|
+
private formDataFrom;
|
|
188
|
+
/**
|
|
189
|
+
* Resolves a nested AsDetail type by CLR name, for rebuilding the nested PO wire shape on save.
|
|
190
|
+
*
|
|
191
|
+
* ⚠️ <b>`detailTypes` first, catalogue second.</b> The catalogue from `/spark/types` is
|
|
192
|
+
* <b>Query-gated</b>, and an AsDetail row type usually has no rights of its own — nobody grants
|
|
193
|
+
* `Query/GateSettings`, because a gate is edited through the Repository that owns it. So the
|
|
194
|
+
* catalogue does not contain it, the resolver returned undefined, and the save quietly took the
|
|
195
|
+
* scalar branch: the attribute went out as a raw dict under `value` instead of a nested PO under
|
|
196
|
+
* `object`, the server could not map it, and `EntityMapper`'s conversion `catch` swallowed the
|
|
197
|
+
* failure. The save reported success and wrote nothing.
|
|
198
|
+
* <para>
|
|
199
|
+
* `detailTypes` is the parent's own copy of its row types, carried on the type definition for
|
|
200
|
+
* exactly this reason (#385 fixed the rendering half; this is the save half). It is gated on the
|
|
201
|
+
* parent's right, which is the right that governs editing the row anyway.
|
|
202
|
+
* </para>
|
|
203
|
+
*/
|
|
204
|
+
private resolveEntityType;
|
|
205
|
+
/**
|
|
206
|
+
* The attributes this page will read values from when it builds the save.
|
|
207
|
+
*
|
|
208
|
+
* ⚠️ <b>Overlaid before filtering, not after loading.</b> A refresh hook can reveal an attribute
|
|
209
|
+
* that was hidden when the object was loaded, and the ordinary reason it does so is that the
|
|
210
|
+
* attribute has just become required. Filtering on the loaded state left such an attribute out of
|
|
211
|
+
* both {@link initFormData} and the save payload, so the user filled in a field whose value was
|
|
212
|
+
* then dropped on the floor and refused by the server as missing — with no way out of the form.
|
|
213
|
+
* The overlay is the form's, bound two-way, so the two halves cannot drift again.
|
|
214
|
+
*/
|
|
215
|
+
getEditableAttributes(): import("@mintplayer/ng-spark/models").EntityAttributeDefinition[];
|
|
216
|
+
onSave(): Promise<void>;
|
|
217
|
+
onCancel(): void;
|
|
218
|
+
/**
|
|
219
|
+
* After a 409: re-fetch the object (a normal read, so read rights and row security apply) and
|
|
220
|
+
* merge this form onto it, three-way against the object as loaded (contributions PRD §5, Q10).
|
|
221
|
+
* Disjoint edits merge silently and the page says what they changed; true conflicts open the
|
|
222
|
+
* dialog. Either way the form is rebased onto the fresh etag and **nothing is saved** — the user
|
|
223
|
+
* reviews and saves, and the server's validation and business rules run again on the result.
|
|
224
|
+
*/
|
|
225
|
+
private resolveConcurrencyConflict;
|
|
226
|
+
/**
|
|
227
|
+
* Reference labels for the dialog, by id. The form keeps only ids; the labels are on the two reads
|
|
228
|
+
* (theirs first: it is the fresher) and, for a reference the user just picked, in the candidate
|
|
229
|
+
* lists the form's pickers choose from — top-level and per AsDetail column.
|
|
230
|
+
*/
|
|
231
|
+
protected readonly conflictReferenceLabels: import("@angular/core").Signal<ReferenceLabels>;
|
|
232
|
+
/**
|
|
233
|
+
* Resolves who wrote their version to a display name, through History: the newest revision from
|
|
234
|
+
* `POST /spark/po/revisions` (page size 1) carries the name the app's `IHistoryUserNameResolver`
|
|
235
|
+
* gave it. Taken only when that revision is the version the merge ran against, and — when the
|
|
236
|
+
* object has `ModifiedBy` — written by that same user.
|
|
237
|
+
*
|
|
238
|
+
* Rights: asked only with `History/T` (the endpoint refuses anyone else anyway), so a caller who may
|
|
239
|
+
* not read the history never sees a name from it. Without History installed, or on any failure,
|
|
240
|
+
* nothing is set and the line names no user. Called only when `SparkConfig.conflictDialog.showChangedBy`
|
|
241
|
+
* is true.
|
|
242
|
+
*/
|
|
243
|
+
private lookUpChangedBy;
|
|
244
|
+
/** The dialog's choices, applied on top of the merge. */
|
|
245
|
+
onConflictResolved(choices: Record<string, ConflictSide>): void;
|
|
246
|
+
/** Closing the dialog changes nothing: the form keeps its values and the conflict message. */
|
|
247
|
+
onConflictCancelled(): void;
|
|
248
|
+
/**
|
|
249
|
+
* Makes `theirs` the object this page edits: its etag is what the next save sends, and it is the
|
|
250
|
+
* base a second conflict is merged against. `isValueChanged` is computed against it on save, so
|
|
251
|
+
* only what differs from their version is sent as changed.
|
|
252
|
+
*/
|
|
253
|
+
private rebase;
|
|
254
|
+
/** The dialog's "they also changed" line, or null when the merge took nothing of theirs. */
|
|
255
|
+
protected readonly theyChanged: import("@angular/core").Signal<string | null>;
|
|
256
|
+
protected readonly conflictAudit: import("@angular/core").Signal<string | null>;
|
|
257
|
+
/**
|
|
258
|
+
* "Changed by X at T" for an `IAuditable` target. History stamps `ModifiedBy`/`ModifiedAt` on the
|
|
259
|
+
* entity, and they reach this page only when the model declares them as attributes. `ModifiedBy` is
|
|
260
|
+
* a user id (History stores ids, never names), and that id is never shown. X is the name History
|
|
261
|
+
* resolved for exactly this version ({@link lookUpChangedBy}), which is only looked up when
|
|
262
|
+
* `SparkConfig.conflictDialog.showChangedBy` is true; with no resolved name there is no user part.
|
|
263
|
+
* Either half is shown alone when only it is present, and nothing when neither is.
|
|
264
|
+
*/
|
|
265
|
+
private auditLine;
|
|
266
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkPoEditComponent, never>;
|
|
267
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkPoEditComponent, "spark-po-edit", never, {}, {
|
|
268
|
+
"saved": "saved";
|
|
269
|
+
"cancelled": "cancelled";
|
|
270
|
+
}, never, never, true, never>;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* One side of a conflict: ready for `<spark-grid-cell>`, or, for a whole row or embedded object, its
|
|
274
|
+
* attributes cell by cell (`fields`), or plain text (a removed row, or a value with no type to show).
|
|
275
|
+
*/
|
|
276
|
+
interface ConflictCell {
|
|
277
|
+
column: QueryColumn;
|
|
278
|
+
display: unknown;
|
|
279
|
+
rendererValue: unknown;
|
|
280
|
+
chips: ReferenceChip[];
|
|
281
|
+
item: PersistentObject;
|
|
282
|
+
text?: string;
|
|
283
|
+
fields?: ConflictField[];
|
|
284
|
+
}
|
|
285
|
+
/** One attribute of a row on one side, and whether the other side holds something else there. */
|
|
286
|
+
interface ConflictField {
|
|
287
|
+
attribute: EntityAttributeDefinition;
|
|
288
|
+
cell: ConflictCell;
|
|
289
|
+
differs: boolean;
|
|
290
|
+
}
|
|
291
|
+
interface ConflictGroup {
|
|
292
|
+
key: string;
|
|
293
|
+
/** The root attribute and row this group sits in; empty for plain top-level attributes. */
|
|
294
|
+
attribute?: EntityAttributeDefinition;
|
|
295
|
+
rowLabel?: string;
|
|
296
|
+
entries: {
|
|
297
|
+
conflict: MergeConflict;
|
|
298
|
+
mine: ConflictCell;
|
|
299
|
+
theirs: ConflictCell;
|
|
300
|
+
}[];
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* The true conflicts of a three-way merge after a 409, Mine or Theirs per conflict (grouped by
|
|
304
|
+
* AsDetail row), rendered with the same `<spark-grid-cell>` a query grid and History use. It only
|
|
305
|
+
* collects choices: applying them, and rebasing onto the fresh etag, is the edit page's job — and
|
|
306
|
+
* nothing is saved from here.
|
|
307
|
+
*/
|
|
308
|
+
export declare class SparkPoConflictDialogComponent {
|
|
309
|
+
private readonly language;
|
|
310
|
+
private readonly gridRenderers;
|
|
311
|
+
/** The true conflicts; the dialog is open while there are any. */
|
|
312
|
+
conflicts: import("@angular/core").InputSignal<MergeConflict[]>;
|
|
313
|
+
/**
|
|
314
|
+
* Reference labels by id — the form keeps only ids. Built by the edit page from the object as
|
|
315
|
+
* loaded, the re-fetched one, and the form's reference candidates (see `reference-labels.ts`).
|
|
316
|
+
* An id without a label shows as the id.
|
|
317
|
+
*/
|
|
318
|
+
referenceLabels: import("@angular/core").InputSignal<ReferenceLabels>;
|
|
319
|
+
/** "Changed by X at T", when the object is audited. */
|
|
320
|
+
audit: import("@angular/core").InputSignal<string | null>;
|
|
321
|
+
/** "They also changed: …", the changes merged without a conflict. */
|
|
322
|
+
theyChanged: import("@angular/core").InputSignal<string | null>;
|
|
323
|
+
resolved: import("@angular/core").OutputEmitterRef<Record<string, ConflictSide>>;
|
|
324
|
+
cancelled: import("@angular/core").OutputEmitterRef<void>;
|
|
325
|
+
colors: typeof Color;
|
|
326
|
+
protected readonly choices: import("@angular/core").WritableSignal<Record<string, ConflictSide>>;
|
|
327
|
+
private readonly lookupOptions;
|
|
328
|
+
protected readonly complete: import("@angular/core").Signal<boolean>;
|
|
329
|
+
protected readonly groups: import("@angular/core").Signal<ConflictGroup[]>;
|
|
330
|
+
constructor();
|
|
331
|
+
choose(path: string, side: ConflictSide): void;
|
|
332
|
+
chooseAll(side: ConflictSide): void;
|
|
333
|
+
apply(): void;
|
|
334
|
+
/**
|
|
335
|
+
* One side of a conflict. A whole row (or an embedded object, when its type resolved) is shown
|
|
336
|
+
* attribute by attribute with the same cells as a scalar, marking where `other` — the other side —
|
|
337
|
+
* holds something else; a row absent on this side is "(removed)", which is what choosing it means.
|
|
338
|
+
*/
|
|
339
|
+
private sideCell;
|
|
340
|
+
/** One attribute's value, in the form's shape, as a `<spark-grid-cell>`. */
|
|
341
|
+
private cell;
|
|
342
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkPoConflictDialogComponent, never>;
|
|
343
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkPoConflictDialogComponent, "spark-po-conflict-dialog", never, {
|
|
344
|
+
"conflicts": {
|
|
345
|
+
"alias": "conflicts";
|
|
346
|
+
"required": false;
|
|
347
|
+
"isSignal": true;
|
|
348
|
+
};
|
|
349
|
+
"referenceLabels": {
|
|
350
|
+
"alias": "referenceLabels";
|
|
351
|
+
"required": false;
|
|
352
|
+
"isSignal": true;
|
|
353
|
+
};
|
|
354
|
+
"audit": {
|
|
355
|
+
"alias": "audit";
|
|
356
|
+
"required": false;
|
|
357
|
+
"isSignal": true;
|
|
358
|
+
};
|
|
359
|
+
"theyChanged": {
|
|
360
|
+
"alias": "theyChanged";
|
|
361
|
+
"required": false;
|
|
362
|
+
"isSignal": true;
|
|
363
|
+
};
|
|
364
|
+
}, {
|
|
365
|
+
"resolved": "resolved";
|
|
366
|
+
"cancelled": "cancelled";
|
|
367
|
+
}, never, never, true, never>;
|
|
368
|
+
}
|