@mintplayer/ng-spark 22.24.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.
Files changed (46) hide show
  1. package/README.md +15 -0
  2. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +5 -3
  3. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  4. package/fesm2022/mintplayer-ng-spark-contributions.mjs +721 -0
  5. package/fesm2022/mintplayer-ng-spark-contributions.mjs.map +1 -0
  6. package/fesm2022/mintplayer-ng-spark-grid.mjs +131 -4
  7. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  8. package/fesm2022/mintplayer-ng-spark-history.mjs +1 -14
  9. package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -1
  10. package/fesm2022/mintplayer-ng-spark-models.mjs +41 -2
  11. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  12. package/fesm2022/mintplayer-ng-spark-moderation.mjs +4 -4
  13. package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -1
  14. package/fesm2022/mintplayer-ng-spark-panels.mjs +9 -1
  15. package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -1
  16. package/fesm2022/mintplayer-ng-spark-pipes.mjs +39 -3
  17. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  18. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +15 -6
  19. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  20. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +924 -16
  21. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  22. package/fesm2022/mintplayer-ng-spark-po-form.mjs +78 -20
  23. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  24. package/fesm2022/mintplayer-ng-spark-query-list.mjs +60 -5
  25. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  26. package/fesm2022/mintplayer-ng-spark-renderers.mjs +28 -1
  27. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  28. package/fesm2022/mintplayer-ng-spark-shell.mjs +31 -13
  29. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  30. package/fesm2022/mintplayer-ng-spark.mjs +4 -1
  31. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  32. package/package.json +7 -2
  33. package/types/mintplayer-ng-spark-client-operations.d.ts +7 -1
  34. package/types/mintplayer-ng-spark-contributions.d.ts +231 -0
  35. package/types/mintplayer-ng-spark-grid.d.ts +35 -3
  36. package/types/mintplayer-ng-spark-history.d.ts +3 -10
  37. package/types/mintplayer-ng-spark-models.d.ts +60 -3
  38. package/types/mintplayer-ng-spark-panels.d.ts +36 -1
  39. package/types/mintplayer-ng-spark-pipes.d.ts +12 -1
  40. package/types/mintplayer-ng-spark-po-detail.d.ts +4 -1
  41. package/types/mintplayer-ng-spark-po-edit.d.ts +305 -1
  42. package/types/mintplayer-ng-spark-po-form.d.ts +64 -15
  43. package/types/mintplayer-ng-spark-query-list.d.ts +26 -1
  44. package/types/mintplayer-ng-spark-renderers.d.ts +44 -2
  45. package/types/mintplayer-ng-spark-shell.d.ts +24 -9
  46. package/types/mintplayer-ng-spark.d.ts +12 -0
@@ -1,7 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, Injectable, computed, effect, inject, input, output, signal, untracked } from "@angular/core";
3
3
  import { SparkLanguageService, SparkService } from "@mintplayer/ng-spark/services";
4
- import { ShowedOn, hasShowedOnFlag, valueFor } from "@mintplayer/ng-spark/models";
4
+ import { ShowedOn, comparableValue, hasShowedOnFlag, valueFor } from "@mintplayer/ng-spark/models";
5
5
  import { DatePipe } from "@angular/common";
6
6
  import { Color } from "@mintplayer/ng-bootstrap";
7
7
  import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
