@mintplayer/ng-spark 0.3.0 → 22.0.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.
Files changed (34) hide show
  1. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +180 -0
  2. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -0
  3. package/fesm2022/mintplayer-ng-spark-icon.mjs +9 -6
  4. package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
  5. package/fesm2022/mintplayer-ng-spark-models.mjs +84 -1
  6. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  7. package/fesm2022/mintplayer-ng-spark-pipes.mjs +91 -74
  8. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  9. package/fesm2022/mintplayer-ng-spark-po-create.mjs +55 -22
  10. package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
  11. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +97 -92
  12. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  13. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +63 -11
  14. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  15. package/fesm2022/mintplayer-ng-spark-po-form.mjs +71 -36
  16. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  17. package/fesm2022/mintplayer-ng-spark-query-list.mjs +138 -126
  18. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  19. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +172 -18
  20. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
  21. package/fesm2022/mintplayer-ng-spark-services.mjs +99 -50
  22. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  23. package/package.json +12 -7
  24. package/types/mintplayer-ng-spark-client-operations.d.ts +170 -0
  25. package/types/mintplayer-ng-spark-icon.d.ts +1 -1
  26. package/types/mintplayer-ng-spark-models.d.ts +54 -10
  27. package/types/mintplayer-ng-spark-pipes.d.ts +8 -0
  28. package/types/mintplayer-ng-spark-po-create.d.ts +2 -1
  29. package/types/mintplayer-ng-spark-po-detail.d.ts +14 -16
  30. package/types/mintplayer-ng-spark-po-edit.d.ts +4 -2
  31. package/types/mintplayer-ng-spark-po-form.d.ts +10 -9
  32. package/types/mintplayer-ng-spark-query-list.d.ts +26 -16
  33. package/types/mintplayer-ng-spark-retry-action-modal.d.ts +33 -4
  34. package/types/mintplayer-ng-spark-services.d.ts +32 -6
@@ -10,13 +10,12 @@ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
10
10
  import { Color } from '@mintplayer/ng-bootstrap';
11
11
  import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
12
12
  import { DatatableSettings, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective } from '@mintplayer/ng-bootstrap/datatable';
13
- import { VirtualDatatableDataSource, BsVirtualDatatableComponent, BsVirtualRowTemplateDirective } from '@mintplayer/ng-bootstrap/virtual-datatable';
14
13
  import { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';
15
- import { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';
16
14
  import { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';
17
15
  import { BsInputGroupComponent } from '@mintplayer/ng-bootstrap/input-group';
16
+ import { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';
18
17
  import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
19
- import { SparkService, SparkStreamingService } from '@mintplayer/ng-spark/services';
18
+ import { SparkService, SparkStreamingService, SparkLanguageService } from '@mintplayer/ng-spark/services';
20
19
  import { ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe } from '@mintplayer/ng-spark/pipes';
21
20
  import { SparkIconComponent } from '@mintplayer/ng-spark/icon';
22
21
  import { SPARK_ATTRIBUTE_RENDERERS } from '@mintplayer/ng-spark/renderers';
@@ -27,39 +26,64 @@ class SparkQueryListComponent {
27
26
  router = inject(Router);
28
27
  sparkService = inject(SparkService);
29
28
  streamingService = inject(SparkStreamingService);
29
+ lang = inject(SparkLanguageService);
30
30
  rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
31
31
  destroyRef = inject(DestroyRef);
32
- extraActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : []));
32
+ extraActionsTemplate = input(null, /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : /* istanbul ignore next */ []));
34
+ showCustomActions = input(true, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "showCustomActions" }] : /* istanbul ignore next */ []));
33
36
  rowClicked = output();
34
37
  createClicked = output();
38
+ customActionExecuted = output();
35
39
  colors = Color;
