@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,411 +1,796 @@
|
|
|
1
|
-
import * as i0 from
|
|
2
|
-
import {
|
|
3
|
-
import { takeUntilDestroyed } from
|
|
4
|
-
import * as i1 from
|
|
5
|
-
import { CommonModule,
|
|
6
|
-
import * as i2 from
|
|
7
|
-
import { ActivatedRoute, Router, RouterModule } from
|
|
8
|
-
import { Color } from
|
|
9
|
-
import { BsAlertComponent } from
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (confirm(this.lang.t('confirmDelete'))) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkPoDetailComponent, isStandalone: true, selector: "spark-po-detail", inputs: { showCustomActions: { classPropertyName: "showCustomActions", publicName: "showCustomActions", isSignal: true, isRequired: false, transformFunction: null }, extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, extraContentTemplate: { classPropertyName: "extraContentTemplate", publicName: "extraContentTemplate", isSignal: true, isRequired: false, transformFunction: null }, queryIconTemplate: { classPropertyName: "queryIconTemplate", publicName: "queryIconTemplate", isSignal: true, isRequired: false, transformFunction: null }, queryCaptionTemplate: { classPropertyName: "queryCaptionTemplate", publicName: "queryCaptionTemplate", isSignal: true, isRequired: false, transformFunction: null }, queryActionsTemplate: { classPropertyName: "queryActionsTemplate", publicName: "queryActionsTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { edited: "edited", deleted: "deleted", customActionExecuted: "customActionExecuted" }, ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n </bs-priority-nav>\n </div>\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose \u2014 an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value \u2014 `2026-12-31T23:59:00-08:00`\n in a definition list \u2014 while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it \u2014 server-side logic, exports, \"which country's morning was\n this?\" \u2014 it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @if (et.queries?.length) {\n @for (queryAlias of et.queries; track queryAlias) {\n <spark-query-card [queryId]=\"queryAlias\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--bs-tertiary-bg);border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "announce", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "component", type: BsPriorityNavComponent, selector: "bs-priority-nav", inputs: ["moreLabel", "moreLabelTemplate", "collapseAt", "overflowFrom", "hideEmptyMore", "ariaLabel"], outputs: ["overflowChange"] }, { kind: "directive", type: BsPriorityNavItemDirective, selector: "[bsPriorityNavItem]", inputs: ["bsPriorityNavItem", "bsPriorityNavItemHideBelow"] }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover", "border", "ariaRowCount"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "selectFirstTab", "tabsPosition"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "component", type: SparkQueryCardComponent, selector: "spark-query-card", inputs: ["queryId", "parentId", "parentType", "reloadToken", "data", "search", "iconTemplate", "captionTemplate", "actionsTemplate", "rowRoute"], outputs: ["error", "rowClicked", "customActionExecuted"] }, { kind: "component", type: SparkGridCellComponent, selector: "spark-grid-cell", inputs: ["column", "display", "rendererValue", "item", "chips", "link"] }, { kind: "component", type: SparkAttributeDescriptionComponent, selector: "spark-attribute-description", inputs: ["description", "position"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }, { kind: "pipe", type: RawAttributeValuePipe, name: "rawAttributeValue" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: ArrayValuePipe, name: "arrayValue" }, { kind: "pipe", type: ReferenceLinkRoutePipe, name: "referenceLinkRoute" }, { kind: "pipe", type: ReferenceChipsPipe, name: "referenceChips" }, { kind: "pipe", type: ParsedDatePipe, name: "parsedDate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
args: [{ selector: 'spark-po-detail', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, RouterModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsTableComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryCardComponent, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe, ReferenceChipsPipe, ParsedDatePipe, SparkAttributeDescriptionComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n </bs-priority-nav>\n </div>\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose \u2014 an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value \u2014 `2026-12-31T23:59:00-08:00`\n in a definition list \u2014 while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it \u2014 server-side logic, exports, \"which country's morning was\n this?\" \u2014 it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @if (et.queries?.length) {\n @for (queryAlias of et.queries; track queryAlias) {\n <spark-query-card [queryId]=\"queryAlias\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--bs-tertiary-bg);border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"] }]
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, untracked } from "@angular/core";
|
|
3
|
+
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
4
|
+
import * as i1 from "@angular/common";
|
|
5
|
+
import { CommonModule, NgComponentOutlet, NgTemplateOutlet } from "@angular/common";
|
|
6
|
+
import * as i2 from "@angular/router";
|
|
7
|
+
import { ActivatedRoute, Router, RouterModule } from "@angular/router";
|
|
8
|
+
import { Color } from "@mintplayer/ng-bootstrap";
|
|
9
|
+
import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
|
|
10
|
+
import { BsBadgeComponent } from "@mintplayer/ng-bootstrap/badge";
|
|
11
|
+
import { BsCardComponent, BsCardHeaderComponent } from "@mintplayer/ng-bootstrap/card";
|
|
12
|
+
import { BsContainerComponent } from "@mintplayer/ng-bootstrap/container";
|
|
13
|
+
import { BsGridColumnDirective, BsGridComponent, BsGridRowDirective } from "@mintplayer/ng-bootstrap/grid";
|
|
14
|
+
import { BsPriorityNavComponent, BsPriorityNavItemDirective } from "@mintplayer/ng-bootstrap/priority-nav";
|
|
15
|
+
import { BsTableComponent } from "@mintplayer/ng-bootstrap/table";
|
|
16
|
+
import { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from "@mintplayer/ng-bootstrap/tab-control";
|
|
17
|
+
import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
|
|
18
|
+
import { SparkLanguageService, SparkReturnNavigationService, SparkService } from "@mintplayer/ng-spark/services";
|
|
19
|
+
import { SparkAttributeRefreshService, SparkQueryRefreshService } from "@mintplayer/ng-spark/client-operations";
|
|
20
|
+
import { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailRowRendererPipe, AttributeValuePipe, ParsedDatePipe, RawAttributeValuePipe, ReferenceChipsPipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, TranslateKeyPipe } from "@mintplayer/ng-spark/pipes";
|
|
21
|
+
import { SparkIconComponent } from "@mintplayer/ng-spark/icon";
|
|
22
|
+
import { SparkAttributeDescriptionComponent } from "@mintplayer/ng-spark/attribute-description";
|
|
23
|
+
import { SparkGridCellComponent, SparkQueryCardComponent } from "@mintplayer/ng-spark/grid";
|
|
24
|
+
import { SPARK_ATTRIBUTE_RENDERERS, rendererValue, rowRendererInputs, withDeclaredInputs } from "@mintplayer/ng-spark/renderers";
|
|
25
|
+
import { ShowedOn, filterDetailActions, hasShowedOnFlag, subQueriesOf } from "@mintplayer/ng-spark/models";
|
|
26
|
+
import { SPARK_DETAIL_ACTIONS, SPARK_DETAIL_PANELS, orderSparkExtensions, parseSparkDeletedParam } from "@mintplayer/ng-spark/panels";
|
|
27
|
+
import { combineLatest } from "rxjs";
|
|
28
|
+
var SparkPoDetailComponent = class SparkPoDetailComponent {
|
|
29
|
+
route = inject(ActivatedRoute);
|
|
30
|
+
router = inject(Router);
|
|
31
|
+
sparkService = inject(SparkService);
|
|
32
|
+
returnNavigation = inject(SparkReturnNavigationService);
|
|
33
|
+
queryRefresh = inject(SparkQueryRefreshService);
|
|
34
|
+
attributeRefresh = inject(SparkAttributeRefreshService);
|
|
35
|
+
lang = inject(SparkLanguageService);
|
|
36
|
+
rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
|
|
37
|
+
detailPanels = orderSparkExtensions(inject(SPARK_DETAIL_PANELS, { optional: true }), (p) => p.order);
|
|
38
|
+
detailActions = orderSparkExtensions(inject(SPARK_DETAIL_ACTIONS, { optional: true }), (a) => a.priority ?? 60);
|
|
39
|
+
showCustomActions = input(true, ...ngDevMode ? [{ debugName: "showCustomActions" }] : /* istanbul ignore next */ []);
|
|
40
|
+
extraActionsTemplate = input(null, ...ngDevMode ? [{ debugName: "extraActionsTemplate" }] : /* istanbul ignore next */ []);
|
|
41
|
+
extraContentTemplate = input(null, ...ngDevMode ? [{ debugName: "extraContentTemplate" }] : /* istanbul ignore next */ []);
|
|
42
|
+
queryIconTemplate = input(null, ...ngDevMode ? [{ debugName: "queryIconTemplate" }] : /* istanbul ignore next */ []);
|
|
43
|
+
queryCaptionTemplate = input(null, ...ngDevMode ? [{ debugName: "queryCaptionTemplate" }] : /* istanbul ignore next */ []);
|
|
44
|
+
queryActionsTemplate = input(null, ...ngDevMode ? [{ debugName: "queryActionsTemplate" }] : /* istanbul ignore next */ []);
|
|
45
|
+
edited = output();
|
|
46
|
+
deleted = output();
|
|
47
|
+
customActionExecuted = output();
|
|
48
|
+
colors = Color;
|
|
49
|
+
errorMessage = signal(null, ...ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []);
|
|
50
|
+
entityType = signal(null, ...ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []);
|
|
51
|
+
subQueries = computed(() => subQueriesOf(this.entityType()), ...ngDevMode ? [{ debugName: "subQueries" }] : /* istanbul ignore next */ []);
|
|
52
|
+
allEntityTypes = signal([], ...ngDevMode ? [{ debugName: "allEntityTypes" }] : /* istanbul ignore next */ []);
|
|
53
|
+
item = signal(null, ...ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []);
|
|
54
|
+
lookupReferenceOptions = signal({}, ...ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : /* istanbul ignore next */ []);
|
|
55
|
+
asDetailTypes = signal({}, ...ngDevMode ? [{ debugName: "asDetailTypes" }] : /* istanbul ignore next */ []);
|
|
56
|
+
asDetailReferenceOptions = signal({}, ...ngDevMode ? [{ debugName: "asDetailReferenceOptions" }] : /* istanbul ignore next */ []);
|
|
57
|
+
type = "";
|
|
58
|
+
id = "";
|
|
59
|
+
canEdit = signal(false, ...ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []);
|
|
60
|
+
canDelete = signal(false, ...ngDevMode ? [{ debugName: "canDelete" }] : /* istanbul ignore next */ []);
|
|
61
|
+
customActions = signal([], ...ngDevMode ? [{ debugName: "customActions" }] : /* istanbul ignore next */ []);
|
|
62
|
+
permissions = signal(null, ...ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []);
|
|
63
|
+
deletedMode = signal(null, ...ngDevMode ? [{ debugName: "deletedMode" }] : /* istanbul ignore next */ []);
|
|
64
|
+
isDeletedView = computed(() => this.deletedMode() === "only", ...ngDevMode ? [{ debugName: "isDeletedView" }] : /* istanbul ignore next */ []);
|
|
65
|
+
detailContext = computed(() => {
|
|
66
|
+
const item = this.item();
|
|
67
|
+
const entityType = this.entityType();
|
|
68
|
+
if (!item || !entityType) return null;
|
|
69
|
+
return {
|
|
70
|
+
type: this.type,
|
|
71
|
+
id: this.id,
|
|
72
|
+
item,
|
|
73
|
+
entityType,
|
|
74
|
+
permissions: this.permissions(),
|
|
75
|
+
deleted: this.deletedMode(),
|
|
76
|
+
reload: () => this.load()
|
|
77
|
+
};
|
|
78
|
+
}, ...ngDevMode ? [{ debugName: "detailContext" }] : /* istanbul ignore next */ []);
|
|
79
|
+
visibleCustomActions = computed(() => {
|
|
80
|
+
const withheld = this.item()?.disabledActions;
|
|
81
|
+
if (!withheld?.length) return this.customActions();
|
|
82
|
+
const lowered = new Set(withheld.map((name) => name.toLowerCase()));
|
|
83
|
+
return this.customActions().filter((action) => !lowered.has(action.name.toLowerCase()));
|
|
84
|
+
}, ...ngDevMode ? [{ debugName: "visibleCustomActions" }] : /* istanbul ignore next */ []);
|
|
85
|
+
constructor() {
|
|
86
|
+
combineLatest([this.route.paramMap, this.route.queryParamMap]).pipe(takeUntilDestroyed()).subscribe(([params, query]) => this.onParamsChange(params, query.get("deleted")));
|
|
87
|
+
effect(() => {
|
|
88
|
+
const objectTypeId = this.entityType()?.id;
|
|
89
|
+
if (!this.attributeRefresh.tokenFor(objectTypeId, this.id)) return;
|
|
90
|
+
const patches = this.attributeRefresh.patchesFor(objectTypeId, this.id);
|
|
91
|
+
untracked(() => this.applyAttributePatches(patches));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
applyAttributePatches(patches) {
|
|
95
|
+
const current = this.item();
|
|
96
|
+
if (!current) return;
|
|
97
|
+
let changed = false;
|
|
98
|
+
const attributes = current.attributes.map((attribute) => {
|
|
99
|
+
if (!Object.prototype.hasOwnProperty.call(patches, attribute.name)) return attribute;
|
|
100
|
+
const patch = patches[attribute.name];
|
|
101
|
+
const next = { ...attribute };
|
|
102
|
+
let patched = false;
|
|
103
|
+
if (patch.value !== void 0 && next.value !== patch.value) {
|
|
104
|
+
next.value = patch.value;
|
|
105
|
+
patched = true;
|
|
106
|
+
}
|
|
107
|
+
if (attribute.dataType === "AsDetail") {
|
|
108
|
+
if (patch.object !== void 0 && next.object !== patch.object) {
|
|
109
|
+
next.object = patch.object;
|
|
110
|
+
patched = true;
|
|
111
|
+
}
|
|
112
|
+
if (patch.objects !== void 0 && next.objects !== patch.objects) {
|
|
113
|
+
next.objects = patch.objects;
|
|
114
|
+
patched = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!patched) return attribute;
|
|
118
|
+
changed = true;
|
|
119
|
+
return next;
|
|
120
|
+
});
|
|
121
|
+
if (changed) this.item.set({
|
|
122
|
+
...current,
|
|
123
|
+
attributes
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async onParamsChange(params, deleted = null) {
|
|
127
|
+
this.type = params.get("type") || "";
|
|
128
|
+
this.id = params.get("id") || "";
|
|
129
|
+
const mode = parseSparkDeletedParam(deleted);
|
|
130
|
+
this.deletedMode.set(mode === "exclude" ? null : mode);
|
|
131
|
+
await this.load();
|
|
132
|
+
}
|
|
133
|
+
loadItem() {
|
|
134
|
+
const deleted = this.deletedMode();
|
|
135
|
+
return deleted ? this.sparkService.get(this.type, this.id, { deleted }) : this.sparkService.get(this.type, this.id);
|
|
136
|
+
}
|
|
137
|
+
async load() {
|
|
138
|
+
this.errorMessage.set(null);
|
|
139
|
+
try {
|
|
140
|
+
const [entityTypes, item] = await Promise.all([this.sparkService.getEntityTypes(), this.loadItem()]);
|
|
141
|
+
this.allEntityTypes.set(entityTypes);
|
|
142
|
+
this.entityType.set(entityTypes.find((t) => t.id === this.type || t.alias === this.type) || null);
|
|
143
|
+
this.item.set(item);
|
|
144
|
+
this.loadLookupReferenceOptions();
|
|
145
|
+
this.loadAsDetailTypes();
|
|
146
|
+
const et = this.entityType();
|
|
147
|
+
if (et) {
|
|
148
|
+
const [permissions, actions] = await Promise.all([this.sparkService.getPermissions(et.id), this.sparkService.getCustomActions(et.id)]);
|
|
149
|
+
const can = item.can;
|
|
150
|
+
const withheld = new Set((item.disabledActions ?? []).map((name) => name.toLowerCase()));
|
|
151
|
+
const deletedView = this.isDeletedView();
|
|
152
|
+
this.permissions.set(permissions);
|
|
153
|
+
this.canEdit.set(!deletedView && (can ? can.edit : permissions.canEdit) && !withheld.has("edit") && !withheld.has("save"));
|
|
154
|
+
this.canDelete.set(!deletedView && (can ? can.delete : permissions.canDelete) && !withheld.has("delete"));
|
|
155
|
+
this.customActions.set(deletedView ? [] : filterDetailActions(actions));
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
const error = e;
|
|
159
|
+
this.errorMessage.set(error.error?.error || error.message || "An unexpected error occurred");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
visibleAttributes = computed(() => {
|
|
163
|
+
return this.entityType()?.attributes.filter((a) => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject)).sort((a, b) => a.order - b.order) || [];
|
|
164
|
+
}, ...ngDevMode ? [{ debugName: "visibleAttributes" }] : /* istanbul ignore next */ []);
|
|
165
|
+
static DEFAULT_TAB = {
|
|
166
|
+
id: "__default__",
|
|
167
|
+
name: "Algemeen",
|
|
168
|
+
label: {
|
|
169
|
+
nl: "Algemeen",
|
|
170
|
+
en: "General"
|
|
171
|
+
},
|
|
172
|
+
order: 0
|
|
173
|
+
};
|
|
174
|
+
ungroupedAttributes = computed(() => {
|
|
175
|
+
const attrs = this.visibleAttributes();
|
|
176
|
+
const groupIds = new Set((this.entityType()?.groups || []).map((g) => g.id));
|
|
177
|
+
return attrs.filter((a) => !a.group || !groupIds.has(a.group));
|
|
178
|
+
}, ...ngDevMode ? [{ debugName: "ungroupedAttributes" }] : /* istanbul ignore next */ []);
|
|
179
|
+
resolvedTabs = computed(() => {
|
|
180
|
+
const et = this.entityType();
|
|
181
|
+
const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];
|
|
182
|
+
const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;
|
|
183
|
+
const hasUntabbedGroups = (et?.groups || []).some((g) => !g.tab);
|
|
184
|
+
if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) return [SparkPoDetailComponent.DEFAULT_TAB, ...definedTabs];
|
|
185
|
+
return definedTabs;
|
|
186
|
+
}, ...ngDevMode ? [{ debugName: "resolvedTabs" }] : /* istanbul ignore next */ []);
|
|
187
|
+
groupsForTab(tab) {
|
|
188
|
+
const groups = this.entityType()?.groups || [];
|
|
189
|
+
if (tab.id === "__default__") return groups.filter((g) => !g.tab).sort((a, b) => a.order - b.order);
|
|
190
|
+
return groups.filter((g) => g.tab === tab.id).sort((a, b) => a.order - b.order);
|
|
191
|
+
}
|
|
192
|
+
attrsForGroup(group) {
|
|
193
|
+
return this.visibleAttributes().filter((a) => a.group === group.id);
|
|
194
|
+
}
|
|
195
|
+
getDetailRendererComponent(attr) {
|
|
196
|
+
if (!attr.renderer) return null;
|
|
197
|
+
return this.rendererRegistry.find((r) => r.name === attr.renderer)?.detailComponent ?? null;
|
|
198
|
+
}
|
|
199
|
+
getRowRendererInputs(renderer, row, item) {
|
|
200
|
+
return rowRendererInputs(renderer, row, item?.id);
|
|
201
|
+
}
|
|
202
|
+
getDetailRendererInputs(component, attr, item) {
|
|
203
|
+
const itemAttr = item.attributes.find((a) => a.name === attr.name);
|
|
204
|
+
const formData = {};
|
|
205
|
+
for (const a of item.attributes) formData[a.name] = rendererValue(a);
|
|
206
|
+
return withDeclaredInputs(component, {
|
|
207
|
+
value: rendererValue(itemAttr),
|
|
208
|
+
attribute: attr,
|
|
209
|
+
options: attr.rendererOptions,
|
|
210
|
+
formData,
|
|
211
|
+
item
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
getAsDetailCellRendererComponent(col) {
|
|
215
|
+
if (!col.renderer) return null;
|
|
216
|
+
return this.rendererRegistry.find((r) => r.name === col.renderer)?.columnComponent ?? null;
|
|
217
|
+
}
|
|
218
|
+
getAsDetailCellRendererInputs(component, row, col) {
|
|
219
|
+
return withDeclaredInputs(component, {
|
|
220
|
+
value: row[col.name],
|
|
221
|
+
attribute: col,
|
|
222
|
+
options: col.rendererOptions,
|
|
223
|
+
item: row
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
async loadLookupReferenceOptions() {
|
|
227
|
+
const lookupAttrs = this.visibleAttributes().filter((a) => a.lookupReferenceType);
|
|
228
|
+
if (lookupAttrs.length === 0) return;
|
|
229
|
+
const lookupNames = [...new Set(lookupAttrs.map((a) => a.lookupReferenceType))];
|
|
230
|
+
const entries = await Promise.all(lookupNames.map(async (name) => {
|
|
231
|
+
return [name, await this.sparkService.getLookupReference(name).catch(() => null)];
|
|
232
|
+
}));
|
|
233
|
+
this.lookupReferenceOptions.set(entries.reduce((acc, [k, v]) => v ? {
|
|
234
|
+
...acc,
|
|
235
|
+
[k]: v
|
|
236
|
+
} : acc, {}));
|
|
237
|
+
}
|
|
238
|
+
async loadAsDetailTypes() {
|
|
239
|
+
const asDetailAttrs = this.visibleAttributes().filter((a) => a.dataType === "AsDetail" && a.isArray && a.asDetailType);
|
|
240
|
+
if (asDetailAttrs.length === 0) return;
|
|
241
|
+
const types = this.allEntityTypes();
|
|
242
|
+
const newAsDetailTypes = {};
|
|
243
|
+
const embedded = this.entityType()?.detailTypes ?? [];
|
|
244
|
+
for (const attr of asDetailAttrs) {
|
|
245
|
+
const asDetailType = types.find((t) => t.clrType === attr.asDetailType) ?? embedded.find((t) => t.clrType === attr.asDetailType);
|
|
246
|
+
if (asDetailType) {
|
|
247
|
+
newAsDetailTypes[attr.name] = asDetailType;
|
|
248
|
+
const refCols = asDetailType.attributes.filter((a) => a.dataType === "Reference" && a.query);
|
|
249
|
+
if (refCols.length > 0) {
|
|
250
|
+
const refEntries = await Promise.all(refCols.map(async (col) => {
|
|
251
|
+
const result = await this.sparkService.executeQueryByName(col.query, {
|
|
252
|
+
parentId: this.id,
|
|
253
|
+
parentType: this.type
|
|
254
|
+
});
|
|
255
|
+
return [col.name, result.items];
|
|
256
|
+
}));
|
|
257
|
+
this.asDetailReferenceOptions.update((prev) => ({
|
|
258
|
+
...prev,
|
|
259
|
+
[attr.name]: refEntries.reduce((acc, [k, v]) => ({
|
|
260
|
+
...acc,
|
|
261
|
+
[k]: v
|
|
262
|
+
}), {})
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.asDetailTypes.set(newAsDetailTypes);
|
|
268
|
+
}
|
|
269
|
+
customActionClass(action) {
|
|
270
|
+
const variant = action.variant?.toLowerCase();
|
|
271
|
+
switch (variant) {
|
|
272
|
+
case "danger":
|
|
273
|
+
case "warning":
|
|
274
|
+
case "primary":
|
|
275
|
+
case "secondary":
|
|
276
|
+
case "success": return `btn btn-${variant}`;
|
|
277
|
+
default: return "btn btn-outline-primary";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
runningAction = signal(null, ...ngDevMode ? [{ debugName: "runningAction" }] : /* istanbul ignore next */ []);
|
|
281
|
+
async onCustomAction(action) {
|
|
282
|
+
if (this.runningAction()) return;
|
|
283
|
+
if (action.confirmationMessageKey) {
|
|
284
|
+
const message = this.lang.t(action.confirmationMessageKey) || "Are you sure?";
|
|
285
|
+
if (!confirm(message)) return;
|
|
286
|
+
}
|
|
287
|
+
this.runningAction.set(action.name);
|
|
288
|
+
try {
|
|
289
|
+
await this.sparkService.executeCustomAction(this.type, action.name, this.item() || void 0);
|
|
290
|
+
this.customActionExecuted.emit({
|
|
291
|
+
action,
|
|
292
|
+
item: this.item()
|
|
293
|
+
});
|
|
294
|
+
if (action.refreshOnCompleted) {
|
|
295
|
+
const item = await this.loadItem();
|
|
296
|
+
this.item.set(item);
|
|
297
|
+
for (const subQuery of this.subQueries()) this.queryRefresh.request(subQuery.query);
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
const err = e;
|
|
301
|
+
this.errorMessage.set(err.error?.error || err.message || "Action failed");
|
|
302
|
+
} finally {
|
|
303
|
+
this.runningAction.set(null);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
onEdit() {
|
|
307
|
+
this.edited.emit();
|
|
308
|
+
this.router.navigate([
|
|
309
|
+
"/po",
|
|
310
|
+
this.type,
|
|
311
|
+
this.id,
|
|
312
|
+
"edit"
|
|
313
|
+
]);
|
|
314
|
+
}
|
|
315
|
+
async onDelete() {
|
|
316
|
+
if (confirm(this.lang.t("common.confirmDelete"))) {
|
|
317
|
+
await this.sparkService.delete(this.type, this.id);
|
|
318
|
+
this.deleted.emit();
|
|
319
|
+
await this.returnNavigation.returnToList(this.entityType()?.name);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
onBack() {
|
|
323
|
+
window.history.back();
|
|
324
|
+
}
|
|
325
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
326
|
+
minVersion: "12.0.0",
|
|
327
|
+
version: "22.2.0",
|
|
328
|
+
ngImport: i0,
|
|
329
|
+
type: SparkPoDetailComponent,
|
|
330
|
+
deps: [],
|
|
331
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
332
|
+
});
|
|
333
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
334
|
+
minVersion: "17.0.0",
|
|
335
|
+
version: "22.2.0",
|
|
336
|
+
type: SparkPoDetailComponent,
|
|
337
|
+
isStandalone: true,
|
|
338
|
+
selector: "spark-po-detail",
|
|
339
|
+
inputs: {
|
|
340
|
+
showCustomActions: {
|
|
341
|
+
classPropertyName: "showCustomActions",
|
|
342
|
+
publicName: "showCustomActions",
|
|
343
|
+
isSignal: true,
|
|
344
|
+
isRequired: false,
|
|
345
|
+
transformFunction: null
|
|
346
|
+
},
|
|
347
|
+
extraActionsTemplate: {
|
|
348
|
+
classPropertyName: "extraActionsTemplate",
|
|
349
|
+
publicName: "extraActionsTemplate",
|
|
350
|
+
isSignal: true,
|
|
351
|
+
isRequired: false,
|
|
352
|
+
transformFunction: null
|
|
353
|
+
},
|
|
354
|
+
extraContentTemplate: {
|
|
355
|
+
classPropertyName: "extraContentTemplate",
|
|
356
|
+
publicName: "extraContentTemplate",
|
|
357
|
+
isSignal: true,
|
|
358
|
+
isRequired: false,
|
|
359
|
+
transformFunction: null
|
|
360
|
+
},
|
|
361
|
+
queryIconTemplate: {
|
|
362
|
+
classPropertyName: "queryIconTemplate",
|
|
363
|
+
publicName: "queryIconTemplate",
|
|
364
|
+
isSignal: true,
|
|
365
|
+
isRequired: false,
|
|
366
|
+
transformFunction: null
|
|
367
|
+
},
|
|
368
|
+
queryCaptionTemplate: {
|
|
369
|
+
classPropertyName: "queryCaptionTemplate",
|
|
370
|
+
publicName: "queryCaptionTemplate",
|
|
371
|
+
isSignal: true,
|
|
372
|
+
isRequired: false,
|
|
373
|
+
transformFunction: null
|
|
374
|
+
},
|
|
375
|
+
queryActionsTemplate: {
|
|
376
|
+
classPropertyName: "queryActionsTemplate",
|
|
377
|
+
publicName: "queryActionsTemplate",
|
|
378
|
+
isSignal: true,
|
|
379
|
+
isRequired: false,
|
|
380
|
+
transformFunction: null
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
outputs: {
|
|
384
|
+
edited: "edited",
|
|
385
|
+
deleted: "deleted",
|
|
386
|
+
customActionExecuted: "customActionExecuted"
|
|
387
|
+
},
|
|
388
|
+
ngImport: i0,
|
|
389
|
+
template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
|
|
390
|
+
styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"],
|
|
391
|
+
dependencies: [
|
|
392
|
+
{
|
|
393
|
+
kind: "ngmodule",
|
|
394
|
+
type: CommonModule
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
kind: "directive",
|
|
398
|
+
type: i1.NgComponentOutlet,
|
|
399
|
+
selector: "[ngComponentOutlet]",
|
|
400
|
+
inputs: [
|
|
401
|
+
"ngComponentOutlet",
|
|
402
|
+
"ngComponentOutletInputs",
|
|
403
|
+
"ngComponentOutletInjector",
|
|
404
|
+
"ngComponentOutletEnvironmentInjector",
|
|
405
|
+
"ngComponentOutletContent",
|
|
406
|
+
"ngComponentOutletNgModule"
|
|
407
|
+
],
|
|
408
|
+
exportAs: ["ngComponentOutlet"]
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
kind: "directive",
|
|
412
|
+
type: i1.NgTemplateOutlet,
|
|
413
|
+
selector: "[ngTemplateOutlet]",
|
|
414
|
+
inputs: [
|
|
415
|
+
"ngTemplateOutletContext",
|
|
416
|
+
"ngTemplateOutlet",
|
|
417
|
+
"ngTemplateOutletInjector"
|
|
418
|
+
]
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
kind: "ngmodule",
|
|
422
|
+
type: RouterModule
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
kind: "directive",
|
|
426
|
+
type: i2.RouterLink,
|
|
427
|
+
selector: "[routerLink]",
|
|
428
|
+
inputs: [
|
|
429
|
+
"target",
|
|
430
|
+
"queryParams",
|
|
431
|
+
"fragment",
|
|
432
|
+
"queryParamsHandling",
|
|
433
|
+
"state",
|
|
434
|
+
"info",
|
|
435
|
+
"relativeTo",
|
|
436
|
+
"preserveFragment",
|
|
437
|
+
"skipLocationChange",
|
|
438
|
+
"replaceUrl",
|
|
439
|
+
"browserUrl",
|
|
440
|
+
"routerLink"
|
|
441
|
+
]
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
kind: "component",
|
|
445
|
+
type: BsAlertComponent,
|
|
446
|
+
selector: "bs-alert",
|
|
447
|
+
inputs: [
|
|
448
|
+
"type",
|
|
449
|
+
"announce",
|
|
450
|
+
"isVisible"
|
|
451
|
+
],
|
|
452
|
+
outputs: ["isVisibleChange", "afterOpenedOrClosed"]
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
kind: "component",
|
|
456
|
+
type: BsBadgeComponent,
|
|
457
|
+
selector: "bs-badge",
|
|
458
|
+
inputs: [
|
|
459
|
+
"type",
|
|
460
|
+
"unit",
|
|
461
|
+
"decorative"
|
|
462
|
+
]
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
kind: "component",
|
|
466
|
+
type: BsCardComponent,
|
|
467
|
+
selector: "bs-card",
|
|
468
|
+
inputs: ["color", "outline"]
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
kind: "component",
|
|
472
|
+
type: BsCardHeaderComponent,
|
|
473
|
+
selector: "bs-card-header",
|
|
474
|
+
inputs: ["color", "navStyle"]
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
kind: "component",
|
|
478
|
+
type: BsContainerComponent,
|
|
479
|
+
selector: "bs-container"
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
kind: "component",
|
|
483
|
+
type: BsGridComponent,
|
|
484
|
+
selector: "bs-grid",
|
|
485
|
+
inputs: ["stopFullWidthAt"]
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
kind: "directive",
|
|
489
|
+
type: BsGridRowDirective,
|
|
490
|
+
selector: "[bsRow]"
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
kind: "directive",
|
|
494
|
+
type: BsGridColumnDirective,
|
|
495
|
+
selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]",
|
|
496
|
+
inputs: [
|
|
497
|
+
"xxs",
|
|
498
|
+
"xs",
|
|
499
|
+
"sm",
|
|
500
|
+
"md",
|
|
501
|
+
"lg",
|
|
502
|
+
"xl",
|
|
503
|
+
"xxl"
|
|
504
|
+
]
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
kind: "component",
|
|
508
|
+
type: BsPriorityNavComponent,
|
|
509
|
+
selector: "bs-priority-nav",
|
|
510
|
+
inputs: [
|
|
511
|
+
"moreLabel",
|
|
512
|
+
"moreLabelTemplate",
|
|
513
|
+
"collapseAt",
|
|
514
|
+
"overflowFrom",
|
|
515
|
+
"hideEmptyMore",
|
|
516
|
+
"ariaLabel"
|
|
517
|
+
],
|
|
518
|
+
outputs: ["overflowChange"]
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
kind: "directive",
|
|
522
|
+
type: BsPriorityNavItemDirective,
|
|
523
|
+
selector: "[bsPriorityNavItem]",
|
|
524
|
+
inputs: ["bsPriorityNavItem", "bsPriorityNavItemHideBelow"]
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
kind: "component",
|
|
528
|
+
type: BsTableComponent,
|
|
529
|
+
selector: "bs-table",
|
|
530
|
+
inputs: [
|
|
531
|
+
"isResponsive",
|
|
532
|
+
"striped",
|
|
533
|
+
"hover",
|
|
534
|
+
"border",
|
|
535
|
+
"ariaRowCount"
|
|
536
|
+
]
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
kind: "component",
|
|
540
|
+
type: BsTabControlComponent,
|
|
541
|
+
selector: "bs-tab-control",
|
|
542
|
+
inputs: [
|
|
543
|
+
"border",
|
|
544
|
+
"selectFirstTab",
|
|
545
|
+
"tabsPosition"
|
|
546
|
+
]
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
kind: "component",
|
|
550
|
+
type: BsTabPageComponent,
|
|
551
|
+
selector: "bs-tab-page",
|
|
552
|
+
inputs: ["disabled"]
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
kind: "directive",
|
|
556
|
+
type: BsTabPageHeaderDirective,
|
|
557
|
+
selector: "[bsTabPageHeader]"
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
kind: "component",
|
|
561
|
+
type: BsSpinnerComponent,
|
|
562
|
+
selector: "bs-spinner",
|
|
563
|
+
inputs: ["type", "color"]
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
kind: "component",
|
|
567
|
+
type: SparkIconComponent,
|
|
568
|
+
selector: "spark-icon",
|
|
569
|
+
inputs: ["name"]
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
kind: "component",
|
|
573
|
+
type: SparkQueryCardComponent,
|
|
574
|
+
selector: "spark-query-card",
|
|
575
|
+
inputs: [
|
|
576
|
+
"queryId",
|
|
577
|
+
"parentId",
|
|
578
|
+
"parentType",
|
|
579
|
+
"reloadToken",
|
|
580
|
+
"data",
|
|
581
|
+
"search",
|
|
582
|
+
"searchable",
|
|
583
|
+
"selectionMode",
|
|
584
|
+
"deleted",
|
|
585
|
+
"parentDeleted",
|
|
586
|
+
"iconTemplate",
|
|
587
|
+
"captionTemplate",
|
|
588
|
+
"actionsTemplate",
|
|
589
|
+
"rowRoute"
|
|
590
|
+
],
|
|
591
|
+
outputs: [
|
|
592
|
+
"error",
|
|
593
|
+
"rowClicked",
|
|
594
|
+
"customActionExecuted"
|
|
595
|
+
]
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
kind: "component",
|
|
599
|
+
type: SparkGridCellComponent,
|
|
600
|
+
selector: "spark-grid-cell",
|
|
601
|
+
inputs: [
|
|
602
|
+
"column",
|
|
603
|
+
"display",
|
|
604
|
+
"rendererValue",
|
|
605
|
+
"item",
|
|
606
|
+
"chips",
|
|
607
|
+
"link"
|
|
608
|
+
]
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
kind: "component",
|
|
612
|
+
type: SparkAttributeDescriptionComponent,
|
|
613
|
+
selector: "spark-attribute-description",
|
|
614
|
+
inputs: ["description", "position"]
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
kind: "pipe",
|
|
618
|
+
type: i1.DatePipe,
|
|
619
|
+
name: "date"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
kind: "pipe",
|
|
623
|
+
type: ResolveTranslationPipe,
|
|
624
|
+
name: "resolveTranslation"
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
kind: "pipe",
|
|
628
|
+
type: TranslateKeyPipe,
|
|
629
|
+
name: "t"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
kind: "pipe",
|
|
633
|
+
type: AttributeValuePipe,
|
|
634
|
+
name: "attributeValue"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
kind: "pipe",
|
|
638
|
+
type: RawAttributeValuePipe,
|
|
639
|
+
name: "rawAttributeValue"
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
kind: "pipe",
|
|
643
|
+
type: AsDetailColumnsPipe,
|
|
644
|
+
name: "asDetailColumns"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
kind: "pipe",
|
|
648
|
+
type: AsDetailRowRendererPipe,
|
|
649
|
+
name: "asDetailRowRenderer"
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
kind: "pipe",
|
|
653
|
+
type: AsDetailCellValuePipe,
|
|
654
|
+
name: "asDetailCellValue"
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
kind: "pipe",
|
|
658
|
+
type: ArrayValuePipe,
|
|
659
|
+
name: "arrayValue"
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
kind: "pipe",
|
|
663
|
+
type: ReferenceLinkRoutePipe,
|
|
664
|
+
name: "referenceLinkRoute"
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
kind: "pipe",
|
|
668
|
+
type: ReferenceChipsPipe,
|
|
669
|
+
name: "referenceChips"
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
kind: "pipe",
|
|
673
|
+
type: ParsedDatePipe,
|
|
674
|
+
name: "parsedDate"
|
|
675
|
+
}
|
|
676
|
+
],
|
|
677
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
678
|
+
});
|
|
679
|
+
};
|
|
680
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
681
|
+
minVersion: "12.0.0",
|
|
682
|
+
version: "22.2.0",
|
|
683
|
+
ngImport: i0,
|
|
684
|
+
type: SparkPoDetailComponent,
|
|
685
|
+
decorators: [{
|
|
686
|
+
type: Component,
|
|
687
|
+
args: [{
|
|
688
|
+
selector: "spark-po-detail",
|
|
689
|
+
imports: [
|
|
690
|
+
CommonModule,
|
|
691
|
+
NgTemplateOutlet,
|
|
692
|
+
NgComponentOutlet,
|
|
693
|
+
RouterModule,
|
|
694
|
+
BsAlertComponent,
|
|
695
|
+
BsBadgeComponent,
|
|
696
|
+
BsCardComponent,
|
|
697
|
+
BsCardHeaderComponent,
|
|
698
|
+
BsContainerComponent,
|
|
699
|
+
BsGridComponent,
|
|
700
|
+
BsGridRowDirective,
|
|
701
|
+
BsGridColumnDirective,
|
|
702
|
+
BsPriorityNavComponent,
|
|
703
|
+
BsPriorityNavItemDirective,
|
|
704
|
+
BsTableComponent,
|
|
705
|
+
BsTabControlComponent,
|
|
706
|
+
BsTabPageComponent,
|
|
707
|
+
BsTabPageHeaderDirective,
|
|
708
|
+
BsSpinnerComponent,
|
|
709
|
+
SparkIconComponent,
|
|
710
|
+
SparkQueryCardComponent,
|
|
711
|
+
SparkGridCellComponent,
|
|
712
|
+
ResolveTranslationPipe,
|
|
713
|
+
TranslateKeyPipe,
|
|
714
|
+
AttributeValuePipe,
|
|
715
|
+
RawAttributeValuePipe,
|
|
716
|
+
AsDetailColumnsPipe,
|
|
717
|
+
AsDetailRowRendererPipe,
|
|
718
|
+
AsDetailCellValuePipe,
|
|
719
|
+
ArrayValuePipe,
|
|
720
|
+
ReferenceLinkRoutePipe,
|
|
721
|
+
ReferenceChipsPipe,
|
|
722
|
+
ParsedDatePipe,
|
|
723
|
+
SparkAttributeDescriptionComponent
|
|
724
|
+
],
|
|
725
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
726
|
+
template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
|
|
727
|
+
styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"]
|
|
728
|
+
}]
|
|
729
|
+
}],
|
|
730
|
+
ctorParameters: () => [],
|
|
731
|
+
propDecorators: {
|
|
732
|
+
showCustomActions: [{
|
|
733
|
+
type: i0.Input,
|
|
734
|
+
args: [{
|
|
735
|
+
isSignal: true,
|
|
736
|
+
alias: "showCustomActions",
|
|
737
|
+
required: false
|
|
738
|
+
}]
|
|
739
|
+
}],
|
|
740
|
+
extraActionsTemplate: [{
|
|
741
|
+
type: i0.Input,
|
|
742
|
+
args: [{
|
|
743
|
+
isSignal: true,
|
|
744
|
+
alias: "extraActionsTemplate",
|
|
745
|
+
required: false
|
|
746
|
+
}]
|
|
747
|
+
}],
|
|
748
|
+
extraContentTemplate: [{
|
|
749
|
+
type: i0.Input,
|
|
750
|
+
args: [{
|
|
751
|
+
isSignal: true,
|
|
752
|
+
alias: "extraContentTemplate",
|
|
753
|
+
required: false
|
|
754
|
+
}]
|
|
755
|
+
}],
|
|
756
|
+
queryIconTemplate: [{
|
|
757
|
+
type: i0.Input,
|
|
758
|
+
args: [{
|
|
759
|
+
isSignal: true,
|
|
760
|
+
alias: "queryIconTemplate",
|
|
761
|
+
required: false
|
|
762
|
+
}]
|
|
763
|
+
}],
|
|
764
|
+
queryCaptionTemplate: [{
|
|
765
|
+
type: i0.Input,
|
|
766
|
+
args: [{
|
|
767
|
+
isSignal: true,
|
|
768
|
+
alias: "queryCaptionTemplate",
|
|
769
|
+
required: false
|
|
770
|
+
}]
|
|
771
|
+
}],
|
|
772
|
+
queryActionsTemplate: [{
|
|
773
|
+
type: i0.Input,
|
|
774
|
+
args: [{
|
|
775
|
+
isSignal: true,
|
|
776
|
+
alias: "queryActionsTemplate",
|
|
777
|
+
required: false
|
|
778
|
+
}]
|
|
779
|
+
}],
|
|
780
|
+
edited: [{
|
|
781
|
+
type: i0.Output,
|
|
782
|
+
args: ["edited"]
|
|
783
|
+
}],
|
|
784
|
+
deleted: [{
|
|
785
|
+
type: i0.Output,
|
|
786
|
+
args: ["deleted"]
|
|
787
|
+
}],
|
|
788
|
+
customActionExecuted: [{
|
|
789
|
+
type: i0.Output,
|
|
790
|
+
args: ["customActionExecuted"]
|
|
791
|
+
}]
|
|
792
|
+
}
|
|
793
|
+
});
|
|
410
794
|
export { SparkPoDetailComponent };
|
|
411
|
-
|
|
795
|
+
|
|
796
|
+
//# sourceMappingURL=mintplayer-ng-spark-po-detail.mjs.map
|