@@ -68,19 +68,6 @@ i0.ɵɵngDeclareClassMetadata({
68
68
  function revisionAttributes(entityType) {
69
69
  return (entityType?.attributes ?? []).filter((a) => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject)).sort((a, b) => a.order - b.order);
70
70
  }
71
- function comparableValue(attribute) {
72
- if (!attribute) return "\0absent";
73
- return JSON.stringify({
74
- v: attribute.value ?? null,
75
- o: attribute.object ? nestedValues(attribute.object) : null,
76
- os: attribute.objects ? attribute.objects.map(nestedValues) : null
77
- });
78
- }
79
- function nestedValues(po) {
80
- const result = {};
81
- for (const a of po.attributes ?? []) result[a.name] = comparableValue(a);
82
- return result;
83
- }
84
71
  function diffRevision(entityType, revision, current) {
85
72
  if (!revision || !current) return [];
86
73
  const changes = [];
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-history.mjs","names":[],"sources":["../../history/src/spark-history.service.ts","../../history/src/revision-diff.ts","../../history/src/spark-po-history.component.ts","../../history/src/spark-po-history.component.html","../../history/src/spark-history-panel.component.ts","../../history/src/provide-spark-history.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { PersistentObject, SparkDeletedFilter } from '@mintplayer/ng-spark/models';\nimport { SparkService } from '@mintplayer/ng-spark/services';\n\n/** One revision of a row, newest first (`MintPlayer.Spark.History.SparkRevision`). */\nexport interface SparkRevision {\n changeVector: string;\n /** When the revision was written (ISO 8601 with offset); null when the server does not know. */\n lastModified?: string | null;\n /** Who wrote it — an id (D8: audit fields hold ids only). */\n userId?: string | null;\n /** The id resolved to a name at read time, when the app registered an `IHistoryUserNameResolver`. */\n userName?: string | null;\n /** The revision RavenDB writes when the document is deleted (a purge-free hard delete). */\n isDeleteRevision?: boolean;\n /** The revision the document currently is. Reverting to it changes nothing. */\n isCurrent?: boolean;\n}\n\nexport interface SparkRevisionListOptions {\n skip?: number;\n /** Server default 50, at most 200. */\n take?: number;\n /** `include` / `only` to read the history of a soft-deleted row (`ViewDeleted` holders only). */\n deleted?: SparkDeletedFilter | null;\n}\n\n/**\n * Client for the History package's endpoints (`MintPlayer.Spark.History`, #460 T1). Every read is\n * gated on the CURRENT row (a row the caller can no longer see has no readable history) and on\n * `History/T`; old revisions come back redacted like the live object.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkHistoryService {\n private readonly spark = inject(SparkService);\n\n /** `POST /spark/po/revisions` → the row's revisions, newest first. */\n list(type: string, id: string, options?: SparkRevisionListOptions): Promise<SparkRevision[]> {\n return this.spark.postEnvelope<SparkRevision[]>('/po/revisions', {\n objectTypeId: type,\n id,\n ...(options?.skip ? { skip: options.skip } : {}),\n ...(options?.take ? { take: options.take } : {}),\n ...(options?.deleted && options.deleted !== 'exclude' ? { deleted: options.deleted } : {}),\n });\n }\n\n /** `POST /spark/po/revision` → the revision as a read-only, redacted persistent object. */\n get(type: string, id: string, changeVector: string, options?: { deleted?: SparkDeletedFilter | null }): Promise<PersistentObject> {\n return this.spark.postEnvelope<PersistentObject>('/po/revision', {\n objectTypeId: type,\n id,\n changeVector,\n ...(options?.deleted && options.deleted !== 'exclude' ? { deleted: options.deleted } : {}),\n });\n }\n\n /**\n * `POST /spark/po/revert` → the reverted row. Requires `History/T`, `Revert/T` and `Edit/T`; saved\n * through the normal pipeline. 403 = the revert is disabled for this row, 409 = the row changed\n * since it was loaded, 400 = an interceptor refused (e.g. the row is locked).\n */\n revert(type: string, id: string, changeVector: string): Promise<PersistentObject> {\n return this.spark.postEnvelope<PersistentObject>('/po/revert', { objectTypeId: type, id, changeVector });\n }\n}\n","import { EntityAttributeDefinition, EntityType, PersistentObject, PersistentObjectAttribute, ShowedOn, hasShowedOnFlag } from '@mintplayer/ng-spark/models';\n\n/** One attribute that differs between a revision and the current object. */\nexport interface SparkRevisionChange {\n attribute: EntityAttributeDefinition;\n /** Absent when the attribute did not exist on that side (added or removed since). */\n revision: PersistentObjectAttribute | undefined;\n current: PersistentObjectAttribute | undefined;\n}\n\n/**\n * The attributes a revision is compared on: the ones the detail page shows, in its order. Redacted\n * attributes are simply absent from what the server sends, so they never appear as a difference.\n */\nexport function revisionAttributes(entityType: EntityType | null | undefined): EntityAttributeDefinition[] {\n return (entityType?.attributes ?? [])\n .filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order);\n}\n\n/**\n * A comparable form of an attribute's content.\n *\n * Nested objects (AsDetail rows, references rendered as objects) are reduced to their attributes'\n * values: their envelopes carry per-read fields (etags, breadcrumbs, row keys) that differ between two\n * reads of identical content and would report every AsDetail attribute as changed.\n */\nexport function comparableValue(attribute: PersistentObjectAttribute | undefined): string {\n if (!attribute) return '\\u0000absent';\n return JSON.stringify({\n v: attribute.value ?? null,\n o: attribute.object ? nestedValues(attribute.object) : null,\n os: attribute.objects ? attribute.objects.map(nestedValues) : null,\n });\n}\n\nfunction nestedValues(po: PersistentObject): Record<string, string> {\n const result: Record<string, string> = {};\n for (const a of po.attributes ?? []) result[a.name] = comparableValue(a);\n return result;\n}\n\n/** The shown attributes whose content differs between `revision` and `current`, in display order. */\nexport function diffRevision(\n entityType: EntityType | null | undefined,\n revision: PersistentObject | null | undefined,\n current: PersistentObject | null | undefined,\n): SparkRevisionChange[] {\n if (!revision || !current) return [];\n const changes: SparkRevisionChange[] = [];\n for (const attribute of revisionAttributes(entityType)) {\n const r = revision.attributes.find(a => a.name === attribute.name);\n const c = current.attributes.find(a => a.name === attribute.name);\n if (comparableValue(r) !== comparableValue(c)) changes.push({ attribute, revision: r, current: c });\n }\n return changes;\n}\n","import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, untracked } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { BsTableComponent } from '@mintplayer/ng-bootstrap/table';\nimport { SparkGridCellComponent, SparkGridRenderers } from '@mintplayer/ng-spark/grid';\nimport { QueryCellValuePipe, QueryReferenceChipsPipe, ReferenceChip, ResolveTranslationPipe, TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { cellValue } from '@mintplayer/ng-spark/renderers';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport {\n EntityAttributeDefinition, EntityPermissions, EntityType, LookupReference, PersistentObject, QueryColumn, QueryResultItem,\n SparkDeletedFilter, valueFor,\n} from '@mintplayer/ng-spark/models';\nimport { SparkHistoryService, SparkRevision } from './spark-history.service';\nimport { diffRevision, revisionAttributes } from './revision-diff';\n\ntype HistoryView = 'view' | 'diff';\n\n/** One attribute of one object, ready for `<spark-grid-cell>`. */\ninterface HistoryCell {\n column: QueryColumn;\n display: unknown;\n rendererValue: unknown;\n chips: ReferenceChip[];\n}\n\nconst cellValuePipe = new QueryCellValuePipe();\nconst chipsPipe = new QueryReferenceChipsPipe();\n\n/**\n * A row's revision history (#460, History package): the revision list, a read-only view of one\n * revision, its field diff against the current object, and Revert.\n *\n * Standalone — bind it anywhere — and also what the History detail panel\n * (`provideSparkHistory()`) renders on every routed detail page.\n *\n * Rights come from `permissions`: nothing is listed without `canViewHistory`, and Revert is offered\n * only with `canRevert` (the server's `Revert/T` together with `Edit/T`), never for the current\n * revision, a delete revision, or a row opened from the recycle bin (Revert saves through the live\n * pipeline, which does not see a deleted row). The server enforces all of it again.\n */\n@Component({\n selector: 'spark-po-history',\n imports: [DatePipe, BsAlertComponent, BsBadgeComponent, BsSpinnerComponent, BsTableComponent, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-po-history.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SparkPoHistoryComponent {\n private readonly history = inject(SparkHistoryService);\n protected readonly lang = inject(SparkLanguageService);\n\n /** Entity type id or alias, as the endpoints take it. */\n type = input.required<string>();\n id = input.required<string>();\n entityType = input.required<EntityType>();\n /** The object as it is now — the right-hand side of every diff. */\n current = input<PersistentObject | null>(null);\n permissions = input<EntityPermissions | null>(null);\n /** The mode the row was opened with (`only` = from the recycle bin). */\n deleted = input<SparkDeletedFilter | null>(null);\n /** Rows per request (server default 50, at most 200). */\n pageSize = input(50);\n\n /** Emitted with the saved object after a successful Revert. */\n reverted = output<PersistentObject>();\n\n protected readonly colors = Color;\n protected readonly revisions = signal<SparkRevision[]>([]);\n protected readonly loading = signal(false);\n protected readonly hasMore = signal(false);\n protected readonly error = signal<string | null>(null);\n\n protected readonly selected = signal<SparkRevision | null>(null);\n protected readonly selectedObject = signal<PersistentObject | null>(null);\n protected readonly selectedLoading = signal(false);\n protected readonly view = signal<HistoryView>('diff');\n protected readonly reverting = signal(false);\n\n protected readonly canView = computed(() => this.permissions()?.canViewHistory === true);\n\n protected readonly attributes = computed(() => revisionAttributes(this.entityType()));\n protected readonly changes = computed(() => diffRevision(this.entityType(), this.selectedObject(), this.current()));\n\n private readonly gridRenderers = inject(SparkGridRenderers);\n /** Lookup-reference labels for the shown attributes, so a lookup value reads as its label. */\n private readonly lookupOptions = signal<Record<string, LookupReference>>({});\n\n /**\n * The read-only view: every shown attribute of the selected revision, rendered by the same\n * `<spark-grid-cell>` (and custom renderers) as a query grid — a reference as its label, a date in\n * the viewer's culture and zone, a boolean as a checkbox — instead of the raw wire value.\n */\n protected readonly viewCells = computed(() => {\n const revision = this.selectedObject();\n return revision ? this.attributes().map(a => ({ attribute: a, cell: this.cell(a, revision) })) : [];\n });\n\n /** The diff rows, both sides rendered like {@link viewCells}. */\n protected readonly changeCells = computed(() => {\n const revision = this.selectedObject();\n const current = this.current();\n return this.changes().map(change => ({\n change,\n old: this.cell(change.attribute, revision),\n now: this.cell(change.attribute, current),\n }));\n });\n\n protected readonly canRevertSelected = computed(() => {\n const revision = this.selected();\n return !!revision\n && this.permissions()?.canRevert === true\n && this.deleted() !== 'only'\n && !revision.isCurrent\n && !revision.isDeleteRevision\n && !!this.selectedObject();\n });\n\n constructor() {\n effect(() => {\n const attributes = this.attributes();\n untracked(() => {\n this.gridRenderers.loadLookupOptions(attributes).then(o => this.lookupOptions.set(o), () => this.lookupOptions.set({}));\n });\n });\n\n // (Re)load the list when the row, its mode or the rights change. A revert emits a new `current`\n // but keeps the id, so it is not in this effect; `refresh()` reloads explicitly.\n effect(() => {\n const type = this.type();\n const id = this.id();\n const deleted = this.deleted();\n const allowed = this.canView();\n untracked(() => {\n this.clearSelection();\n this.revisions.set([]);\n if (allowed && type && id) void this.loadPage(type, id, deleted, 0);\n });\n });\n }\n\n /** Reloads the list from the top, dropping the selection. */\n async refresh(): Promise<void> {\n this.clearSelection();\n this.revisions.set([]);\n await this.loadPage(this.type(), this.id(), this.deleted(), 0);\n }\n\n async loadMore(): Promise<void> {\n await this.loadPage(this.type(), this.id(), this.deleted(), this.revisions().length);\n }\n\n private async loadPage(type: string, id: string, deleted: SparkDeletedFilter | null, skip: number): Promise<void> {\n this.loading.set(true);\n this.error.set(null);\n try {\n const take = this.pageSize();\n const page = await this.history.list(type, id, { skip, take, deleted });\n // A response for a row the inputs have moved away from is dropped.\n if (type !== this.type() || id !== this.id()) return;\n this.revisions.update(existing => skip === 0 ? page : [...existing, ...page]);\n this.hasMore.set(page.length >= take);\n } catch (e) {\n this.error.set(this.describe(e as HttpErrorResponse));\n } finally {\n this.loading.set(false);\n }\n }\n\n async select(revision: SparkRevision): Promise<void> {\n if (this.selected()?.changeVector === revision.changeVector) {\n this.clearSelection();\n return;\n }\n this.selected.set(revision);\n this.selectedObject.set(null);\n this.error.set(null);\n this.selectedLoading.set(true);\n try {\n const po = await this.history.get(this.type(), this.id(), revision.changeVector, { deleted: this.deleted() });\n if (this.selected()?.changeVector === revision.changeVector) this.selectedObject.set(po);\n } catch (e) {\n this.error.set(this.describe(e as HttpErrorResponse));\n } finally {\n this.selectedLoading.set(false);\n }\n }\n\n clearSelection(): void {\n this.selected.set(null);\n this.selectedObject.set(null);\n }\n\n async revert(): Promise<void> {\n const revision = this.selected();\n if (!revision || !this.canRevertSelected() || this.reverting()) return;\n if (!confirm(this.lang.t('history.confirmRevert'))) return;\n\n this.reverting.set(true);\n this.error.set(null);\n try {\n const saved = await this.history.revert(this.type(), this.id(), revision.changeVector);\n this.reverted.emit(saved);\n await this.refresh();\n } catch (e) {\n this.error.set(this.describe(e as HttpErrorResponse));\n } finally {\n this.reverting.set(false);\n }\n }\n\n /**\n * One attribute of `po` for `<spark-grid-cell>`. A persistent object is a row the grid pipes read\n * (`valueFor` understands both shapes), so the value resolution is the grid's, not a copy of it.\n */\n private cell(attribute: EntityAttributeDefinition, po: PersistentObject | null): HistoryCell {\n const column = attribute as unknown as QueryColumn;\n const row = po as unknown as QueryResultItem | null;\n const display = cellValuePipe.transform(column, row, this.lookupOptions());\n return {\n column,\n // The detail page's placeholder for an empty value; a boolean keeps null (an indeterminate box).\n display: attribute.dataType !== 'boolean' && (display == null || display === '') ? '-' : display,\n rendererValue: cellValue(valueFor(row, attribute.name)),\n chips: chipsPipe.transform(column, row),\n };\n }\n\n protected who(revision: SparkRevision): string {\n return revision.userName || revision.userId || this.lang.t('history.unknownUser');\n }\n\n private describe(err: HttpErrorResponse): string {\n switch (err?.status) {\n case 404: return this.lang.t('history.unavailable');\n case 409: return this.lang.t('history.conflict');\n default: return err?.error?.error || err?.message || this.lang.t('common.actionFailed');\n }\n }\n}\n","@if (canView()) {\n <div class=\"spark-po-history\">\n @if (error(); as message) {\n <bs-alert [type]=\"colors.danger\" class=\"d-block mb-3\">{{ message }}</bs-alert>\n }\n\n @if (revisions().length === 0 && !loading()) {\n <p class=\"text-muted mb-0 spark-history-empty\">{{ 'history.noRevisions' | t }}</p>\n } @else {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n <th>{{ 'history.modifiedAt' | t }}</th>\n <th>{{ 'history.modifiedBy' | t }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (revision of revisions(); track revision.changeVector) {\n <tr class=\"spark-history-row\" [class.table-active]=\"selected()?.changeVector === revision.changeVector\">\n <td>\n <button type=\"button\" class=\"btn btn-link p-0 spark-history-select\" (click)=\"select(revision)\">\n {{ revision.lastModified ? (revision.lastModified | date:'medium') : '—' }}\n </button>\n </td>\n <td>{{ who(revision) }}</td>\n <td class=\"text-end\">\n @if (revision.isCurrent) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-history-current\">{{ 'history.current' | t }}</bs-badge>\n }\n @if (revision.isDeleteRevision) {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-1\">{{ 'history.deleteRevision' | t }}</bs-badge>\n }\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n }\n\n @if (loading()) {\n <div class=\"text-center p-2\"><bs-spinner /></div>\n } @else if (hasMore()) {\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm spark-history-more\" (click)=\"loadMore()\">\n {{ 'history.loadMore' | t }}\n </button>\n }\n\n @if (selected(); as revision) {\n <div class=\"mt-3 spark-history-selected\">\n <div class=\"d-flex flex-wrap gap-2 align-items-center mb-2\">\n <div class=\"btn-group\" role=\"group\">\n <button type=\"button\" class=\"btn btn-sm spark-history-diff-tab\"\n [class.btn-secondary]=\"view() === 'diff'\" [class.btn-outline-secondary]=\"view() !== 'diff'\"\n (click)=\"view.set('diff')\">{{ 'history.changes' | t }}</button>\n <button type=\"button\" class=\"btn btn-sm spark-history-view-tab\"\n [class.btn-secondary]=\"view() === 'view'\" [class.btn-outline-secondary]=\"view() !== 'view'\"\n (click)=\"view.set('view')\">{{ 'history.revision' | t }}</button>\n </div>\n @if (canRevertSelected()) {\n <button type=\"button\" class=\"btn btn-warning btn-sm spark-history-revert\" [disabled]=\"reverting()\" (click)=\"revert()\">\n {{ 'history.revert' | t }}\n </button>\n }\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm ms-auto\" (click)=\"clearSelection()\">\n {{ 'history.close' | t }}\n </button>\n </div>\n\n @if (selectedLoading()) {\n <div class=\"text-center p-2\"><bs-spinner /></div>\n } @else if (selectedObject()) {\n @if (view() === 'diff') {\n @if (changes().length === 0) {\n <p class=\"text-muted spark-history-nochanges\">{{ 'history.noChanges' | t }}</p>\n } @else {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n <th>{{ 'history.field' | t }}</th>\n <th>{{ 'history.revisionValue' | t }}</th>\n <th>{{ 'history.currentValue' | t }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of changeCells(); track row.change.attribute.name) {\n <tr class=\"spark-history-change\">\n <th scope=\"row\">{{ row.change.attribute.label | resolveTranslation:row.change.attribute.name }}</th>\n <td class=\"spark-history-old\">\n <spark-grid-cell [column]=\"row.old.column\" [display]=\"row.old.display\" [rendererValue]=\"row.old.rendererValue\"\n [item]=\"selectedObject()\" [chips]=\"row.old.chips\" />\n </td>\n <td class=\"spark-history-new\">\n <spark-grid-cell [column]=\"row.now.column\" [display]=\"row.now.display\" [rendererValue]=\"row.now.rendererValue\"\n [item]=\"current()\" [chips]=\"row.now.chips\" />\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n }\n } @else {\n <dl class=\"spark-history-view\">\n @for (entry of viewCells(); track entry.attribute.name) {\n <dt>{{ entry.attribute.label | resolveTranslation:entry.attribute.name }}</dt>\n <dd>\n <spark-grid-cell [column]=\"entry.cell.column\" [display]=\"entry.cell.display\" [rendererValue]=\"entry.cell.rendererValue\"\n [item]=\"selectedObject()\" [chips]=\"entry.cell.chips\" />\n </dd>\n }\n </dl>\n }\n }\n </div>\n }\n </div>\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkDetailContext } from '@mintplayer/ng-spark/panels';\nimport { SparkPoHistoryComponent } from './spark-po-history.component';\n\n/**\n * The History card on the routed detail page (#460). Registered by {@link provideSparkHistory} as a\n * `SPARK_DETAIL_PANELS` entry; renders nothing for a caller without `History/T`.\n * After a Revert it asks the page to reload, so the attributes above show the reverted values.\n */\n@Component({\n selector: 'spark-history-panel',\n imports: [BsCardComponent, BsCardHeaderComponent, TranslateKeyPipe, SparkPoHistoryComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (visible()) {\n <bs-card class=\"d-block spark-history-panel\">\n <bs-card-header>{{ 'history.title' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-history\n [type]=\"context().type\"\n [id]=\"context().id\"\n [entityType]=\"context().entityType\"\n [current]=\"context().item\"\n [permissions]=\"context().permissions\"\n [deleted]=\"context().deleted\"\n (reverted)=\"context().reload()\" />\n </div>\n </bs-card>\n }\n `,\n})\nexport class SparkHistoryPanelComponent {\n context = input.required<SparkDetailContext>();\n\n protected readonly visible = computed(() => this.context().permissions?.canViewHistory === true);\n}\n","import { Provider } from '@angular/core';\nimport { provideSparkDetailPanels } from '@mintplayer/ng-spark/panels';\nimport { SparkHistoryPanelComponent } from './spark-history-panel.component';\n\n/**\n * Adds a History card (`<spark-po-history>`: revision list, read-only view, diff, Revert) to every\n * detail page `sparkRoutes()` routes to, for holders of `History/T` (#460). Needs the server package\n * `MintPlayer.Spark.History` and revisions enabled for the type in its model.\n *\n * `providers: [provideSpark(...), provideSparkHistory()]`\n */\nexport function provideSparkHistory(options?: { order?: number }): Provider[] {\n return provideSparkDetailPanels({ id: 'spark-history', component: SparkHistoryPanelComponent, order: options?.order ?? 100 });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiCA,IAAa,sBAAb,MAAa,oBAAmB;CACb,QAAQ,OAAO,YAAY;CAG5C,KAAK,MAAc,IAAY,SAAkC;EAC/D,OAAO,KAAK,MAAM,aAA8B,iBAAiB;GAC/D,cAAc;GACd;GACA,GAAI,SAAS,OAAO,EAAE,MAAM,QAAQ,KAAI,IAAK,CAAA;GAC7C,GAAI,SAAS,OAAO,EAAE,MAAM,QAAQ,KAAI,IAAK,CAAA;GAC7C,GAAI,SAAS,WAAW,QAAQ,YAAY,YAAY,EAAE,SAAS,QAAQ,QAAO,IAAK,CAAA;EACxF,CAAA;CACH;CAGA,IAAI,MAAc,IAAY,cAAsB,SAAiD;EACnG,OAAO,KAAK,MAAM,aAA+B,gBAAgB;GAC/D,cAAc;GACd;GACA;GACA,GAAI,SAAS,WAAW,QAAQ,YAAY,YAAY,EAAE,SAAS,QAAQ,QAAO,IAAK,CAAA;EACxF,CAAA;CACH;CAOA,OAAO,MAAc,IAAY,cAAoB;EACnD,OAAO,KAAK,MAAM,aAA+B,cAAc;GAAE,cAAc;GAAM;GAAI;EAAY,CAAE;CACzG;CA/BW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAnB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,YADN;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAmB,YAAA,CAAA;EAD/B,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE;;;AClBlC,SAAgB,mBAAmB,YAAyC;CAC1E,QAAQ,YAAY,cAAc,CAAA,EAAA,CAC/B,QAAO,MAAK,EAAE,aAAa,gBAAgB,EAAE,UAAU,SAAS,gBAAgB,CAAC,CAAC,CAClF,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACrC;AASA,SAAgB,gBAAgB,WAAgD;CAC9E,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO,KAAK,UAAU;EACpB,GAAG,UAAU,SAAS;EACtB,GAAG,UAAU,SAAS,aAAa,UAAU,MAAM,IAAI;EACvD,IAAI,UAAU,UAAU,UAAU,QAAQ,IAAI,YAAY,IAAI;CAC/D,CAAA;AACH;AAEA,SAAS,aAAa,IAAoB;CACxC,MAAM,SAAiC,CAAA;CACvC,KAAK,MAAM,KAAK,GAAG,cAAc,CAAA,GAAI,OAAO,EAAE,QAAQ,gBAAgB,CAAC;CACvE,OAAO;AACT;AAGA,SAAgB,aACd,YACA,UACA,SAA4C;CAE5C,IAAI,CAAC,YAAY,CAAC,SAAS,OAAO,CAAA;CAClC,MAAM,UAAiC,CAAA;CACvC,KAAK,MAAM,aAAa,mBAAmB,UAAU,GAAG;EACtD,MAAM,IAAI,SAAS,WAAW,MAAK,MAAK,EAAE,SAAS,UAAU,IAAI;EACjE,MAAM,IAAI,QAAQ,WAAW,MAAK,MAAK,EAAE,SAAS,UAAU,IAAI;EAChE,IAAI,gBAAgB,CAAC,MAAM,gBAAgB,CAAC,GAAG,QAAQ,KAAK;GAAE;GAAW,UAAU;GAAG,SAAS;EAAC,CAAE;CACpG;CACA,OAAO;AACT;AC3BA,MAAM,gBAAgB,IAAI,mBAAkB;AAC5C,MAAM,YAAY,IAAI,wBAAuB;AAoB7C,IAAa,0BAAb,MAAa,wBAAuB;CACjB,UAAU,OAAO,mBAAmB;CAClC,OAAO,OAAO,oBAAoB;CAGrD,OAAO,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAC/B,KAAK,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,KAAA,CAAA,+BAAA,CAAA,CAAA;CAC7B,aAAa,MAAM,SAAsB,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEzC,UAAU,MAA+B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAC9C,cAAc,MAAgC,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAEnD,UAAU,MAAiC,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEhD,WAAW,MAAM,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGpB,WAAW,OAAM;CAEE,SAAS;CACT,YAAY,OAAwB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CACvC,UAAU,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACvB,UAAU,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACvB,QAAQ,OAAsB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEnC,WAAW,OAA6B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAC7C,iBAAiB,OAAgC,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CACtD,kBAAkB,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAC/B,OAAO,OAAoB,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAClC,YAAY,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEzB,UAAU,eAAe,KAAK,YAAW,CAAE,EAAE,mBAAmB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAErE,aAAa,eAAe,mBAAmB,KAAK,WAAU,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAClE,UAAU,eAAe,aAAa,KAAK,WAAU,GAAI,KAAK,eAAc,GAAI,KAAK,QAAO,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAElG,gBAAgB,OAAO,kBAAkB;CAEzC,gBAAgB,OAAwC,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAOzD,YAAY,eAAc;EAC3C,MAAM,WAAW,KAAK,eAAc;EACpC,OAAO,WAAW,KAAK,WAAU,CAAE,CAAC,KAAI,OAAM;GAAE,WAAW;GAAG,MAAM,KAAK,KAAK,GAAG,QAAQ;EAAC,EAAG,IAAI,CAAA;CACnG,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGiB,cAAc,eAAc;EAC7C,MAAM,WAAW,KAAK,eAAc;EACpC,MAAM,UAAU,KAAK,QAAO;EAC5B,OAAO,KAAK,QAAO,CAAE,CAAC,KAAI,YAAW;GACnC;GACA,KAAK,KAAK,KAAK,OAAO,WAAW,QAAQ;GACzC,KAAK,KAAK,KAAK,OAAO,WAAW,OAAO;EACzC,EAAC;CACJ,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,oBAAoB,eAAc;EACnD,MAAM,WAAW,KAAK,SAAQ;EAC9B,OAAO,CAAC,CAAC,YACJ,KAAK,YAAW,CAAE,EAAE,cAAc,QAClC,KAAK,QAAO,MAAO,UACnB,CAAC,SAAS,aACV,CAAC,SAAS,oBACV,CAAC,CAAC,KAAK,eAAc;CAC5B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,oBAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAA;EACE,aAAY;GACV,MAAM,aAAa,KAAK,WAAU;GAClC,gBAAe;IACb,KAAK,cAAc,kBAAkB,UAAU,CAAC,CAAC,MAAK,MAAK,KAAK,cAAc,IAAI,CAAC,SAAS,KAAK,cAAc,IAAI,CAAA,CAAE,CAAC;GACxH,CAAC;EACH,CAAC;EAID,aAAY;GACV,MAAM,OAAO,KAAK,KAAI;GACtB,MAAM,KAAK,KAAK,GAAE;GAClB,MAAM,UAAU,KAAK,QAAO;GAC5B,MAAM,UAAU,KAAK,QAAO;GAC5B,gBAAe;IACb,KAAK,eAAc;IACnB,KAAK,UAAU,IAAI,CAAA,CAAE;IACrB,IAAI,WAAW,QAAQ,IAAI,AAAK,KAAK,SAAS,MAAM,IAAI,SAAS,CAAC;GACpE,CAAC;EACH,CAAC;CACH;CAGA,MAAM,UAAO;EACX,KAAK,eAAc;EACnB,KAAK,UAAU,IAAI,CAAA,CAAE;EACrB,MAAM,KAAK,SAAS,KAAK,KAAI,GAAI,KAAK,GAAE,GAAI,KAAK,QAAO,GAAI,CAAC;CAC/D;CAEA,MAAM,WAAQ;EACZ,MAAM,KAAK,SAAS,KAAK,KAAI,GAAI,KAAK,GAAE,GAAI,KAAK,QAAO,GAAI,KAAK,UAAS,CAAE,CAAC,MAAM;CACrF;CAEQ,MAAM,SAAS,MAAc,IAAY,SAAoC,MAAY;EAC/F,KAAK,QAAQ,IAAI,IAAI;EACrB,KAAK,MAAM,IAAI,IAAI;EACnB,IAAI;GACF,MAAM,OAAO,KAAK,SAAQ;GAC1B,MAAM,OAAO,MAAM,KAAK,QAAQ,KAAK,MAAM,IAAI;IAAE;IAAM;IAAM;GAAO,CAAE;GAEtE,IAAI,SAAS,KAAK,KAAI,KAAM,OAAO,KAAK,GAAE,GAAI;GAC9C,KAAK,UAAU,QAAO,aAAY,SAAS,IAAI,OAAO,CAAC,GAAG,UAAU,GAAG,IAAI,CAAC;GAC5E,KAAK,QAAQ,IAAI,KAAK,UAAU,IAAI;EACtC,SAAS,GAAG;GACV,KAAK,MAAM,IAAI,KAAK,SAAS,CAAsB,CAAC;EACtD,UAAU;GACR,KAAK,QAAQ,IAAI,KAAK;EACxB;CACF;CAEA,MAAM,OAAO,UAAuB;EAClC,IAAI,KAAK,SAAQ,CAAE,EAAE,iBAAiB,SAAS,cAAc;GAC3D,KAAK,eAAc;GACnB;EACF;EACA,KAAK,SAAS,IAAI,QAAQ;EAC1B,KAAK,eAAe,IAAI,IAAI;EAC5B,KAAK,MAAM,IAAI,IAAI;EACnB,KAAK,gBAAgB,IAAI,IAAI;EAC7B,IAAI;GACF,MAAM,KAAK,MAAM,KAAK,QAAQ,IAAI,KAAK,KAAI,GAAI,KAAK,GAAE,GAAI,SAAS,cAAc,EAAE,SAAS,KAAK,QAAO,EAAE,CAAE;GAC5G,IAAI,KAAK,SAAQ,CAAE,EAAE,iBAAiB,SAAS,cAAc,KAAK,eAAe,IAAI,EAAE;EACzF,SAAS,GAAG;GACV,KAAK,MAAM,IAAI,KAAK,SAAS,CAAsB,CAAC;EACtD,UAAU;GACR,KAAK,gBAAgB,IAAI,KAAK;EAChC;CACF;CAEA,iBAAc;EACZ,KAAK,SAAS,IAAI,IAAI;EACtB,KAAK,eAAe,IAAI,IAAI;CAC9B;CAEA,MAAM,SAAM;EACV,MAAM,WAAW,KAAK,SAAQ;EAC9B,IAAI,CAAC,YAAY,CAAC,KAAK,kBAAiB,KAAM,KAAK,UAAS,GAAI;EAChE,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,uBAAuB,CAAC,GAAG;EAEpD,KAAK,UAAU,IAAI,IAAI;EACvB,KAAK,MAAM,IAAI,IAAI;EACnB,IAAI;GACF,MAAM,QAAQ,MAAM,KAAK,QAAQ,OAAO,KAAK,KAAI,GAAI,KAAK,GAAE,GAAI,SAAS,YAAY;GACrF,KAAK,SAAS,KAAK,KAAK;GACxB,MAAM,KAAK,QAAO;EACpB,SAAS,GAAG;GACV,KAAK,MAAM,IAAI,KAAK,SAAS,CAAsB,CAAC;EACtD,UAAU;GACR,KAAK,UAAU,IAAI,KAAK;EAC1B;CACF;CAMQ,KAAK,WAAsC,IAA2B;EAC5E,MAAM,SAAS;EACf,MAAM,MAAM;EACZ,MAAM,UAAU,cAAc,UAAU,QAAQ,KAAK,KAAK,cAAa,CAAE;EACzE,OAAO;GACL;GAEA,SAAS,UAAU,aAAa,cAAc,WAAW,QAAQ,YAAY,MAAM,MAAM;GACzF,eAAe,UAAU,SAAS,KAAK,UAAU,IAAI,CAAC;GACtD,OAAO,UAAU,UAAU,QAAQ,GAAG;EACvC;CACH;CAEU,IAAI,UAAuB;EACnC,OAAO,SAAS,YAAY,SAAS,UAAU,KAAK,KAAK,EAAE,qBAAqB;CAClF;CAEQ,SAAS,KAAsB;EACrC,QAAQ,KAAK,QAAb;GACE,KAAK,KAAK,OAAO,KAAK,KAAK,EAAE,qBAAqB;GAClD,KAAK,KAAK,OAAO,KAAK,KAAK,EAAE,kBAAkB;GAC/C,SAAS,OAAO,KAAK,OAAO,SAAS,KAAK,WAAW,KAAK,KAAK,EAAE,qBAAqB;EACxF;CACF;CA/LW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAuB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAvB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAuB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,IAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,UAAA,WAAA;EAAA,UAAA;EAAA,UClDpC;EAqHA,cAAA;GAAA;IAAA,MAAA;IAAA,MDvEsB;IAAgB,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,mBAAA,qBAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAgB,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAkB,UAAA;IAAA,QAAA,CAAA,QAAA,OAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAgB,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAsB,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAA1G;IAAQ,MAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAoG;IAAsB,MAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAgB,MAAA;GAAA;EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAInJ,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAuB,YAAA,CAAA;EANnC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAkB,SACnB;IAAC;IAAU;IAAkB;IAAkB;IAAoB;IAAkB;IAAwB;IAAwB;GAAgB;GAAC,iBAE9I,wBAAwB;GAAM,UAAA;EAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEfjD,IAAa,6BAAb,MAAa,2BAA0B;CACrC,UAAU,MAAM,SAA8B,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAE3B,UAAU,eAAe,KAAK,QAAO,CAAE,CAAC,aAAa,mBAAmB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAHrF,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAA0B,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAA1B,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAA0B,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,SAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UAlB3B;;;;;;;;;;;;;;;;;EAgBT,UAAA;EAAA,cAAA;GAAA;IAAA,MAAA;IAAA,MAlBS;IAAe,UAAA;IAAA,QAAA,CAAA,SAAA,SAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAqB,UAAA;IAAA,QAAA,CAAA,SAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAoB;IAAuB,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAzC;IAAgB,MAAA;GAAA;EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAoBvD,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAA0B,YAAA,CAAA;EAtBtC,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS;IAAC;IAAiB;IAAuB;IAAkB;GAAuB;GAC3F,iBAAiB,wBAAwB;GACzC,UAAU;;;;;;;;;;;;;;;;;EAiBX,CAAA;;;;;;;;;;;ACrBD,SAAgB,oBAAoB,SAA4B;CAC9D,OAAO,yBAAyB;EAAE,IAAI;EAAiB,WAAW;EAA4B,OAAO,SAAS,SAAS;CAAG,CAAE;AAC9H"}
1
+ {"version":3,"file":"mintplayer-ng-spark-history.mjs","names":[],"sources":["../../history/src/spark-history.service.ts","../../history/src/revision-diff.ts","../../history/src/spark-po-history.component.ts","../../history/src/spark-po-history.component.html","../../history/src/spark-history-panel.component.ts","../../history/src/provide-spark-history.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { PersistentObject, SparkDeletedFilter } from '@mintplayer/ng-spark/models';\nimport { SparkService } from '@mintplayer/ng-spark/services';\n\n/** One revision of a row, newest first (`MintPlayer.Spark.History.SparkRevision`). */\nexport interface SparkRevision {\n changeVector: string;\n /** When the revision was written (ISO 8601 with offset); null when the server does not know. */\n lastModified?: string | null;\n /** Who wrote it — an id (D8: audit fields hold ids only). */\n userId?: string | null;\n /** The id resolved to a name at read time, when the app registered an `IHistoryUserNameResolver`. */\n userName?: string | null;\n /** The revision RavenDB writes when the document is deleted (a purge-free hard delete). */\n isDeleteRevision?: boolean;\n /** The revision the document currently is. Reverting to it changes nothing. */\n isCurrent?: boolean;\n}\n\nexport interface SparkRevisionListOptions {\n skip?: number;\n /** Server default 50, at most 200. */\n take?: number;\n /** `include` / `only` to read the history of a soft-deleted row (`ViewDeleted` holders only). */\n deleted?: SparkDeletedFilter | null;\n}\n\n/**\n * Client for the History package's endpoints (`MintPlayer.Spark.History`, #460 T1). Every read is\n * gated on the CURRENT row (a row the caller can no longer see has no readable history) and on\n * `History/T`; old revisions come back redacted like the live object.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkHistoryService {\n private readonly spark = inject(SparkService);\n\n /** `POST /spark/po/revisions` → the row's revisions, newest first. */\n list(type: string, id: string, options?: SparkRevisionListOptions): Promise<SparkRevision[]> {\n return this.spark.postEnvelope<SparkRevision[]>('/po/revisions', {\n objectTypeId: type,\n id,\n ...(options?.skip ? { skip: options.skip } : {}),\n ...(options?.take ? { take: options.take } : {}),\n ...(options?.deleted && options.deleted !== 'exclude' ? { deleted: options.deleted } : {}),\n });\n }\n\n /** `POST /spark/po/revision` → the revision as a read-only, redacted persistent object. */\n get(type: string, id: string, changeVector: string, options?: { deleted?: SparkDeletedFilter | null }): Promise<PersistentObject> {\n return this.spark.postEnvelope<PersistentObject>('/po/revision', {\n objectTypeId: type,\n id,\n changeVector,\n ...(options?.deleted && options.deleted !== 'exclude' ? { deleted: options.deleted } : {}),\n });\n }\n\n /**\n * `POST /spark/po/revert` → the reverted row. Requires `History/T`, `Revert/T` and `Edit/T`; saved\n * through the normal pipeline. 403 = the revert is disabled for this row, 409 = the row changed\n * since it was loaded, 400 = an interceptor refused (e.g. the row is locked).\n */\n revert(type: string, id: string, changeVector: string): Promise<PersistentObject> {\n return this.spark.postEnvelope<PersistentObject>('/po/revert', { objectTypeId: type, id, changeVector });\n }\n}\n","import { EntityAttributeDefinition, EntityType, PersistentObject, PersistentObjectAttribute, ShowedOn, comparableValue, hasShowedOnFlag } from '@mintplayer/ng-spark/models';\n\n/** One attribute that differs between a revision and the current object. */\nexport interface SparkRevisionChange {\n attribute: EntityAttributeDefinition;\n /** Absent when the attribute did not exist on that side (added or removed since). */\n revision: PersistentObjectAttribute | undefined;\n current: PersistentObjectAttribute | undefined;\n}\n\n/**\n * The attributes a revision is compared on: the ones the detail page shows, in its order. Redacted\n * attributes are simply absent from what the server sends, so they never appear as a difference.\n */\nexport function revisionAttributes(entityType: EntityType | null | undefined): EntityAttributeDefinition[] {\n return (entityType?.attributes ?? [])\n .filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order);\n}\n\n// The normalizer lives in models since po-edit's conflict merge needs the same answer; re-exported\n// here so History's public surface is unchanged.\nexport { comparableValue };\n\n/** The shown attributes whose content differs between `revision` and `current`, in display order. */\nexport function diffRevision(\n entityType: EntityType | null | undefined,\n revision: PersistentObject | null | undefined,\n current: PersistentObject | null | undefined,\n): SparkRevisionChange[] {\n if (!revision || !current) return [];\n const changes: SparkRevisionChange[] = [];\n for (const attribute of revisionAttributes(entityType)) {\n const r = revision.attributes.find(a => a.name === attribute.name);\n const c = current.attributes.find(a => a.name === attribute.name);\n if (comparableValue(r) !== comparableValue(c)) changes.push({ attribute, revision: r, current: c });\n }\n return changes;\n}\n","import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, untracked } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { BsTableComponent } from '@mintplayer/ng-bootstrap/table';\nimport { SparkGridCellComponent, SparkGridRenderers } from '@mintplayer/ng-spark/grid';\nimport { QueryCellValuePipe, QueryReferenceChipsPipe, ReferenceChip, ResolveTranslationPipe, TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { cellValue } from '@mintplayer/ng-spark/renderers';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport {\n EntityAttributeDefinition, EntityPermissions, EntityType, LookupReference, PersistentObject, QueryColumn, QueryResultItem,\n SparkDeletedFilter, valueFor,\n} from '@mintplayer/ng-spark/models';\nimport { SparkHistoryService, SparkRevision } from './spark-history.service';\nimport { diffRevision, revisionAttributes } from './revision-diff';\n\ntype HistoryView = 'view' | 'diff';\n\n/** One attribute of one object, ready for `<spark-grid-cell>`. */\ninterface HistoryCell {\n column: QueryColumn;\n display: unknown;\n rendererValue: unknown;\n chips: ReferenceChip[];\n}\n\nconst cellValuePipe = new QueryCellValuePipe();\nconst chipsPipe = new QueryReferenceChipsPipe();\n\n/**\n * A row's revision history (#460, History package): the revision list, a read-only view of one\n * revision, its field diff against the current object, and Revert.\n *\n * Standalone — bind it anywhere — and also what the History detail panel\n * (`provideSparkHistory()`) renders on every routed detail page.\n *\n * Rights come from `permissions`: nothing is listed without `canViewHistory`, and Revert is offered\n * only with `canRevert` (the server's `Revert/T` together with `Edit/T`), never for the current\n * revision, a delete revision, or a row opened from the recycle bin (Revert saves through the live\n * pipeline, which does not see a deleted row). The server enforces all of it again.\n */\n@Component({\n selector: 'spark-po-history',\n imports: [DatePipe, BsAlertComponent, BsBadgeComponent, BsSpinnerComponent, BsTableComponent, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-po-history.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SparkPoHistoryComponent {\n private readonly history = inject(SparkHistoryService);\n protected readonly lang = inject(SparkLanguageService);\n\n /** Entity type id or alias, as the endpoints take it. */\n type = input.required<string>();\n id = input.required<string>();\n entityType = input.required<EntityType>();\n /** The object as it is now — the right-hand side of every diff. */\n current = input<PersistentObject | null>(null);\n permissions = input<EntityPermissions | null>(null);\n /** The mode the row was opened with (`only` = from the recycle bin). */\n deleted = input<SparkDeletedFilter | null>(null);\n /** Rows per request (server default 50, at most 200). */\n pageSize = input(50);\n\n /** Emitted with the saved object after a successful Revert. */\n reverted = output<PersistentObject>();\n\n protected readonly colors = Color;\n protected readonly revisions = signal<SparkRevision[]>([]);\n protected readonly loading = signal(false);\n protected readonly hasMore = signal(false);\n protected readonly error = signal<string | null>(null);\n\n protected readonly selected = signal<SparkRevision | null>(null);\n protected readonly selectedObject = signal<PersistentObject | null>(null);\n protected readonly selectedLoading = signal(false);\n protected readonly view = signal<HistoryView>('diff');\n protected readonly reverting = signal(false);\n\n protected readonly canView = computed(() => this.permissions()?.canViewHistory === true);\n\n protected readonly attributes = computed(() => revisionAttributes(this.entityType()));\n protected readonly changes = computed(() => diffRevision(this.entityType(), this.selectedObject(), this.current()));\n\n private readonly gridRenderers = inject(SparkGridRenderers);\n /** Lookup-reference labels for the shown attributes, so a lookup value reads as its label. */\n private readonly lookupOptions = signal<Record<string, LookupReference>>({});\n\n /**\n * The read-only view: every shown attribute of the selected revision, rendered by the same\n * `<spark-grid-cell>` (and custom renderers) as a query grid — a reference as its label, a date in\n * the viewer's culture and zone, a boolean as a checkbox — instead of the raw wire value.\n */\n protected readonly viewCells = computed(() => {\n const revision = this.selectedObject();\n return revision ? this.attributes().map(a => ({ attribute: a, cell: this.cell(a, revision) })) : [];\n });\n\n /** The diff rows, both sides rendered like {@link viewCells}. */\n protected readonly changeCells = computed(() => {\n const revision = this.selectedObject();\n const current = this.current();\n return this.changes().map(change => ({\n change,\n old: this.cell(change.attribute, revision),\n now: this.cell(change.attribute, current),\n }));\n });\n\n protected readonly canRevertSelected = computed(() => {\n const revision = this.selected();\n return !!revision\n && this.permissions()?.canRevert === true\n && this.deleted() !== 'only'\n && !revision.isCurrent\n && !revision.isDeleteRevision\n && !!this.selectedObject();\n });\n\n constructor() {\n effect(() => {\n const attributes = this.attributes();\n untracked(() => {\n this.gridRenderers.loadLookupOptions(attributes).then(o => this.lookupOptions.set(o), () => this.lookupOptions.set({}));\n });\n });\n\n // (Re)load the list when the row, its mode or the rights change. A revert emits a new `current`\n // but keeps the id, so it is not in this effect; `refresh()` reloads explicitly.\n effect(() => {\n const type = this.type();\n const id = this.id();\n const deleted = this.deleted();\n const allowed = this.canView();\n untracked(() => {\n this.clearSelection();\n this.revisions.set([]);\n if (allowed && type && id) void this.loadPage(type, id, deleted, 0);\n });\n });\n }\n\n /** Reloads the list from the top, dropping the selection. */\n async refresh(): Promise<void> {\n this.clearSelection();\n this.revisions.set([]);\n await this.loadPage(this.type(), this.id(), this.deleted(), 0);\n }\n\n async loadMore(): Promise<void> {\n await this.loadPage(this.type(), this.id(), this.deleted(), this.revisions().length);\n }\n\n private async loadPage(type: string, id: string, deleted: SparkDeletedFilter | null, skip: number): Promise<void> {\n this.loading.set(true);\n this.error.set(null);\n try {\n const take = this.pageSize();\n const page = await this.history.list(type, id, { skip, take, deleted });\n // A response for a row the inputs have moved away from is dropped.\n if (type !== this.type() || id !== this.id()) return;\n this.revisions.update(existing => skip === 0 ? page : [...existing, ...page]);\n this.hasMore.set(page.length >= take);\n } catch (e) {\n this.error.set(this.describe(e as HttpErrorResponse));\n } finally {\n this.loading.set(false);\n }\n }\n\n async select(revision: SparkRevision): Promise<void> {\n if (this.selected()?.changeVector === revision.changeVector) {\n this.clearSelection();\n return;\n }\n this.selected.set(revision);\n this.selectedObject.set(null);\n this.error.set(null);\n this.selectedLoading.set(true);\n try {\n const po = await this.history.get(this.type(), this.id(), revision.changeVector, { deleted: this.deleted() });\n if (this.selected()?.changeVector === revision.changeVector) this.selectedObject.set(po);\n } catch (e) {\n this.error.set(this.describe(e as HttpErrorResponse));\n } finally {\n this.selectedLoading.set(false);\n }\n }\n\n clearSelection(): void {\n this.selected.set(null);\n this.selectedObject.set(null);\n }\n\n async revert(): Promise<void> {\n const revision = this.selected();\n if (!revision || !this.canRevertSelected() || this.reverting()) return;\n if (!confirm(this.lang.t('history.confirmRevert'))) return;\n\n this.reverting.set(true);\n this.error.set(null);\n try {\n const saved = await this.history.revert(this.type(), this.id(), revision.changeVector);\n this.reverted.emit(saved);\n await this.refresh();\n } catch (e) {\n this.error.set(this.describe(e as HttpErrorResponse));\n } finally {\n this.reverting.set(false);\n }\n }\n\n /**\n * One attribute of `po` for `<spark-grid-cell>`. A persistent object is a row the grid pipes read\n * (`valueFor` understands both shapes), so the value resolution is the grid's, not a copy of it.\n */\n private cell(attribute: EntityAttributeDefinition, po: PersistentObject | null): HistoryCell {\n const column = attribute as unknown as QueryColumn;\n const row = po as unknown as QueryResultItem | null;\n const display = cellValuePipe.transform(column, row, this.lookupOptions());\n return {\n column,\n // The detail page's placeholder for an empty value; a boolean keeps null (an indeterminate box).\n display: attribute.dataType !== 'boolean' && (display == null || display === '') ? '-' : display,\n rendererValue: cellValue(valueFor(row, attribute.name)),\n chips: chipsPipe.transform(column, row),\n };\n }\n\n protected who(revision: SparkRevision): string {\n return revision.userName || revision.userId || this.lang.t('history.unknownUser');\n }\n\n private describe(err: HttpErrorResponse): string {\n switch (err?.status) {\n case 404: return this.lang.t('history.unavailable');\n case 409: return this.lang.t('history.conflict');\n default: return err?.error?.error || err?.message || this.lang.t('common.actionFailed');\n }\n }\n}\n","@if (canView()) {\n <div class=\"spark-po-history\">\n @if (error(); as message) {\n <bs-alert [type]=\"colors.danger\" class=\"d-block mb-3\">{{ message }}</bs-alert>\n }\n\n @if (revisions().length === 0 && !loading()) {\n <p class=\"text-muted mb-0 spark-history-empty\">{{ 'history.noRevisions' | t }}</p>\n } @else {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n <th>{{ 'history.modifiedAt' | t }}</th>\n <th>{{ 'history.modifiedBy' | t }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (revision of revisions(); track revision.changeVector) {\n <tr class=\"spark-history-row\" [class.table-active]=\"selected()?.changeVector === revision.changeVector\">\n <td>\n <button type=\"button\" class=\"btn btn-link p-0 spark-history-select\" (click)=\"select(revision)\">\n {{ revision.lastModified ? (revision.lastModified | date:'medium') : '—' }}\n </button>\n </td>\n <td>{{ who(revision) }}</td>\n <td class=\"text-end\">\n @if (revision.isCurrent) {\n <bs-badge [type]=\"colors.primary\" class=\"spark-history-current\">{{ 'history.current' | t }}</bs-badge>\n }\n @if (revision.isDeleteRevision) {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-1\">{{ 'history.deleteRevision' | t }}</bs-badge>\n }\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n }\n\n @if (loading()) {\n <div class=\"text-center p-2\"><bs-spinner /></div>\n } @else if (hasMore()) {\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm spark-history-more\" (click)=\"loadMore()\">\n {{ 'history.loadMore' | t }}\n </button>\n }\n\n @if (selected(); as revision) {\n <div class=\"mt-3 spark-history-selected\">\n <div class=\"d-flex flex-wrap gap-2 align-items-center mb-2\">\n <div class=\"btn-group\" role=\"group\">\n <button type=\"button\" class=\"btn btn-sm spark-history-diff-tab\"\n [class.btn-secondary]=\"view() === 'diff'\" [class.btn-outline-secondary]=\"view() !== 'diff'\"\n (click)=\"view.set('diff')\">{{ 'history.changes' | t }}</button>\n <button type=\"button\" class=\"btn btn-sm spark-history-view-tab\"\n [class.btn-secondary]=\"view() === 'view'\" [class.btn-outline-secondary]=\"view() !== 'view'\"\n (click)=\"view.set('view')\">{{ 'history.revision' | t }}</button>\n </div>\n @if (canRevertSelected()) {\n <button type=\"button\" class=\"btn btn-warning btn-sm spark-history-revert\" [disabled]=\"reverting()\" (click)=\"revert()\">\n {{ 'history.revert' | t }}\n </button>\n }\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm ms-auto\" (click)=\"clearSelection()\">\n {{ 'history.close' | t }}\n </button>\n </div>\n\n @if (selectedLoading()) {\n <div class=\"text-center p-2\"><bs-spinner /></div>\n } @else if (selectedObject()) {\n @if (view() === 'diff') {\n @if (changes().length === 0) {\n <p class=\"text-muted spark-history-nochanges\">{{ 'history.noChanges' | t }}</p>\n } @else {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n <th>{{ 'history.field' | t }}</th>\n <th>{{ 'history.revisionValue' | t }}</th>\n <th>{{ 'history.currentValue' | t }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of changeCells(); track row.change.attribute.name) {\n <tr class=\"spark-history-change\">\n <th scope=\"row\">{{ row.change.attribute.label | resolveTranslation:row.change.attribute.name }}</th>\n <td class=\"spark-history-old\">\n <spark-grid-cell [column]=\"row.old.column\" [display]=\"row.old.display\" [rendererValue]=\"row.old.rendererValue\"\n [item]=\"selectedObject()\" [chips]=\"row.old.chips\" />\n </td>\n <td class=\"spark-history-new\">\n <spark-grid-cell [column]=\"row.now.column\" [display]=\"row.now.display\" [rendererValue]=\"row.now.rendererValue\"\n [item]=\"current()\" [chips]=\"row.now.chips\" />\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n }\n } @else {\n <dl class=\"spark-history-view\">\n @for (entry of viewCells(); track entry.attribute.name) {\n <dt>{{ entry.attribute.label | resolveTranslation:entry.attribute.name }}</dt>\n <dd>\n <spark-grid-cell [column]=\"entry.cell.column\" [display]=\"entry.cell.display\" [rendererValue]=\"entry.cell.rendererValue\"\n [item]=\"selectedObject()\" [chips]=\"entry.cell.chips\" />\n </dd>\n }\n </dl>\n }\n }\n </div>\n }\n </div>\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkDetailContext } from '@mintplayer/ng-spark/panels';\nimport { SparkPoHistoryComponent } from './spark-po-history.component';\n\n/**\n * The History card on the routed detail page (#460). Registered by {@link provideSparkHistory} as a\n * `SPARK_DETAIL_PANELS` entry; renders nothing for a caller without `History/T`.\n * After a Revert it asks the page to reload, so the attributes above show the reverted values.\n */\n@Component({\n selector: 'spark-history-panel',\n imports: [BsCardComponent, BsCardHeaderComponent, TranslateKeyPipe, SparkPoHistoryComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (visible()) {\n <bs-card class=\"d-block spark-history-panel\">\n <bs-card-header>{{ 'history.title' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-history\n [type]=\"context().type\"\n [id]=\"context().id\"\n [entityType]=\"context().entityType\"\n [current]=\"context().item\"\n [permissions]=\"context().permissions\"\n [deleted]=\"context().deleted\"\n (reverted)=\"context().reload()\" />\n </div>\n </bs-card>\n }\n `,\n})\nexport class SparkHistoryPanelComponent {\n context = input.required<SparkDetailContext>();\n\n protected readonly visible = computed(() => this.context().permissions?.canViewHistory === true);\n}\n","import { Provider } from '@angular/core';\nimport { provideSparkDetailPanels } from '@mintplayer/ng-spark/panels';\nimport { SparkHistoryPanelComponent } from './spark-history-panel.component';\n\n/**\n * Adds a History card (`<spark-po-history>`: revision list, read-only view, diff, Revert) to every\n * detail page `sparkRoutes()` routes to, for holders of `History/T` (#460). Needs the server package\n * `MintPlayer.Spark.History` and revisions enabled for the type in its model.\n *\n * `providers: [provideSpark(...), provideSparkHistory()]`\n */\nexport function provideSparkHistory(options?: { order?: number }): Provider[] {\n return provideSparkDetailPanels({ id: 'spark-history', component: SparkHistoryPanelComponent, order: options?.order ?? 100 });\n}\n"],"mappings":"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"}
@@ -85,6 +85,7 @@ function nestedPoToDict(po) {
85
85
  if (typeof po.id === "string" && po.id !== "") dict[AS_DETAIL_ROW_KEY] = po.id;
86
86
  const originalDates = originalDatesOf(po);
87
87
  if (originalDates) dict[AS_DETAIL_ORIGINAL_DATES_KEY] = originalDates;
88
+ if (po.metadata) dict[AS_DETAIL_METADATA_KEY] = po.metadata;
88
89
  return dict;
89
90
  }
90
91
  function originalDatesOf(po) {
@@ -115,7 +116,12 @@ function resolvedBreadcrumb(value, typeName) {
115
116
  const AS_DETAIL_BREADCRUMBS_KEY = "__sparkBreadcrumbs";
116
117
  const AS_DETAIL_ORIGINAL_DATES_KEY = "__sparkOriginalDates";
117
118
  function isReservedAsDetailKey(key) {
118
- return key === "__sparkRowKey" || key === "__sparkBreadcrumb" || key === "__sparkBreadcrumbs" || key === "__sparkOriginalDates";
119
+ return key === "__sparkRowKey" || key === "__sparkBreadcrumb" || key === "__sparkBreadcrumbs" || key === "__sparkOriginalDates" || key === "__sparkMetadata";
120
+ }
121
+ const AS_DETAIL_METADATA_KEY = "__sparkMetadata";
122
+ function rowMetadata(row, key) {
123
+ const metadata = row?.[AS_DETAIL_METADATA_KEY];
124
+ return metadata && typeof metadata === "object" ? metadata[key] : void 0;
119
125
  }
120
126
  function nestedPoToDisplayRow(po) {
121
127
  if (!po) return {};
@@ -128,6 +134,7 @@ function nestedPoToDisplayRow(po) {
128
134
  if (breadcrumbs) dict[AS_DETAIL_BREADCRUMBS_KEY] = breadcrumbs;
129
135
  if (typeof po.breadcrumb === "string" && po.breadcrumb !== "") dict[AS_DETAIL_SELF_BREADCRUMB_KEY] = po.breadcrumb;
130
136
  if (typeof po.id === "string" && po.id !== "") dict[AS_DETAIL_ROW_KEY] = po.id;
137
+ if (po.metadata) dict[AS_DETAIL_METADATA_KEY] = po.metadata;
131
138
  return dict;
132
139
  }
133
140
  function displayValueForAttribute(attr) {
@@ -220,6 +227,38 @@ var ELookupDisplayType;
220
227
  ELookupDisplayType[ELookupDisplayType["Dropdown"] = 0] = "Dropdown";
221
228
  ELookupDisplayType[ELookupDisplayType["Modal"] = 1] = "Modal";
222
229
  })(ELookupDisplayType || (ELookupDisplayType = {}));
230
+ function comparableValue(attribute) {
231
+ if (!attribute) return "\0absent";
232
+ return JSON.stringify({
233
+ v: attribute.value ?? null,
234
+ o: attribute.object ? nestedValues(attribute.object) : null,
235
+ os: attribute.objects ? attribute.objects.map(nestedValues) : null
236
+ });
237
+ }
238
+ function nestedValues(po) {
239
+ const result = {};
240
+ for (const a of po.attributes ?? []) result[a.name] = comparableValue(a);
241
+ return result;
242
+ }
243
+ function comparableFormValue(value) {
244
+ return JSON.stringify(normalizeFormValue(value));
245
+ }
246
+ function formValuesEqual(a, b) {
247
+ return comparableFormValue(a) === comparableFormValue(b);
248
+ }
249
+ function normalizeFormValue(value) {
250
+ if (value === void 0 || value === null) return null;
251
+ if (Array.isArray(value)) return value.map(normalizeFormValue);
252
+ if (typeof value === "object") {
253
+ const result = {};
254
+ for (const key of Object.keys(value).sort()) {
255
+ if (isReservedAsDetailKey(key)) continue;
256
+ result[key] = normalizeFormValue(value[key]);
257
+ }
258
+ return result;
259
+ }
260
+ return value;
261
+ }
223
262
  function isShowedOnQuery(showedOn) {
224
263
  const value = (showedOn ?? "").trim().toLowerCase();
225
264
  return value === "query" || value === "both";
@@ -433,6 +472,6 @@ function toNumber(value) {
433
472
  const n = Number(value);
434
473
  return Number.isFinite(n) ? n : null;
435
474
  }
436
- export { AS_DETAIL_BREADCRUMBS_KEY, AS_DETAIL_ORIGINAL_DATES_KEY, AS_DETAIL_ROW_KEY, AS_DETAIL_SELF_BREADCRUMB_KEY, ELookupDisplayType, EReferenceDisplayType, ShowedOn, applyOverlay, currentLanguage, defaultQueryActions, dictToNestedPo, evaluateRules, filterDetailActions, filterQueryActions, fromDateInput, fromDateInputValue, fromDateTimeLocalInput, hasShowedOnFlag, isDateDataType, isPersistentObject, isQueryRow, isReservedAsDetailKey, isShowedOnDetail, isShowedOnQuery, mergeRefreshValues, nestedPoToDict, nestedPoToDisplayRow, normalizeSubQuery, overlayFromResponse, parseSelectionRule, parseWireDate, resolveTranslation, resolvedBreadcrumb, selectionModeFor, selfBreadcrumb, subQueriesOf, toDateInput, toDateInputValue, toDateTimeLocalInput, valueFor, wireDatesEqual };
475
+ export { AS_DETAIL_BREADCRUMBS_KEY, AS_DETAIL_METADATA_KEY, AS_DETAIL_ORIGINAL_DATES_KEY, AS_DETAIL_ROW_KEY, AS_DETAIL_SELF_BREADCRUMB_KEY, ELookupDisplayType, EReferenceDisplayType, ShowedOn, applyOverlay, comparableFormValue, comparableValue, currentLanguage, defaultQueryActions, dictToNestedPo, evaluateRules, filterDetailActions, filterQueryActions, formValuesEqual, fromDateInput, fromDateInputValue, fromDateTimeLocalInput, hasShowedOnFlag, isDateDataType, isPersistentObject, isQueryRow, isReservedAsDetailKey, isShowedOnDetail, isShowedOnQuery, mergeRefreshValues, nestedPoToDict, nestedPoToDisplayRow, normalizeSubQuery, overlayFromResponse, parseSelectionRule, parseWireDate, resolveTranslation, resolvedBreadcrumb, rowMetadata, selectionModeFor, selfBreadcrumb, subQueriesOf, toDateInput, toDateInputValue, toDateTimeLocalInput, valueFor, wireDatesEqual };
437
476
 
438
477
  //# sourceMappingURL=mintplayer-ng-spark-models.mjs.map