36
- errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
37
- query = signal(null, ...(ngDevMode ? [{ debugName: "query" }] : []));
38
- entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
39
- allEntityTypes = signal([], ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : []));
40
- lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
41
- paginationData = signal(undefined, ...(ngDevMode ? [{ debugName: "paginationData" }] : []));
40
+ errorMessage = signal(null, /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
42
+ query = signal(null, /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
44
+ entityType = signal(null, /* @ts-ignore */
45
+ ...(ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []));
46
+ allEntityTypes = signal([], /* @ts-ignore */
47
+ ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : /* istanbul ignore next */ []));
48
+ lookupReferenceOptions = signal({}, /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : /* istanbul ignore next */ []));
50
+ resultCount = signal(null, /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "resultCount" }] : /* istanbul ignore next */ []));
42
52
  searchTerm = '';
43
- canRead = signal(false, ...(ngDevMode ? [{ debugName: "canRead" }] : []));
44
- canCreate = signal(false, ...(ngDevMode ? [{ debugName: "canCreate" }] : []));
45
- isStreaming = signal(false, ...(ngDevMode ? [{ debugName: "isStreaming" }] : []));
53
+ canRead = signal(false, /* @ts-ignore */
54
+ ...(ngDevMode ? [{ debugName: "canRead" }] : /* istanbul ignore next */ []));
55
+ canCreate = signal(false, /* @ts-ignore */
56
+ ...(ngDevMode ? [{ debugName: "canCreate" }] : /* istanbul ignore next */ []));
57
+ customActions = signal([], /* @ts-ignore */
58
+ ...(ngDevMode ? [{ debugName: "customActions" }] : /* istanbul ignore next */ []));
59
+ isStreaming = signal(false, /* @ts-ignore */
60
+ ...(ngDevMode ? [{ debugName: "isStreaming" }] : /* istanbul ignore next */ []));
46
61
  streamingSub = null;
47
- allItems = signal([], ...(ngDevMode ? [{ debugName: "allItems" }] : []));
48
- filteredItems = [];
62
+ allItems = signal([], /* @ts-ignore */
63
+ ...(ngDevMode ? [{ debugName: "allItems" }] : /* istanbul ignore next */ []));
64
+ streamItems = signal([], /* @ts-ignore */
65
+ ...(ngDevMode ? [{ debugName: "streamItems" }] : /* istanbul ignore next */ []));
66
+ fetchFn = signal(null, /* @ts-ignore */
67
+ ...(ngDevMode ? [{ debugName: "fetchFn" }] : /* istanbul ignore next */ []));
49
68
  settings = signal(new DatatableSettings({
50
69
  perPage: { values: [10, 25, 50], selected: 10 },
51
70
  page: { values: [1], selected: 1 },
52
71
  sortColumns: []
53
- }), ...(ngDevMode ? [{ debugName: "settings" }] : []));
54
- virtualDataSource = signal(null, ...(ngDevMode ? [{ debugName: "virtualDataSource" }] : []));
55
- virtualSettings = signal(new DatatableSettings({
56
- sortColumns: []
57
- }), ...(ngDevMode ? [{ debugName: "virtualSettings" }] : []));
72
+ }), /* @ts-ignore */
73
+ ...(ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []));
58
74
  constructor() {
59
75
  this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
60
76
  this.destroyRef.onDestroy(() => this.disconnectStreaming());
61
77
  }
62
78
  async onParamsChange(params) {
79
+ // Reset prior-route state so we render the spinner (not stale rows from
80
+ // the previous query) while the new query/entityType resolve.
81
+ this.entityType.set(null);
82
+ this.fetchFn.set(null);
83
+ this.resultCount.set(null);
84
+ this.allItems.set([]);
85
+ this.streamItems.set([]);
86
+ this.disconnectStreaming();
63
87
  const queryId = params.get('queryId');
64
88
  const typeParam = params.get('type');
65
89
  let resolvedQuery = null;
@@ -101,32 +125,47 @@ class SparkQueryListComponent {
101
125
  property: sc.property,
102
126
  direction: sc.direction === 'desc' ? 'descending' : 'ascending'
103
127
  }));
104
- if (resolvedQuery?.renderMode === 'VirtualScrolling') {
105
- this.virtualSettings.set(new DatatableSettings({ sortColumns: initialSortColumns }));
106
- if (resolvedQuery?.isStreamingQuery) {
107
- // Streaming + VirtualScrolling: create a stable client-side data source, then connect WebSocket
108
- this.virtualDataSource.set(new VirtualDatatableDataSource((skip, take) => Promise.resolve({
109
- data: this.filteredItems.slice(skip, skip + take),
110
- totalRecords: this.filteredItems.length
111
- }), 50));
112
- this.connectStreaming(resolvedQuery.id);
113
- }
114
- else {
115
- this.initVirtualDataSource();
116
- }
128
+ this.settings.set(new DatatableSettings({
129
+ perPage: { values: [10, 25, 50], selected: 10 },
130
+ page: { values: [1], selected: 1 },
131
+ sortColumns: initialSortColumns
132
+ }));
133
+ if (resolvedQuery?.isStreamingQuery) {
134
+ // Streaming: WebSocket feeds allItems; the datatable binds [data]="streamItems()".
135
+ this.connectStreaming(resolvedQuery.id);
117
136
  }
118
- else {
119
- this.settings.set(new DatatableSettings({
120
- perPage: { values: [10, 25, 50], selected: 10 },
121
- page: { values: [1], selected: 1 },
122
- sortColumns: initialSortColumns
123
- }));
124
- await this.loadItems();
137
+ else if (resolvedQuery) {
138
+ // Non-streaming: the datatable drives paging/sorting via [(settings)] and calls
139
+ // fetchFn per page. Virtual scrolling is just the [virtualScroll] template flag
140
+ // the datatable front-loads all pages from fetchFn when virtual.
141
+ this.fetchFn.set(this.makeFetch(resolvedQuery));
125
142
  }
126
143
  this.loadLookupReferenceOptions();
127
- const permissions = await this.sparkService.getPermissions(resolvedEntityType.id);
144
+ const [permissions, actions] = await Promise.all([
145
+ this.sparkService.getPermissions(resolvedEntityType.id),
146
+ this.sparkService.getCustomActions(resolvedEntityType.id),
147
+ ]);
128
148
  this.canRead.set(permissions.canRead);
129
149
  this.canCreate.set(permissions.canCreate);
150
+ this.customActions.set(actions.filter(a => a.showedOn === 'list' || a.showedOn === 'both'));
151
+ }
152
+ }
153
+ async onCustomAction(action) {
154
+ if (action.confirmationMessageKey) {
155
+ const message = this.lang.t(action.confirmationMessageKey) || 'Are you sure?';
156
+ if (!confirm(message))
157
+ return;
158
+ }
159
+ try {
160
+ await this.sparkService.executeCustomAction(this.entityType().id, action.name);
161
+ this.customActionExecuted.emit({ action });
162
+ if (action.refreshOnCompleted) {
163
+ this.refresh();
164
+ }
165
+ }
166
+ catch (e) {
167
+ const err = e;
168
+ this.errorMessage.set(err.error?.error || err.message || 'Action failed');
130
169
  }
131
170
  }
132
171
  async resolveEntityTypeForQuery(query) {
@@ -168,89 +207,76 @@ class SparkQueryListComponent {
168
207
  }
169
208
  return plural;
170
209
  }
171
- initVirtualDataSource() {
172
- const currentQuery = this.query();
173
- if (!currentQuery)
174
- return;
175
- this.virtualDataSource.set(new VirtualDatatableDataSource((skip, take) => this.sparkService.executeQuery(currentQuery.id, {
176
- sortColumns: this.virtualSettings().sortColumns,
177
- skip, take,
210
+ /**
211
+ * Builds the server-side fetch callback the datatable invokes per page/sort.
212
+ * Reads `searchTerm` live, so a settings change (or a new fetchFn identity)
213
+ * refetches with the current search term.
214
+ */
215
+ makeFetch(query) {
216
+ return (req) => this.sparkService.executeQuery(query.id, {
217
+ sortColumns: req.sortColumns,
218
+ skip: (req.page - 1) * req.perPage,
219
+ take: req.perPage,
178
220
  search: this.searchTerm || undefined,
179
- }).then(r => ({ data: r.data, totalRecords: r.totalRecords })), 50));
180
- }
181
- async loadItems() {
182
- const currentQuery = this.query();
183
- if (!currentQuery)
184
- return;
185
- // Streaming queries use WebSocket instead of HTTP
186
- if (currentQuery.isStreamingQuery) {
187
- this.connectStreaming(currentQuery.id);
188
- return;
189
- }
190
- // Non-streaming: disconnect any previous streaming connection
191
- this.disconnectStreaming();
192
- try {
193
- const s = this.settings();
194
- const result = await this.sparkService.executeQuery(currentQuery.id, {
195
- sortColumns: s.sortColumns,
196
- skip: (s.page.selected - 1) * s.perPage.selected,
197
- take: s.perPage.selected,
198
- search: this.searchTerm || undefined,
199
- });
221
+ }).then(r => {
200
222
  this.errorMessage.set(null);
201
- const totalPages = Math.ceil(result.totalRecords / s.perPage.selected) || 1;
202
- this.paginationData.set({
203
- data: result.data,
204
- totalRecords: result.totalRecords,
205
- totalPages: totalPages,
206
- perPage: s.perPage.selected,
207
- page: s.page.selected
208
- });
209
- s.page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
210
- }
211
- catch (e) {
223
+ this.resultCount.set(r.totalRecords);
224
+ return {
225
+ data: r.data,
226
+ totalRecords: r.totalRecords,
227
+ totalPages: Math.ceil(r.totalRecords / req.perPage) || 1,
228
+ perPage: req.perPage,
229
+ page: req.page,
230
+ };
231
+ }).catch((e) => {
212
232
  this.errorMessage.set(e.error?.error || e.message || 'An unexpected error occurred');
213
- this.paginationData.set(undefined);
214
- }
233
+ this.resultCount.set(0);
234
+ return { data: [], totalRecords: 0, totalPages: 1, perPage: req.perPage, page: req.page };
235
+ });
215
236
  }
216
- onSettingsChange() {
237
+ /** Force a refetch (e.g. after a custom action) without changing page/sort. */
238
+ refresh() {
217
239
  if (this.isStreaming()) {
218
- // Streaming: sort/filter is client-side only
219
240
  this.applyFilter();
220
241
  return;
221
242
  }
222
- if (this.query()?.renderMode === 'VirtualScrolling') {
223
- this.virtualDataSource()?.reset();
224
- this.initVirtualDataSource();
225
- }
226
- else {
227
- this.loadItems();
228
- }
243
+ const q = this.query();
244
+ if (q)
245
+ this.fetchFn.set(this.makeFetch(q));
229
246
  }
230
247
  onSearchChange() {
231
248
  if (this.isStreaming()) {
232
249
  this.applyFilter();
233
250
  return;
234
251
  }
235
- if (this.query()?.renderMode === 'VirtualScrolling') {
236
- this.virtualDataSource()?.reset();
237
- this.initVirtualDataSource();
238
- }
239
- else {
240
- this.settings().page.selected = 1;
241
- this.loadItems();
242
- }
252
+ // Reset to page 1 for the new search.
253
+ const s = this.settings();
254
+ this.settings.set(new DatatableSettings({
255
+ perPage: { values: s.perPage.values, selected: s.perPage.selected },
256
+ page: { values: [1], selected: 1 },
257
+ sortColumns: s.sortColumns,
258
+ }));
259
+ // Re-assign the fetch callback so the datatable refetches even when
260
+ // page/perPage/sort are unchanged. ng-bootstrap 22.4's web component dedupes
261
+ // reloads by {sortColumns, perPage, page}; setting a new fetch identity resets
262
+ // that key (set fetch → _lastReloadKey = null) and forces the reload. makeFetch
263
+ // reads searchTerm live (mirrors refresh()).
264
+ const q = this.query();
265
+ if (q)
266
+ this.fetchFn.set(this.makeFetch(q));
243
267
  }
244
268
  clearSearch() {
245
269
  this.searchTerm = '';
246
270
  this.onSearchChange();
247
271
  }
248
- isVirtualScrolling = computed(() => this.query()?.renderMode === 'VirtualScrolling', ...(ngDevMode ? [{ debugName: "isVirtualScrolling" }] : []));
272
+ isVirtualScrolling = computed(() => this.query()?.renderMode === 'VirtualScrolling', /* @ts-ignore */
273
+ ...(ngDevMode ? [{ debugName: "isVirtualScrolling" }] : /* istanbul ignore next */ []));
249
274
  visibleAttributes = computed(() => {
250
275
  return this.entityType()?.attributes
251
276
  .filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.Query))
252
277
  .sort((a, b) => a.order - b.order) || [];
253
- }, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
278
+ }, /* @ts-ignore */
279
+ ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : /* istanbul ignore next */ []));
254
280
  getColumnRendererComponent(attr) {
255
281
  if (!attr.renderer)
256
282
  return null;
@@ -342,9 +368,9 @@ class SparkQueryListComponent {
342
368
  const term = this.searchTerm.toLowerCase();
343
369
  items = items.filter(item => item.attributes.some(a => String(a.value ?? '').toLowerCase().includes(term)));
344
370
  }
345
- // Apply sorting
346
- const isVirtual = this.query()?.renderMode === 'VirtualScrolling';
347
- const sortCols = isVirtual ? this.virtualSettings().sortColumns : this.settings().sortColumns;
371
+ // Apply sorting (client-side for the streaming snapshot; the datatable in
372
+ // [data] mode also auto-sorts on header clicks).
373
+ const sortCols = this.settings().sortColumns;
348
374
  if (sortCols.length > 0) {
349
375
  items = [...items].sort((a, b) => {
350
376
  for (const col of sortCols) {
@@ -357,32 +383,18 @@ class SparkQueryListComponent {
357
383
  return 0;
358
384
  });
359
385
  }
360
- if (isVirtual) {
361
- // Update the mutable filtered items array.
362
- // The stable data source's fetchFn closure reads from this.filteredItems,
363
- // so clearing its cache and emitting empty triggers the CDK viewport to re-fetch.
364
- this.filteredItems = items;
365
- this.virtualDataSource()?.reset();
366
- }
367
- else {
368
- this.paginationData.set({
369
- data: items,
370
- totalRecords: items.length,
371
- totalPages: 1,
372
- perPage: items.length,
373
- page: 1
374
- });
375
- }
386
+ this.streamItems.set(items);
387
+ this.resultCount.set(items.length);
376
388
  }
377
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkQueryListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
378
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SparkQueryListComponent, isStandalone: true, selector: "spark-query-list", inputs: { extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClicked: "rowClicked", createClicked: "createClicked" }, host: { properties: { "class.virtual-scrolling": "isVirtualScrolling()" } }, ngImport: i0, template: "<div class=\"d-flex flex-column h-100\">\n <!-- Title + Actions row -->\n <div class=\"d-flex justify-content-between align-items-center mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <h2>\n {{ (query()?.description | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <span class=\"badge bg-success ms-2\" style=\"font-size: 0.5em; vertical-align: middle;\">LIVE</span>\n }\n </h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'common.new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n </div>\n\n @if (query()?.renderMode === 'VirtualScrolling') {\n @if (virtualDataSource(); as vds) {\n <bs-virtual-datatable class=\"flex-grow-1 overflow-hidden\"\n [(settings)]=\"virtualSettings\"\n [dataSource]=\"vds\"\n [isResponsive]=\"true\"\n (settingsChange)=\"onSettingsChange()\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-template bsVirtualRowTemplate let-item>\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (item) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n </ng-template>\n </bs-virtual-datatable>\n }\n } @else {\n <bs-datatable class=\"flex-grow-1\" [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <tr *bsRowTemplate=\"let item of paginationData()\">\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n </td>\n }\n </tr>\n </bs-datatable>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(item, attr)\"></ng-container>\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 } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 -1.5rem -1.5rem}tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\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: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["data"], outputs: ["dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { kind: "component", type: BsVirtualDatatableComponent, selector: "bs-virtual-datatable", inputs: ["dataSource", "isResponsive", "itemSize"] }, { kind: "directive", type: BsVirtualRowTemplateDirective, selector: "[bsVirtualRowTemplate]" }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { 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: BsInputGroupComponent, selector: "bs-input-group" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
389
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkQueryListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
390
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkQueryListComponent, isStandalone: true, selector: "spark-query-list", inputs: { extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, showCustomActions: { classPropertyName: "showCustomActions", publicName: "showCustomActions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClicked: "rowClicked", createClicked: "createClicked", customActionExecuted: "customActionExecuted" }, host: { properties: { "class.virtual-scrolling": "isVirtualScrolling()" } }, ngImport: i0, template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\" [class.px-3]=\"!isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @if (canCreate()) {\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'common.new' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" class=\"btn btn-outline-primary\" (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 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.description | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <span class=\"badge bg-success ms-2\" style=\"font-size: 0.5em; vertical-align: middle;\">LIVE</span>\n }\n </h2>\n\n @if (entityType()) {\n <!-- Search box -->\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"8\" class=\"text-end\">\n @if (searchTerm && resultCount() !== null) {\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n </div>\n\n @if (isStreaming()) {\n <bs-datatable class=\"flex-grow-1\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"streamItems()\"\n [(settings)]=\"settings\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (row) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, row.id]\" (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n } @else {\n <bs-datatable class=\"flex-grow-1\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [fetch]=\"fetchFn()\"\n [(settings)]=\"settings\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (row) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, row.id]\" (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(item, attr)\"></ng-container>\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 } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n</ng-template>\n", styles: [".spark-actionbar{position:sticky;top:0;z-index:400;background-color:var(--bs-tertiary-bg);border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem}.spark-actionbar .btn{border-radius:0}:host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 -1.5rem -1.5rem}:host.virtual-scrolling ::ng-deep bs-datatable{display:flex;flex-direction:column;min-height:0;--mp-datatable-virtual-max-height: 100%}:host.virtual-scrolling ::ng-deep bs-datatable mp-datatable{flex:1 1 auto;min-height:0}tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\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: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3.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", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["columns", "data", "fetch", "settings", "selectionMode", "selectable", "selection", "rowKey", "resizableColumns", "pagination", "virtualScroll", "itemSize", "virtualBuffer", "isResponsive", "compareWith", "tree", "idKey", "childCountKey", "treeIndent", "expandedIds", "selectionStrategy"], outputs: ["settingsChange", "selectionChange", "rowClick", "rowDblClick", "rowContextMenu", "expandedIdsChange", "rowExpand", "rowCollapse"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]" }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { 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: BsInputGroupComponent, selector: "bs-input-group" }, { 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: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
379
391
  }
380
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkQueryListComponent, decorators: [{
392
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkQueryListComponent, decorators: [{
381
393
  type: Component,
382
- args: [{ selector: 'spark-query-list', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, RouterModule, BsAlertComponent, BsContainerComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsVirtualDatatableComponent, BsVirtualRowTemplateDirective, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsInputGroupComponent, BsSpinnerComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
394
+ args: [{ selector: 'spark-query-list', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, RouterModule, BsAlertComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsInputGroupComponent, BsPriorityNavComponent, BsPriorityNavItemDirective, BsSpinnerComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
383
395
  '[class.virtual-scrolling]': 'isVirtualScrolling()'
384
- }, template: "<div class=\"d-flex flex-column h-100\">\n <!-- Title + Actions row -->\n <div class=\"d-flex justify-content-between align-items-center mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <h2>\n {{ (query()?.description | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <span class=\"badge bg-success ms-2\" style=\"font-size: 0.5em; vertical-align: middle;\">LIVE</span>\n }\n </h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'common.new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n </div>\n\n @if (query()?.renderMode === 'VirtualScrolling') {\n @if (virtualDataSource(); as vds) {\n <bs-virtual-datatable class=\"flex-grow-1 overflow-hidden\"\n [(settings)]=\"virtualSettings\"\n [dataSource]=\"vds\"\n [isResponsive]=\"true\"\n (settingsChange)=\"onSettingsChange()\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-template bsVirtualRowTemplate let-item>\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (item) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n </ng-template>\n </bs-virtual-datatable>\n }\n } @else {\n <bs-datatable class=\"flex-grow-1\" [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <tr *bsRowTemplate=\"let item of paginationData()\">\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n </td>\n }\n </tr>\n </bs-datatable>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(item, attr)\"></ng-container>\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 } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 -1.5rem -1.5rem}tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"] }]
385
- }], ctorParameters: () => [], propDecorators: { extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], createClicked: [{ type: i0.Output, args: ["createClicked"] }] } });
396
+ }, template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\" [class.px-3]=\"!isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n @if (canCreate()) {\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'common.new' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" class=\"btn btn-outline-primary\" (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 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.description | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <span class=\"badge bg-success ms-2\" style=\"font-size: 0.5em; vertical-align: middle;\">LIVE</span>\n }\n </h2>\n\n @if (entityType()) {\n <!-- Search box -->\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"8\" class=\"text-end\">\n @if (searchTerm && resultCount() !== null) {\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n </div>\n\n @if (isStreaming()) {\n <bs-datatable class=\"flex-grow-1\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [data]=\"streamItems()\"\n [(settings)]=\"settings\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (row) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, row.id]\" (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n } @else {\n <bs-datatable class=\"flex-grow-1\"\n [virtualScroll]=\"isVirtualScrolling()\"\n [itemSize]=\"40\"\n [isResponsive]=\"isVirtualScrolling()\"\n [fetch]=\"fetchFn()\"\n [(settings)]=\"settings\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @let row = $any(item);\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (row) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, row.id]\" (click)=\"rowClicked.emit(row)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: row, attr: attr }\"></ng-container>\n }\n } @else {\n &nbsp;\n }\n </td>\n }\n </ng-container>\n </bs-datatable>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(item, attr)\"></ng-container>\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 } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n</ng-template>\n", styles: [".spark-actionbar{position:sticky;top:0;z-index:400;background-color:var(--bs-tertiary-bg);border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem}.spark-actionbar .btn{border-radius:0}:host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 -1.5rem -1.5rem}:host.virtual-scrolling ::ng-deep bs-datatable{display:flex;flex-direction:column;min-height:0;--mp-datatable-virtual-max-height: 100%}:host.virtual-scrolling ::ng-deep bs-datatable mp-datatable{flex:1 1 auto;min-height:0}tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"] }]
397
+ }], ctorParameters: () => [], propDecorators: { extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], showCustomActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCustomActions", required: false }] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], createClicked: [{ type: i0.Output, args: ["createClicked"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }] } });
386
398
 
387
399
  /**
388
400
  * Generated bundle index. Do not edit.