@mintplayer/ng-spark 22.31.0 → 22.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -1
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-account.mjs +18 -18
- package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +16 -8
- package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-core.mjs +6 -6
- package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-login.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs +24 -24
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-contributions.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-grid.mjs +220 -49
- package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-history.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-icon.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +6 -6
- package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-models.mjs +44 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-moderation.mjs +21 -21
- package/fesm2022/mintplayer-ng-spark-notifications.mjs +687 -0
- package/fesm2022/mintplayer-ng-spark-notifications.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs +179 -0
- package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-organizations.mjs +202 -0
- package/fesm2022/mintplayer-ng-spark-organizations.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +125 -85
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +90 -26
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +84 -12
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-pwa.mjs +74 -6
- package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +10 -109
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-renderers.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-score-renderers.mjs +801 -0
- package/fesm2022/mintplayer-ng-spark-score-renderers.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-services.mjs +424 -72
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-share-page.mjs +334 -0
- package/fesm2022/mintplayer-ng-spark-share-page.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-share.mjs +10 -0
- package/fesm2022/mintplayer-ng-spark-share.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-shell.mjs +38 -36
- package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +9 -9
- package/package.json +25 -1
- package/types/mintplayer-ng-spark-grid.d.ts +77 -26
- package/types/mintplayer-ng-spark-models.d.ts +150 -17
- package/types/mintplayer-ng-spark-notifications.d.ts +188 -0
- package/types/mintplayer-ng-spark-organizations-accept.d.ts +40 -0
- package/types/mintplayer-ng-spark-organizations.d.ts +72 -0
- package/types/mintplayer-ng-spark-pipes.d.ts +10 -0
- package/types/mintplayer-ng-spark-po-detail.d.ts +26 -0
- package/types/mintplayer-ng-spark-po-edit.d.ts +37 -0
- package/types/mintplayer-ng-spark-pwa.d.ts +48 -2
- package/types/mintplayer-ng-spark-query-list.d.ts +8 -23
- package/types/mintplayer-ng-spark-score-renderers.d.ts +340 -0
- package/types/mintplayer-ng-spark-services.d.ts +146 -6
- package/types/mintplayer-ng-spark-share-page.d.ts +67 -0
- package/types/mintplayer-ng-spark-share.d.ts +26 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-po-detail.mjs","names":[],"sources":["../../po-detail/src/spark-po-detail.component.ts","../../po-detail/src/spark-po-detail.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal, TemplateRef, Type, untracked } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';\nimport { ActivatedRoute, Router, RouterModule } from '@angular/router';\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 { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsTableComponent } from '@mintplayer/ng-bootstrap/table';\nimport { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from '@mintplayer/ng-bootstrap/tab-control';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkService, SparkLanguageService, SparkReturnNavigationService } from '@mintplayer/ng-spark/services';\nimport { SparkAttributePatch, SparkAttributeRefreshService, SparkClientMethodRegistry, SparkQueryRefreshService } from '@mintplayer/ng-spark/client-operations';\nimport {\n TranslateKeyPipe,\n ResolveTranslationPipe,\n AttributeValuePipe,\n RawAttributeValuePipe,\n AsDetailColumnsPipe,\n AsDetailCellValuePipe,\n ArrayValuePipe,\n ReferenceLinkRoutePipe,\n ReferenceChipsPipe,\n ParsedDatePipe,\n AsDetailRowRendererPipe,\n} from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkAttributeDescriptionComponent } from '@mintplayer/ng-spark/attribute-description';\nimport { SparkQueryCardComponent, SparkGridCellComponent } from '@mintplayer/ng-spark/grid';\nimport { SPARK_ATTRIBUTE_RENDERERS, SparkResolvedRowRenderer, rendererValue, rowRendererInputs, withDeclaredInputs } from '@mintplayer/ng-spark/renderers';\nimport {\n AttributeGroup,\n AttributeTab,\n CustomActionDefinition,\n EntityAttributeDefinition,\n EntityType,\n LookupReference,\n PersistentObject,\n QueryResultItem,\n ShowedOn,\n hasShowedOnFlag,\n EntityPermissions,\n SparkDeletedFilter,\n builtInAction,\n confirmationText,\n filterDetailActions,\n isShowedOnDetail,\n subQueriesOf,\n} from '@mintplayer/ng-spark/models';\nimport {\n SPARK_DETAIL_ACTIONS,\n SPARK_DETAIL_PANELS,\n SparkDetailContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\nimport { combineLatest } from 'rxjs';\n\n@Component({\n selector: 'spark-po-detail',\n imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, RouterModule, BsAlertComponent, BsBadgeComponent,BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsTableComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryCardComponent, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailRowRendererPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe, ReferenceChipsPipe, ParsedDatePipe, SparkAttributeDescriptionComponent],\n templateUrl: './spark-po-detail.component.html',\n styleUrl: './spark-po-detail.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SparkPoDetailComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly returnNavigation = inject(SparkReturnNavigationService);\n private readonly queryRefresh = inject(SparkQueryRefreshService);\n private readonly attributeRefresh = inject(SparkAttributeRefreshService);\n protected readonly lang = inject(SparkLanguageService);\n private readonly clientMethods = inject(SparkClientMethodRegistry);\n protected readonly rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);\n\n /**\n * Add-on panels and action-bar buttons (#460, `SPARK_DETAIL_PANELS` / `SPARK_DETAIL_ACTIONS`).\n * Multi-provided, so History, SoftDelete and Moderation can each add theirs to the page\n * `sparkRoutes()` routes to, which is created by the router and takes no templates.\n */\n protected readonly detailPanels = orderSparkExtensions(inject(SPARK_DETAIL_PANELS, { optional: true }), p => p.order);\n protected readonly detailActions = orderSparkExtensions(inject(SPARK_DETAIL_ACTIONS, { optional: true }), a => a.priority ?? 60);\n\n showCustomActions = input(true);\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n extraContentTemplate = input<TemplateRef<{ $implicit: PersistentObject; entityType: EntityType }> | null>(null);\n\n /**\n * Header slots forwarded to every sub-query card on this page.\n *\n * A structural directive cannot cross a component boundary, and this component is created by\n * the router — in a default app there is no `<spark-po-detail>` tag to project content into.\n * So an app that wants slotted chrome on its sub-queries substitutes its own route component\n * via `SparkRouteConfig.poDetail` and forwards the templates through here. Forwarding a\n * `TemplateRef` as an input is already this component's idiom; see the two above.\n *\n * Each slot may target one query by alias, so a single forwarded set serves a page rendering\n * several sub-queries.\n */\n queryIconTemplate = input<TemplateRef<any> | null>(null);\n queryCaptionTemplate = input<TemplateRef<any> | null>(null);\n queryActionsTemplate = input<TemplateRef<any> | null>(null);\n\n edited = output<void>();\n deleted = output<void>();\n customActionExecuted = output<{ action: CustomActionDefinition; item: PersistentObject }>();\n\n colors = Color;\n errorMessage = signal<string | null>(null);\n entityType = signal<EntityType | null>(null);\n\n /** The sub-queries rendered as cards below the form, in object form (#460, D17). */\n protected readonly subQueries = computed(() => subQueriesOf(this.entityType()));\n allEntityTypes = signal<EntityType[]>([]);\n item = signal<PersistentObject | null>(null);\n lookupReferenceOptions = signal<Record<string, LookupReference>>({});\n asDetailTypes = signal<Record<string, EntityType>>({});\n asDetailReferenceOptions = signal<Record<string, Record<string, QueryResultItem[]>>>({});\n type = '';\n id = '';\n /** The catalogue's Edit (#467, D8) when this page offers it: listed for the caller, on the detail side, and allowed on this row. */\n editAction = signal<CustomActionDefinition | null>(null);\n /** The catalogue's Delete, on the same terms as {@link editAction}. */\n deleteAction = signal<CustomActionDefinition | null>(null);\n canEdit = computed(() => this.editAction() !== null);\n canDelete = computed(() => this.deleteAction() !== null);\n customActions = signal<CustomActionDefinition[]>([]);\n /** Type-level rights, kept for the add-on context; null until loaded. */\n permissions = signal<EntityPermissions | null>(null);\n\n /**\n * The route's `?deleted=` mode (#460, T2): `only` when the row was opened from the recycle bin.\n * Sent with every load of this object; the server honours it only for `ViewDeleted` holders, so\n * for anyone else a deleted row stays a 404.\n */\n deletedMode = signal<SparkDeletedFilter | null>(null);\n\n /** True when the page shows a soft-deleted row: nothing but Restore/Purge applies to it. */\n protected readonly isDeletedView = computed(() => this.deletedMode() === 'only');\n\n /** What every add-on panel and action receives; rebuilt whenever the object or rights change. */\n protected readonly detailContext = computed((): SparkDetailContext | null => {\n const item = this.item();\n const entityType = this.entityType();\n if (!item || !entityType) return null;\n return {\n type: this.type,\n id: this.id,\n item,\n entityType,\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n reload: () => this.load(),\n };\n });\n\n /**\n * The actions actually offered for the object on screen.\n *\n * The catalogue at `/spark/actions/{objectTypeId}` is per TYPE -- the server is never told which\n * row is open -- so an action that applies to only some rows cannot be filtered there. The\n * entity's actions hook decides while it has the entity in hand and withholds what does not\n * apply, and the object arrives carrying that answer.\n *\n * Since #460 (D13) the server asks the same hook again when the action is submitted and refuses a\n * disabled one with 403, so hiding it here matches what the server will do. What this prevents is\n * offering a destructive action where it cannot possibly apply -- Coverage showed an irreversible\n * red \"Delete data\" button on every repository page, healthy ones included, and only admitted it\n * would refuse after the confirmation prompt.\n */\n visibleCustomActions = computed(() => {\n const withheld = this.item()?.disabledActions;\n if (!withheld?.length) return this.customActions();\n\n const lowered = new Set(withheld.map(name => name.toLowerCase()));\n return this.customActions().filter(action => !lowered.has(action.name.toLowerCase()));\n });\n\n constructor() {\n // The query map too: Restore navigates from `?deleted=only` to the same path without it, and the\n // object must then be re-read as a live row.\n combineLatest([this.route.paramMap, this.route.queryParamMap])\n .pipe(takeUntilDestroyed())\n .subscribe(([params, query]) => this.onParamsChange(params, query.get('deleted')));\n\n // Server-issued `refreshAttribute` patches for the object this page is showing.\n //\n // Tracks the token and the entity type, never `item()`: writing the signal this effect reads\n // would re-run it forever, so the read-modify-write of the object happens untracked.\n effect(() => {\n const objectTypeId = this.entityType()?.id;\n if (!this.attributeRefresh.tokenFor(objectTypeId, this.id)) return;\n\n const patches = this.attributeRefresh.patchesFor(objectTypeId, this.id);\n untracked(() => this.applyAttributePatches(patches));\n });\n }\n\n /**\n * Applies patched attribute values in place, without a re-fetch.\n *\n * The operation carries the value the server computed after its own write, so this is both\n * cheaper and more correct than re-reading: a re-read goes through a RavenDB index that may\n * still be stale from the very write that produced the patch, and would show the old value.\n */\n private applyAttributePatches(patches: Record<string, SparkAttributePatch>): void {\n const current = this.item();\n if (!current) return;\n\n let changed = false;\n const attributes = current.attributes.map(attribute => {\n if (!Object.prototype.hasOwnProperty.call(patches, attribute.name)) return attribute;\n\n const patch = patches[attribute.name];\n const next = { ...attribute };\n let patched = false;\n\n // Each field is applied on its own, because they are not interchangeable: an AsDetail\n // attribute carries its rows in object/objects and leaves value null, so applying only\n // value could never refresh a detail grid.\n //\n // Compared by identity in every case. A scalar skips when unchanged, and a null skips\n // against a null -- which matters because the server writes object/objects as null on a\n // scalar patch, and treating that as a change would repaint on every patch. A rewritten\n // collection is a new array and so never matches, which is the behaviour wanted: comparing\n // rows deeply would cost more than the repaint it saves.\n if (patch.value !== undefined && next.value !== patch.value) {\n next.value = patch.value;\n patched = true;\n }\n // Only for the attributes that actually use them. The server writes object/objects as\n // null on every scalar patch, so applying them unconditionally would mark a scalar\n // attribute changed on each patch and repaint for nothing.\n if (attribute.dataType === 'AsDetail') {\n if (patch.object !== undefined && next.object !== patch.object) {\n next.object = patch.object as PersistentObject | null;\n patched = true;\n }\n if (patch.objects !== undefined && next.objects !== patch.objects) {\n next.objects = patch.objects as PersistentObject[] | null;\n patched = true;\n }\n }\n\n if (!patched) return attribute;\n changed = true;\n return next;\n });\n\n if (changed) this.item.set({ ...current, attributes });\n }\n\n private async onParamsChange(params: any, deleted: string | null = null): Promise<void> {\n this.type = params.get('type') || '';\n this.id = params.get('id') || '';\n const mode = parseSparkDeletedParam(deleted);\n // `exclude` is the default on the wire; keep it out of the request like an absent parameter.\n this.deletedMode.set(mode === 'exclude' ? null : mode);\n await this.load();\n }\n\n /** The object as the page's current mode sees it. Only a set mode is sent. */\n private loadItem(): Promise<PersistentObject> {\n const deleted = this.deletedMode();\n return deleted\n ? this.sparkService.get(this.type, this.id, { deleted })\n : this.sparkService.get(this.type, this.id);\n }\n\n /** (Re)loads the object, its type, rights and actions. Also what add-ons call via `context.reload()`. */\n async load(): Promise<void> {\n this.errorMessage.set(null);\n try {\n const [entityTypes, item] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.loadItem()\n ]);\n\n this.allEntityTypes.set(entityTypes);\n this.entityType.set(entityTypes.find(t => t.id === this.type || t.alias === this.type) || null);\n this.item.set(item);\n this.loadLookupReferenceOptions();\n this.loadAsDetailTypes();\n\n const et = this.entityType();\n if (et) {\n const [permissions, actions] = await Promise.all([\n this.sparkService.getPermissions(et.id),\n this.sparkService.getCustomActions(et.id)\n ]);\n // Prefer the per-row affordances (#236 G5) when the server attached them — a row the\n // caller may read but not mutate then hides its Edit/Delete buttons instead of rendering\n // ones that 404. `can` is absent for types with no row rule, so fall back to type-level.\n // The override (not an intersection) is safe because the server computes `can` as the\n // intersection of type-level rights and the row rule (#243) — it never claims more than\n // `permissions` does.\n const can = item.can;\n // The built-in actions honour `disabledActions` too (#460, D13): the server's\n // OnDisableActionsAsync decides, and refuses a disabled Edit/Save/Delete with 403 at submit,\n // so offering the button would only lead to that refusal. Case-insensitive, like the\n // custom-action filter below.\n const withheld = new Set((item.disabledActions ?? []).map(name => name.toLowerCase()));\n // A row opened from the recycle bin (#460): Edit and Delete judge live rows only — the\n // SoftDelete policy hides a deleted row from both — so they would 404. Restore/Purge come\n // from the soft-delete entry point's detail action instead.\n const deletedView = this.isDeletedView();\n this.permissions.set(permissions);\n // Edit and Delete come from the composed catalogue (#467, D8): listed only when the caller\n // holds Edit/T or Delete/T, shown here when their showedOn includes the detail side (an app\n // removes one with \"Edit\": null), and the row's `can` must allow it too. The selection rule\n // does not apply: the page acts on the one object it shows.\n const edit = builtInAction(actions, 'Edit');\n const del = builtInAction(actions, 'Delete');\n this.editAction.set(edit && isShowedOnDetail(edit.showedOn) && !deletedView\n && (can ? can.edit : permissions.canEdit) && !withheld.has('edit') && !withheld.has('save') ? edit : null);\n this.deleteAction.set(del && isShowedOnDetail(del.showedOn) && !deletedView\n && (can ? can.delete : permissions.canDelete) && !withheld.has('delete') ? del : null);\n // Custom actions only: the built-ins are rendered above as this page's Edit and Delete.\n this.customActions.set(deletedView ? [] : filterDetailActions(actions));\n }\n } catch (e) {\n const error = e as HttpErrorResponse;\n this.errorMessage.set(error.error?.error || error.message || 'An unexpected error occurred');\n }\n }\n\n /**\n * Drawn from the loaded object's own `showedOn`, falling back to the model's (#264, G-Q3): an action sets\n * it per object in `OnLoadAsync` — a stolen car's police report number is shown, the model says `None`.\n */\n visibleAttributes = computed(() => {\n const loaded = new Map((this.item()?.attributes ?? []).map(a => [a.name, a] as const));\n return this.entityType()?.attributes\n .filter(a => hasShowedOnFlag(loaded.get(a.name)?.showedOn ?? a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order) || [];\n });\n\n private static readonly DEFAULT_TAB: AttributeTab = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };\n\n ungroupedAttributes = computed(() => {\n const attrs = this.visibleAttributes();\n const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));\n return attrs.filter(a => !a.group || !groupIds.has(a.group));\n });\n\n resolvedTabs = computed((): AttributeTab[] => {\n const et = this.entityType();\n const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];\n const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;\n const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);\n\n if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {\n return [SparkPoDetailComponent.DEFAULT_TAB, ...definedTabs];\n }\n return definedTabs;\n });\n\n groupsForTab(tab: AttributeTab): AttributeGroup[] {\n const groups = this.entityType()?.groups || [];\n if (tab.id === '__default__') {\n return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);\n }\n return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);\n }\n\n attrsForGroup(group: AttributeGroup): EntityAttributeDefinition[] {\n return this.visibleAttributes().filter(a => a.group === group.id);\n }\n\n /** Whether the attribute's renderer asks for the whole row, without a label (`fullWidth`). */\n isFullWidthRenderer(attr: EntityAttributeDefinition): boolean {\n if (!attr.renderer) return false;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.fullWidth === true;\n }\n\n getDetailRendererComponent(attr: EntityAttributeDefinition): Type<any> | null {\n if (!attr.renderer) return null;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.detailComponent ?? null;\n }\n\n /** Inputs of an AsDetail row renderer (`rowComponent`): the row and the object that owns it. */\n getRowRendererInputs(renderer: SparkResolvedRowRenderer, row: Record<string, any>, item: PersistentObject): Record<string, any> {\n return rowRendererInputs(renderer, row, item?.id);\n }\n\n getDetailRendererInputs(component: Type<any>, attr: EntityAttributeDefinition, item: PersistentObject): Record<string, any> {\n const itemAttr = item.attributes.find(a => a.name === attr.name);\n const formData: Record<string, any> = {};\n for (const a of item.attributes) {\n formData[a.name] = rendererValue(a);\n }\n return withDeclaredInputs(component, {\n value: rendererValue(itemAttr),\n attribute: attr,\n options: attr.rendererOptions,\n formData,\n item,\n });\n }\n\n /** Column renderer for a cell of an AsDetail sub-table (so embedded rows honor `col.renderer` too). */\n getAsDetailCellRendererComponent(col: EntityAttributeDefinition): Type<any> | null {\n if (!col.renderer) return null;\n return this.rendererRegistry.find(r => r.name === col.renderer)?.columnComponent ?? null;\n }\n\n getAsDetailCellRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any> {\n return withDeclaredInputs(component, {\n value: row[col.name],\n attribute: col,\n options: col.rendererOptions,\n item: row,\n });\n }\n\n private async loadLookupReferenceOptions(): Promise<void> {\n const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);\n if (lookupAttrs.length === 0) return;\n\n const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType!))];\n // A lookup that fails to load only costs its labels: the attribute then shows its raw value\n // (attributeValue's fallback) instead of the whole page erroring over a secondary request (#453).\n const entries = await Promise.all(\n lookupNames.map(async name => {\n const result = await this.sparkService.getLookupReference(name).catch(() => null);\n return [name, result] as const;\n })\n );\n this.lookupReferenceOptions.set(entries.reduce(\n (acc, [k, v]) => v ? { ...acc, [k]: v } : acc,\n {} as Record<string, LookupReference>));\n }\n\n private async loadAsDetailTypes(): Promise<void> {\n const asDetailAttrs = this.visibleAttributes().filter(a => a.dataType === 'AsDetail' && a.isArray && a.asDetailType);\n if (asDetailAttrs.length === 0) return;\n\n const types = this.allEntityTypes();\n const newAsDetailTypes: Record<string, EntityType> = {};\n // Same fallback as the form: a row type is usually absent from the Query-gated catalogue, and\n // the server now ships its definition alongside the parent (#385).\n const embedded = this.entityType()?.detailTypes ?? [];\n\n for (const attr of asDetailAttrs) {\n const asDetailType =\n types.find(t => t.clrType === attr.asDetailType)\n ?? embedded.find(t => t.clrType === attr.asDetailType);\n if (asDetailType) {\n newAsDetailTypes[attr.name] = asDetailType;\n const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);\n if (refCols.length > 0) {\n const refEntries = await Promise.all(\n refCols.map(async col => {\n const result = await this.sparkService.executeQueryByName(col.query!, {\n parentId: this.id,\n parentType: this.type,\n });\n return [col.name, result.items] as const;\n })\n );\n this.asDetailReferenceOptions.update(prev => ({\n ...prev,\n [attr.name]: refEntries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {} as Record<string, QueryResultItem[]>)\n }));\n }\n }\n }\n this.asDetailTypes.set(newAsDetailTypes);\n }\n\n /**\n * The bootstrap classes for a custom action's button. Outline-primary stays the default, so\n * every existing action looks exactly as it did; a server-declared variant opts one action into\n * a louder style. Restricted to a known set rather than interpolated, because the value arrives\n * from a JSON file and must not be able to put arbitrary classes on the button.\n */\n /** Edit's and Delete's button classes: the catalogue's variant, else the look they always had. */\n protected builtInActionClass(action: CustomActionDefinition, fallback: 'primary' | 'danger'): string {\n const variant = action.variant?.toLowerCase();\n return ['danger', 'warning', 'primary', 'secondary', 'success'].includes(variant ?? '')\n ? `btn btn-${variant}`\n : `btn btn-${fallback}`;\n }\n\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /**\n * The action currently running, or null. Drives the disabled state on every custom-action\n * button.\n *\n * A custom action is not necessarily quick: Coverage's Resync makes paged GitHub App calls for\n * every account the caller manages, inside the request. Without this the button stayed live and\n * looked inert for several seconds, and a second click queued a second full run -- which is how\n * a slow action and a broken one became indistinguishable.\n */\n runningAction = signal<string | null>(null);\n\n /**\n * Why an action cannot run in this browser, translated, for its tooltip: the client method it\n * requires (`requiresClient`) is not registered or not supported here (Q9). Null otherwise.\n */\n protected customActionUnavailableReason(action: CustomActionDefinition): string | null {\n const key = this.clientMethods.unavailableReason(action.requiresClient);\n return key === null ? null : this.lang.t(key);\n }\n\n async onCustomAction(action: CustomActionDefinition): Promise<void> {\n // Guard re-entry as well as disabling the button: the template is not the only caller, and a\n // host component driving this method directly would otherwise bypass the check.\n if (this.runningAction()) return;\n if (this.clientMethods.unavailableReason(action.requiresClient) !== null) return;\n\n const message = confirmationText(action, 1);\n if (message && !confirm(message)) return;\n\n this.runningAction.set(action.name);\n try {\n await this.sparkService.executeCustomAction(this.type, action.name, this.item() || undefined);\n this.customActionExecuted.emit({ action, item: this.item()! });\n if (action.refreshOnCompleted) {\n // The sub-query grids below do not depend on item(), so re-fetching the PO left them\n // showing pre-action rows -- the action appeared to have done nothing to the very\n // lists it changed.\n //\n // Before the await, not after (#319): a server-issued refreshQuery was already dispatched\n // inside executeCustomAction, and bumps in one synchronous run coalesce into one grid\n // fetch. Bumping after the re-fetch's network round trip landed in a second\n // change-detection flush, and the grid fetched twice for one click.\n for (const subQuery of this.subQueries()) {\n this.queryRefresh.request(subQuery.query);\n }\n\n const item = await this.loadItem();\n this.item.set(item);\n }\n } catch (e) {\n const err = e as HttpErrorResponse;\n this.errorMessage.set(err.error?.error || err.message || 'Action failed');\n } finally {\n // finally, not after the try: an action that fails must not leave every button on the page\n // permanently disabled.\n this.runningAction.set(null);\n }\n }\n\n onEdit(): void {\n this.edited.emit();\n this.router.navigate(['/po', this.type, this.id, 'edit']);\n }\n\n async onDelete(): Promise<void> {\n // The version on screen is the one deleted (#467, D14): a row changed since is a 409.\n const etag = this.item()?.etag;\n if (!etag) return;\n const message = confirmationText(this.deleteAction(), 1);\n if (!message || confirm(message)) {\n await this.sparkService.delete(this.type, this.id, etag);\n this.deleted.emit();\n // Back to the list the row was opened from (else the type's list), not the start page.\n await this.returnNavigation.returnToList(this.entityType()?.name);\n }\n }\n\n onBack(): void {\n window.history.back();\n }\n}\n","<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n <!-- Edit and Delete are the composed catalogue's (#467, D8): label, icon and variant included. -->\n @if (editAction(); as edit) {\n <button *bsPriorityNavItem=\"2\" [class]=\"builtInActionClass(edit, 'primary')\" (click)=\"onEdit()\">\n @if (edit.icon) { <spark-icon [name]=\"edit.icon\" /> } {{ edit.label | resolveTranslation }}\n </button>\n }\n @if (deleteAction(); as del) {\n <button *bsPriorityNavItem=\"3\" [class]=\"builtInActionClass(del, 'danger')\" (click)=\"onDelete()\">\n @if (del.icon) { <spark-icon [name]=\"del.icon\" /> } {{ del.label | resolveTranslation }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n @let unavailable = customActionUnavailableReason(action);\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [attr.data-action]=\"action.name\"\n [disabled]=\"runningAction() !== null || unavailable !== null\" [attr.title]=\"unavailable\" (click)=\"onCustomAction(action)\">\n {{ action.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <!-- A full-width renderer (the core 'paragraph') is body text: no label, the whole row. -->\n @let fullWidth = isFullWidthRenderer(attr);\n @if (!fullWidth) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n }\n <dd [sm]=\"fullWidth ? 12 : 9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-po-detail.mjs","names":[],"sources":["../../po-detail/src/spark-po-detail.component.ts","../../po-detail/src/spark-po-detail.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, DestroyRef, computed, effect, inject, input, output, signal, TemplateRef, Type, untracked } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';\nimport { ActivatedRoute, Router, RouterModule } from '@angular/router';\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 { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsTableComponent } from '@mintplayer/ng-bootstrap/table';\nimport { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from '@mintplayer/ng-bootstrap/tab-control';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkService, SparkLanguageService, SparkLiveService, SparkReturnNavigationService, describeSparkError } from '@mintplayer/ng-spark/services';\nimport { SparkAttributePatch, SparkAttributeRefreshService, SparkClientMethodRegistry, SparkQueryRefreshService } from '@mintplayer/ng-spark/client-operations';\nimport {\n TranslateKeyPipe,\n ResolveTranslationPipe,\n AttributeValuePipe,\n OrDashPipe,\n RawAttributeValuePipe,\n AsDetailColumnsPipe,\n AsDetailCellValuePipe,\n ArrayValuePipe,\n ReferenceLinkRoutePipe,\n ReferenceChipsPipe,\n ParsedDatePipe,\n AsDetailRowRendererPipe,\n} from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkAttributeDescriptionComponent } from '@mintplayer/ng-spark/attribute-description';\nimport { SparkQueryCardComponent, SparkGridCellComponent } from '@mintplayer/ng-spark/grid';\nimport { SPARK_ATTRIBUTE_RENDERERS, SparkResolvedRowRenderer, rendererValue, rowRendererInputs, withDeclaredInputs } from '@mintplayer/ng-spark/renderers';\nimport {\n AttributeGroup,\n AttributeTab,\n CustomActionDefinition,\n EntityAttributeDefinition,\n EntityType,\n LookupReference,\n PersistentObject,\n QueryResultItem,\n ShowedOn,\n hasShowedOnFlag,\n EntityPermissions,\n SparkDeletedFilter,\n builtInAction,\n confirmationText,\n filterDetailActions,\n isShowedOnDetail,\n subQueriesOf,\n} from '@mintplayer/ng-spark/models';\nimport {\n SPARK_DETAIL_ACTIONS,\n SPARK_DETAIL_PANELS,\n SparkDetailContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\nimport { Subscription, combineLatest } from 'rxjs';\n\n@Component({\n selector: 'spark-po-detail',\n imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, RouterModule, BsAlertComponent, BsBadgeComponent,BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsTableComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryCardComponent, SparkGridCellComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, OrDashPipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailRowRendererPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe, ReferenceChipsPipe, ParsedDatePipe, SparkAttributeDescriptionComponent],\n templateUrl: './spark-po-detail.component.html',\n styleUrl: './spark-po-detail.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SparkPoDetailComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly returnNavigation = inject(SparkReturnNavigationService);\n private readonly queryRefresh = inject(SparkQueryRefreshService);\n private readonly attributeRefresh = inject(SparkAttributeRefreshService);\n private readonly live = inject(SparkLiveService);\n protected readonly lang = inject(SparkLanguageService);\n private readonly clientMethods = inject(SparkClientMethodRegistry);\n protected readonly rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);\n\n /**\n * Add-on panels and action-bar buttons (#460, `SPARK_DETAIL_PANELS` / `SPARK_DETAIL_ACTIONS`).\n * Multi-provided, so History, SoftDelete and Moderation can each add theirs to the page\n * `sparkRoutes()` routes to, which is created by the router and takes no templates.\n */\n protected readonly detailPanels = orderSparkExtensions(inject(SPARK_DETAIL_PANELS, { optional: true }), p => p.order);\n protected readonly detailActions = orderSparkExtensions(inject(SPARK_DETAIL_ACTIONS, { optional: true }), a => a.priority ?? 60);\n\n showCustomActions = input(true);\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n extraContentTemplate = input<TemplateRef<{ $implicit: PersistentObject; entityType: EntityType }> | null>(null);\n\n /**\n * Header slots forwarded to every sub-query card on this page.\n *\n * A structural directive cannot cross a component boundary, and this component is created by\n * the router — in a default app there is no `<spark-po-detail>` tag to project content into.\n * So an app that wants slotted chrome on its sub-queries substitutes its own route component\n * via `SparkRouteConfig.poDetail` and forwards the templates through here. Forwarding a\n * `TemplateRef` as an input is already this component's idiom; see the two above.\n *\n * Each slot may target one query by alias, so a single forwarded set serves a page rendering\n * several sub-queries.\n */\n queryIconTemplate = input<TemplateRef<any> | null>(null);\n queryCaptionTemplate = input<TemplateRef<any> | null>(null);\n queryActionsTemplate = input<TemplateRef<any> | null>(null);\n\n edited = output<void>();\n deleted = output<void>();\n /** After an action succeeded; `result` is what it handed to `CustomActionArgs.SetResult` (a download url, a new id). */\n customActionExecuted = output<{ action: CustomActionDefinition; item: PersistentObject; result?: unknown }>();\n\n colors = Color;\n /** The page could not be loaded: shown instead of the page. */\n errorMessage = signal<string | null>(null);\n /** A custom action failed or was refused: shown above the page, which stays (it used to replace the page). */\n actionError = signal<string | null>(null);\n entityType = signal<EntityType | null>(null);\n\n /** The sub-queries rendered as cards below the form, in object form (#460, D17). */\n protected readonly subQueries = computed(() => subQueriesOf(this.entityType()));\n allEntityTypes = signal<EntityType[]>([]);\n item = signal<PersistentObject | null>(null);\n lookupReferenceOptions = signal<Record<string, LookupReference>>({});\n asDetailTypes = signal<Record<string, EntityType>>({});\n asDetailReferenceOptions = signal<Record<string, Record<string, QueryResultItem[]>>>({});\n type = '';\n id = '';\n /** The catalogue's Edit (#467, D8) when this page offers it: listed for the caller, on the detail side, and allowed on this row. */\n editAction = signal<CustomActionDefinition | null>(null);\n /** The catalogue's Delete, on the same terms as {@link editAction}. */\n deleteAction = signal<CustomActionDefinition | null>(null);\n canEdit = computed(() => this.editAction() !== null);\n canDelete = computed(() => this.deleteAction() !== null);\n customActions = signal<CustomActionDefinition[]>([]);\n /** Type-level rights, kept for the add-on context; null until loaded. */\n permissions = signal<EntityPermissions | null>(null);\n\n /**\n * The route's `?deleted=` mode (#460, T2): `only` when the row was opened from the recycle bin.\n * Sent with every load of this object; the server honours it only for `ViewDeleted` holders, so\n * for anyone else a deleted row stays a 404.\n */\n deletedMode = signal<SparkDeletedFilter | null>(null);\n\n /** True when the page shows a soft-deleted row: nothing but Restore/Purge applies to it. */\n protected readonly isDeletedView = computed(() => this.deletedMode() === 'only');\n\n /** What every add-on panel and action receives; rebuilt whenever the object or rights change. */\n protected readonly detailContext = computed((): SparkDetailContext | null => {\n const item = this.item();\n const entityType = this.entityType();\n if (!item || !entityType) return null;\n return {\n type: this.type,\n id: this.id,\n item,\n entityType,\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n reload: () => this.load(),\n };\n });\n\n /**\n * The actions actually offered for the object on screen.\n *\n * The catalogue at `/spark/actions/{objectTypeId}` is per TYPE -- the server is never told which\n * row is open -- so an action that applies to only some rows cannot be filtered there. The\n * entity's actions hook decides while it has the entity in hand and withholds what does not\n * apply, and the object arrives carrying that answer.\n *\n * Since #460 (D13) the server asks the same hook again when the action is submitted and refuses a\n * disabled one with 403, so hiding it here matches what the server will do. What this prevents is\n * offering a destructive action where it cannot possibly apply -- Coverage showed an irreversible\n * red \"Delete data\" button on every repository page, healthy ones included, and only admitted it\n * would refuse after the confirmation prompt.\n */\n visibleCustomActions = computed(() => {\n const withheld = this.item()?.disabledActions;\n if (!withheld?.length) return this.customActions();\n\n const lowered = new Set(withheld.map(name => name.toLowerCase()));\n return this.customActions().filter(action => !lowered.has(action.name.toLowerCase()));\n });\n\n constructor() {\n // The query map too: Restore navigates from `?deleted=only` to the same path without it, and the\n // object must then be re-read as a live row.\n combineLatest([this.route.paramMap, this.route.queryParamMap])\n .pipe(takeUntilDestroyed())\n .subscribe(([params, query]) => this.onParamsChange(params, query.get('deleted')));\n\n // Server-issued `refreshAttribute` patches for the object this page is showing.\n //\n // Tracks the token and the entity type, never `item()`: writing the signal this effect reads\n // would re-run it forever, so the read-modify-write of the object happens untracked.\n effect(() => {\n const objectTypeId = this.entityType()?.id;\n if (!this.attributeRefresh.tokenFor(objectTypeId, this.id)) return;\n\n const patches = this.attributeRefresh.patchesFor(objectTypeId, this.id);\n untracked(() => this.applyAttributePatches(patches));\n });\n\n inject(DestroyRef).onDestroy(() => this.stopFollowing());\n }\n\n // --- live (X4) ---------------------------------------------------------------------------------\n\n /** The live subscription of the object on screen, while its type is live; keyed so a reload keeps it. */\n private liveSubscription: Subscription | null = null;\n private liveKey: string | null = null;\n /** The catalogue and rights of the last load, so a pushed object re-derives its Edit/Delete/actions. */\n private loadedActions: CustomActionDefinition[] | null = null;\n\n /**\n * Follows the object when its type is live: every push replaces the shown object and re-derives Edit,\n * Delete and the custom actions from its own `can`/`disabledActions`, as a load does. `gone` (deleted, or\n * no longer readable — one answer) shows the unavailable state. A subscription `error` stops following\n * and keeps the loaded object: the page still shows what the HTTP load returned.\n */\n private follow(et: EntityType | null): void {\n if (!et?.live || !this.id) {\n this.stopFollowing();\n return;\n }\n const deleted = this.deletedMode();\n const key = JSON.stringify([et.id, this.id, deleted]);\n if (key === this.liveKey && this.liveSubscription) return;\n this.stopFollowing();\n this.liveKey = key;\n this.liveSubscription = this.live.watchPersistentObject(et.id, this.id, { deleted }).subscribe(event => {\n if (event.type === 'po') {\n this.item.set(event.po);\n this.deriveActions(event.po);\n } else if (event.type === 'gone') {\n this.liveKey = null;\n this.liveSubscription = null;\n this.item.set(null);\n this.errorMessage.set(this.translateOr('spark.po.unavailable', 'This item was deleted or is no longer available.'));\n } else {\n this.liveKey = null;\n this.liveSubscription = null;\n console.warn(`[spark] live updates stopped for ${et.name} ${this.id}: ${event.code}: ${event.message}`);\n }\n });\n }\n\n private stopFollowing(): void {\n this.liveSubscription?.unsubscribe();\n this.liveSubscription = null;\n this.liveKey = null;\n }\n\n /** The translation, or `fallback` while the key has none (`t` echoes a missing key). */\n private translateOr(key: string, fallback: string): string {\n const text = this.lang.t(key);\n return text && text !== key ? text : fallback;\n }\n\n /**\n * Applies patched attribute values in place, without a re-fetch.\n *\n * The operation carries the value the server computed after its own write, so this is both\n * cheaper and more correct than re-reading: a re-read goes through a RavenDB index that may\n * still be stale from the very write that produced the patch, and would show the old value.\n */\n private applyAttributePatches(patches: Record<string, SparkAttributePatch>): void {\n const current = this.item();\n if (!current) return;\n\n let changed = false;\n const attributes = current.attributes.map(attribute => {\n if (!Object.prototype.hasOwnProperty.call(patches, attribute.name)) return attribute;\n\n const patch = patches[attribute.name];\n const next = { ...attribute };\n let patched = false;\n\n // Each field is applied on its own, because they are not interchangeable: an AsDetail\n // attribute carries its rows in object/objects and leaves value null, so applying only\n // value could never refresh a detail grid.\n //\n // Compared by identity in every case. A scalar skips when unchanged, and a null skips\n // against a null -- which matters because the server writes object/objects as null on a\n // scalar patch, and treating that as a change would repaint on every patch. A rewritten\n // collection is a new array and so never matches, which is the behaviour wanted: comparing\n // rows deeply would cost more than the repaint it saves.\n if (patch.value !== undefined && next.value !== patch.value) {\n next.value = patch.value;\n patched = true;\n }\n // Only for the attributes that actually use them. The server writes object/objects as\n // null on every scalar patch, so applying them unconditionally would mark a scalar\n // attribute changed on each patch and repaint for nothing.\n if (attribute.dataType === 'AsDetail') {\n if (patch.object !== undefined && next.object !== patch.object) {\n next.object = patch.object as PersistentObject | null;\n patched = true;\n }\n if (patch.objects !== undefined && next.objects !== patch.objects) {\n next.objects = patch.objects as PersistentObject[] | null;\n patched = true;\n }\n }\n\n if (!patched) return attribute;\n changed = true;\n return next;\n });\n\n if (changed) this.item.set({ ...current, attributes });\n }\n\n private async onParamsChange(params: any, deleted: string | null = null): Promise<void> {\n this.type = params.get('type') || '';\n this.id = params.get('id') || '';\n // The previous object's pushes must not land on this one, even when this load fails.\n this.stopFollowing();\n const mode = parseSparkDeletedParam(deleted);\n // `exclude` is the default on the wire; keep it out of the request like an absent parameter.\n this.deletedMode.set(mode === 'exclude' ? null : mode);\n await this.load();\n }\n\n /** The object as the page's current mode sees it. Only a set mode is sent. */\n private loadItem(): Promise<PersistentObject> {\n const deleted = this.deletedMode();\n return deleted\n ? this.sparkService.get(this.type, this.id, { deleted })\n : this.sparkService.get(this.type, this.id);\n }\n\n /** (Re)loads the object, its type, rights and actions. Also what add-ons call via `context.reload()`. */\n async load(): Promise<void> {\n this.errorMessage.set(null);\n try {\n const [entityTypes, item] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.loadItem()\n ]);\n\n const et = entityTypes.find(t => t.id === this.type || t.alias === this.type) || null;\n // Rights and actions are fetched BEFORE the object is published. The component is reused when\n // the route moves to another object, so publishing the new object first would show it under\n // the previous object's Edit/Delete/custom actions until these arrive: a Delete the caller\n // holds on the page they came from flashed on one where they hold none.\n const [permissions, actions] = et\n ? await Promise.all([\n this.sparkService.getPermissions(et.id),\n this.sparkService.getCustomActions(et.id)\n ])\n : [null, null];\n\n this.allEntityTypes.set(entityTypes);\n this.entityType.set(et);\n this.item.set(item);\n this.loadLookupReferenceOptions();\n this.loadAsDetailTypes();\n\n this.permissions.set(et && permissions && actions ? permissions : null);\n this.loadedActions = et && permissions && actions ? actions : null;\n this.deriveActions(item);\n this.follow(et);\n } catch (e) {\n const error = e as HttpErrorResponse;\n this.errorMessage.set(error.error?.error || error.message || 'An unexpected error occurred');\n }\n }\n\n /**\n * Edit, Delete and the custom actions for `item`, from the last load's catalogue and rights: on a load,\n * and again on every live push, since `can` and `disabledActions` travel with the object.\n */\n private deriveActions(item: PersistentObject): void {\n const permissions = this.permissions();\n const actions = this.loadedActions;\n if (!permissions || !actions) {\n this.editAction.set(null);\n this.deleteAction.set(null);\n this.customActions.set([]);\n } else {\n // Prefer the per-row affordances (#236 G5) when the server attached them — a row the\n // caller may read but not mutate then hides its Edit/Delete buttons instead of rendering\n // ones that 404. `can` is absent for types with no row rule, so fall back to type-level.\n // The override (not an intersection) is safe because the server computes `can` as the\n // intersection of type-level rights and the row rule (#243) — it never claims more than\n // `permissions` does.\n const can = item.can;\n // The built-in actions honour `disabledActions` too (#460, D13): the server's\n // OnDisableActionsAsync decides, and refuses a disabled Edit/Save/Delete with 403 at submit,\n // so offering the button would only lead to that refusal. Case-insensitive, like the\n // custom-action filter below.\n const withheld = new Set((item.disabledActions ?? []).map(name => name.toLowerCase()));\n // A row opened from the recycle bin (#460): Edit and Delete judge live rows only — the\n // SoftDelete policy hides a deleted row from both — so they would 404. Restore/Purge come\n // from the soft-delete entry point's detail action instead.\n const deletedView = this.isDeletedView();\n // Edit and Delete come from the composed catalogue (#467, D8): listed only when the caller\n // holds Edit/T or Delete/T, shown here when their showedOn includes the detail side (an app\n // removes one with \"Edit\": null), and the row's `can` must allow it too. The selection rule\n // does not apply: the page acts on the one object it shows.\n const edit = builtInAction(actions, 'Edit');\n const del = builtInAction(actions, 'Delete');\n this.editAction.set(edit && isShowedOnDetail(edit.showedOn) && !deletedView\n && (can ? can.edit : permissions.canEdit) && !withheld.has('edit') && !withheld.has('save') ? edit : null);\n this.deleteAction.set(del && isShowedOnDetail(del.showedOn) && !deletedView\n && (can ? can.delete : permissions.canDelete) && !withheld.has('delete') ? del : null);\n // Custom actions only: the built-ins are rendered above as this page's Edit and Delete.\n this.customActions.set(deletedView ? [] : filterDetailActions(actions));\n }\n }\n\n /**\n * Drawn from the loaded object's own `showedOn`, falling back to the model's (#264, G-Q3): an action sets\n * it per object in `OnLoadAsync` — a stolen car's police report number is shown, the model says `None`.\n */\n visibleAttributes = computed(() => {\n const loaded = new Map((this.item()?.attributes ?? []).map(a => [a.name, a] as const));\n return this.entityType()?.attributes\n .filter(a => hasShowedOnFlag(loaded.get(a.name)?.showedOn ?? a.showedOn, ShowedOn.PersistentObject))\n .sort((a, b) => a.order - b.order) || [];\n });\n\n private static readonly DEFAULT_TAB: AttributeTab = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };\n\n ungroupedAttributes = computed(() => {\n const attrs = this.visibleAttributes();\n const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));\n return attrs.filter(a => !a.group || !groupIds.has(a.group));\n });\n\n resolvedTabs = computed((): AttributeTab[] => {\n const et = this.entityType();\n const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];\n const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;\n const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);\n\n if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {\n return [SparkPoDetailComponent.DEFAULT_TAB, ...definedTabs];\n }\n return definedTabs;\n });\n\n groupsForTab(tab: AttributeTab): AttributeGroup[] {\n const groups = this.entityType()?.groups || [];\n if (tab.id === '__default__') {\n return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);\n }\n return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);\n }\n\n attrsForGroup(group: AttributeGroup): EntityAttributeDefinition[] {\n return this.visibleAttributes().filter(a => a.group === group.id);\n }\n\n /** Whether the attribute's renderer asks for the whole row, without a label (`fullWidth`). */\n isFullWidthRenderer(attr: EntityAttributeDefinition): boolean {\n if (!attr.renderer) return false;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.fullWidth === true;\n }\n\n getDetailRendererComponent(attr: EntityAttributeDefinition): Type<any> | null {\n if (!attr.renderer) return null;\n return this.rendererRegistry.find(r => r.name === attr.renderer)?.detailComponent ?? null;\n }\n\n /** Inputs of an AsDetail row renderer (`rowComponent`): the row and the object that owns it. */\n getRowRendererInputs(renderer: SparkResolvedRowRenderer, row: Record<string, any>, item: PersistentObject): Record<string, any> {\n return rowRendererInputs(renderer, row, item?.id);\n }\n\n getDetailRendererInputs(component: Type<any>, attr: EntityAttributeDefinition, item: PersistentObject): Record<string, any> {\n const itemAttr = item.attributes.find(a => a.name === attr.name);\n const formData: Record<string, any> = {};\n for (const a of item.attributes) {\n formData[a.name] = rendererValue(a);\n }\n return withDeclaredInputs(component, {\n value: rendererValue(itemAttr),\n attribute: attr,\n options: attr.rendererOptions,\n formData,\n item,\n });\n }\n\n /** Column renderer for a cell of an AsDetail sub-table (so embedded rows honor `col.renderer` too). */\n getAsDetailCellRendererComponent(col: EntityAttributeDefinition): Type<any> | null {\n if (!col.renderer) return null;\n return this.rendererRegistry.find(r => r.name === col.renderer)?.columnComponent ?? null;\n }\n\n getAsDetailCellRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any> {\n return withDeclaredInputs(component, {\n value: row[col.name],\n attribute: col,\n options: col.rendererOptions,\n item: row,\n });\n }\n\n private async loadLookupReferenceOptions(): Promise<void> {\n const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);\n if (lookupAttrs.length === 0) return;\n\n const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType!))];\n // A lookup that fails to load only costs its labels: the attribute then shows its raw value\n // (attributeValue's fallback) instead of the whole page erroring over a secondary request (#453).\n const entries = await Promise.all(\n lookupNames.map(async name => {\n const result = await this.sparkService.getLookupReference(name).catch(() => null);\n return [name, result] as const;\n })\n );\n this.lookupReferenceOptions.set(entries.reduce(\n (acc, [k, v]) => v ? { ...acc, [k]: v } : acc,\n {} as Record<string, LookupReference>));\n }\n\n private async loadAsDetailTypes(): Promise<void> {\n const asDetailAttrs = this.visibleAttributes().filter(a => a.dataType === 'AsDetail' && a.isArray && a.asDetailType);\n if (asDetailAttrs.length === 0) return;\n\n const types = this.allEntityTypes();\n const newAsDetailTypes: Record<string, EntityType> = {};\n // Same fallback as the form: a row type is usually absent from the Query-gated catalogue, and\n // the server now ships its definition alongside the parent (#385).\n const embedded = this.entityType()?.detailTypes ?? [];\n\n for (const attr of asDetailAttrs) {\n const asDetailType =\n types.find(t => t.clrType === attr.asDetailType)\n ?? embedded.find(t => t.clrType === attr.asDetailType);\n if (asDetailType) {\n newAsDetailTypes[attr.name] = asDetailType;\n const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);\n if (refCols.length > 0) {\n const refEntries = await Promise.all(\n refCols.map(async col => {\n const result = await this.sparkService.executeQueryByName(col.query!, {\n parentId: this.id,\n parentType: this.type,\n });\n return [col.name, result.items] as const;\n })\n );\n this.asDetailReferenceOptions.update(prev => ({\n ...prev,\n [attr.name]: refEntries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {} as Record<string, QueryResultItem[]>)\n }));\n }\n }\n }\n this.asDetailTypes.set(newAsDetailTypes);\n }\n\n /**\n * The bootstrap classes for a custom action's button. Outline-primary stays the default, so\n * every existing action looks exactly as it did; a server-declared variant opts one action into\n * a louder style. Restricted to a known set rather than interpolated, because the value arrives\n * from a JSON file and must not be able to put arbitrary classes on the button.\n */\n /** Edit's and Delete's button classes: the catalogue's variant, else the look they always had. */\n protected builtInActionClass(action: CustomActionDefinition, fallback: 'primary' | 'danger'): string {\n const variant = action.variant?.toLowerCase();\n return ['danger', 'warning', 'primary', 'secondary', 'success'].includes(variant ?? '')\n ? `btn btn-${variant}`\n : `btn btn-${fallback}`;\n }\n\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /**\n * The action currently running, or null. Drives the disabled state on every custom-action\n * button.\n *\n * A custom action is not necessarily quick: Coverage's Resync makes paged GitHub App calls for\n * every account the caller manages, inside the request. Without this the button stayed live and\n * looked inert for several seconds, and a second click queued a second full run -- which is how\n * a slow action and a broken one became indistinguishable.\n */\n runningAction = signal<string | null>(null);\n\n /**\n * Why an action cannot run in this browser, translated, for its tooltip: the client method it\n * requires (`requiresClient`) is not registered or not supported here (Q9). Null otherwise.\n */\n protected customActionUnavailableReason(action: CustomActionDefinition): string | null {\n const key = this.clientMethods.unavailableReason(action.requiresClient);\n return key === null ? null : this.lang.t(key);\n }\n\n async onCustomAction(action: CustomActionDefinition): Promise<void> {\n // Guard re-entry as well as disabling the button: the template is not the only caller, and a\n // host component driving this method directly would otherwise bypass the check.\n if (this.runningAction()) return;\n if (this.clientMethods.unavailableReason(action.requiresClient) !== null) return;\n\n const message = confirmationText(action, 1);\n if (message && !confirm(message)) return;\n\n this.runningAction.set(action.name);\n this.actionError.set(null);\n try {\n const result = await this.sparkService.executeCustomAction(this.type, action.name, this.item() || undefined);\n this.customActionExecuted.emit({ action, item: this.item()!, result });\n if (action.refreshOnCompleted) {\n // The sub-query grids below do not depend on item(), so re-fetching the PO left them\n // showing pre-action rows -- the action appeared to have done nothing to the very\n // lists it changed.\n //\n // Before the await, not after (#319): a server-issued refreshQuery was already dispatched\n // inside executeCustomAction, and bumps in one synchronous run coalesce into one grid\n // fetch. Bumping after the re-fetch's network round trip landed in a second\n // change-detection flush, and the grid fetched twice for one click.\n for (const subQuery of this.subQueries()) {\n this.queryRefresh.request(subQuery.query);\n }\n\n const item = await this.loadItem();\n this.item.set(item);\n }\n } catch (e) {\n // The envelope's reason (and, for a 429, when to try again), never Angular's HTTP status line.\n this.actionError.set(describeSparkError(e, this.lang, (e as HttpErrorResponse)?.message || this.lang.t('common.actionFailed', 'Action failed')));\n } finally {\n // finally, not after the try: an action that fails must not leave every button on the page\n // permanently disabled.\n this.runningAction.set(null);\n }\n }\n\n onEdit(): void {\n this.edited.emit();\n this.router.navigate(['/po', this.type, this.id, 'edit']);\n }\n\n async onDelete(): Promise<void> {\n // The version on screen is the one deleted (#467, D14): a row changed since is a 409.\n const etag = this.item()?.etag;\n if (!etag) return;\n const message = confirmationText(this.deleteAction(), 1);\n if (!message || confirm(message)) {\n await this.sparkService.delete(this.type, this.id, etag);\n this.deleted.emit();\n // Back to the list the row was opened from (else the type's list), not the start page.\n await this.returnNavigation.returnToList(this.entityType()?.name);\n }\n }\n\n onBack(): void {\n window.history.back();\n }\n}\n","<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n @if (actionError(); as actionErr) {\n <bs-alert [type]=\"colors.danger\" class=\"d-block mt-2 spark-action-error\" role=\"alert\">\n <div class=\"d-flex align-items-start gap-2\">\n <span class=\"flex-grow-1\">{{ actionErr }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-link p-0 text-reset text-decoration-none\" [attr.aria-label]=\"lang.t('common.close', 'Close')\" (click)=\"actionError.set(null)\">×</button>\n </div>\n </bs-alert>\n }\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n <!-- Edit and Delete are the composed catalogue's (#467, D8): label, icon and variant included. -->\n @if (editAction(); as edit) {\n <button *bsPriorityNavItem=\"2\" [class]=\"builtInActionClass(edit, 'primary')\" (click)=\"onEdit()\">\n @if (edit.icon) { <spark-icon [name]=\"edit.icon\" /> } {{ edit.label | resolveTranslation }}\n </button>\n }\n @if (deleteAction(); as del) {\n <button *bsPriorityNavItem=\"3\" [class]=\"builtInActionClass(del, 'danger')\" (click)=\"onDelete()\">\n @if (del.icon) { <spark-icon [name]=\"del.icon\" /> } {{ del.label | resolveTranslation }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n @let unavailable = customActionUnavailableReason(action);\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [attr.data-action]=\"action.name\"\n [disabled]=\"runningAction() !== null || unavailable !== null\" [attr.title]=\"unavailable\" (click)=\"onCustomAction(action)\">\n {{ action.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <!-- A full-width renderer (the core 'paragraph') is body text: no label, the whole row. -->\n @let fullWidth = isFullWidthRenderer(attr);\n @if (!fullWidth) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n }\n <dd [sm]=\"fullWidth ? 12 : 9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) | orDash }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { ChangeDetectionStrategy, Component, LOCALE_ID, computed, effect, inject, input, output, signal, untracked, viewChild } from "@angular/core";
|
|
2
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, LOCALE_ID, computed, effect, inject, input, output, signal, untracked, viewChild } from "@angular/core";
|
|
3
3
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
4
4
|
import { CommonModule, NgTemplateOutlet, formatDate } from "@angular/common";
|
|
5
5
|
import { ActivatedRoute, Router } from "@angular/router";
|
|
@@ -8,7 +8,7 @@ import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
|
|
|
8
8
|
import { BsContainerComponent } from "@mintplayer/ng-bootstrap/container";
|
|
9
9
|
import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
|
|
10
10
|
import { SPARK_CONFIG } from "@mintplayer/ng-spark";
|
|
11
|
-
import { SparkLanguageService, SparkService } from "@mintplayer/ng-spark/services";
|
|
11
|
+
import { SparkLanguageService, SparkLiveService, SparkService, canonicalJson } from "@mintplayer/ng-spark/services";
|
|
12
12
|
import { SparkPoFormComponent } from "@mintplayer/ng-spark/po-form";
|
|
13
13
|
import { QueryCellValuePipe, QueryReferenceChipsPipe, ResolveTranslationPipe, TranslateKeyPipe } from "@mintplayer/ng-spark/pipes";
|
|
14
14
|
import { AS_DETAIL_ROW_KEY, ShowedOn, applyOverlay, dictToNestedPo, formValuesEqual, fromDateInputValue, hasShowedOnFlag, isDateDataType, isReservedAsDetailKey, nestedPoToDict, rowMetadata, selfBreadcrumb, toDateInputValue, valueFor, wireDatesEqual } from "@mintplayer/ng-spark/models";
|
|
@@ -335,7 +335,7 @@ var SparkPoConflictDialogComponent = class SparkPoConflictDialogComponent {
|
|
|
335
335
|
}
|
|
336
336
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
337
337
|
minVersion: "12.0.0",
|
|
338
|
-
version: "22.2.
|
|
338
|
+
version: "22.2.2",
|
|
339
339
|
ngImport: i0,
|
|
340
340
|
type: SparkPoConflictDialogComponent,
|
|
341
341
|
deps: [],
|
|
@@ -343,7 +343,7 @@ var SparkPoConflictDialogComponent = class SparkPoConflictDialogComponent {
|
|
|
343
343
|
});
|
|
344
344
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
345
345
|
minVersion: "17.0.0",
|
|
346
|
-
version: "22.2.
|
|
346
|
+
version: "22.2.2",
|
|
347
347
|
type: SparkPoConflictDialogComponent,
|
|
348
348
|
isStandalone: true,
|
|
349
349
|
selector: "spark-po-conflict-dialog",
|
|
@@ -558,7 +558,7 @@ var SparkPoConflictDialogComponent = class SparkPoConflictDialogComponent {
|
|
|
558
558
|
};
|
|
559
559
|
i0.ɵɵngDeclareClassMetadata({
|
|
560
560
|
minVersion: "12.0.0",
|
|
561
|
-
version: "22.2.
|
|
561
|
+
version: "22.2.2",
|
|
562
562
|
ngImport: i0,
|
|
563
563
|
type: SparkPoConflictDialogComponent,
|
|
564
564
|
decorators: [{
|
|
@@ -749,6 +749,7 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
749
749
|
route = inject(ActivatedRoute);
|
|
750
750
|
router = inject(Router);
|
|
751
751
|
sparkService = inject(SparkService);
|
|
752
|
+
live = inject(SparkLiveService);
|
|
752
753
|
language = inject(SparkLanguageService);
|
|
753
754
|
showChangedBy = inject(SPARK_CONFIG, { optional: true })?.conflictDialog?.showChangedBy === true;
|
|
754
755
|
saved = output();
|
|
@@ -808,10 +809,73 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
808
809
|
locale = inject(LOCALE_ID);
|
|
809
810
|
constructor() {
|
|
810
811
|
this.route.paramMap.pipe(takeUntilDestroyed()).subscribe((params) => this.onParamsChange(params));
|
|
812
|
+
inject(DestroyRef).onDestroy(() => this.stopFollowing());
|
|
813
|
+
}
|
|
814
|
+
liveSubscription = null;
|
|
815
|
+
latestPushed = null;
|
|
816
|
+
liveTheirs = signal(null, ...ngDevMode ? [{ debugName: "liveTheirs" }] : /* istanbul ignore next */ []);
|
|
817
|
+
liveChangedText = computed(() => this.translateOr("common.liveChanged", "Changed by someone else while you edit."), ...ngDevMode ? [{ debugName: "liveChangedText" }] : /* istanbul ignore next */ []);
|
|
818
|
+
liveReviewText = computed(() => this.translateOr("common.liveReview", "Review"), ...ngDevMode ? [{ debugName: "liveReviewText" }] : /* istanbul ignore next */ []);
|
|
819
|
+
follow(entityType) {
|
|
820
|
+
this.stopFollowing();
|
|
821
|
+
if (!entityType?.live || !this.id) return;
|
|
822
|
+
this.liveSubscription = this.live.watchPersistentObject(entityType.id, this.id).subscribe((event) => {
|
|
823
|
+
if (event.type === "po") {
|
|
824
|
+
this.latestPushed = event.po;
|
|
825
|
+
this.considerLivePush();
|
|
826
|
+
} else if (event.type === "gone") {
|
|
827
|
+
this.liveSubscription = null;
|
|
828
|
+
this.liveTheirs.set(null);
|
|
829
|
+
this.validationErrors.set([{
|
|
830
|
+
attributeName: "",
|
|
831
|
+
errorMessage: { en: this.language.t("common.deletedByAnotherUser") },
|
|
832
|
+
ruleType: "error"
|
|
833
|
+
}]);
|
|
834
|
+
} else {
|
|
835
|
+
this.liveSubscription = null;
|
|
836
|
+
console.warn(`[spark] live updates stopped for ${entityType.name} ${this.id}: ${event.code}: ${event.message}`);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
stopFollowing() {
|
|
841
|
+
this.liveSubscription?.unsubscribe();
|
|
842
|
+
this.liveSubscription = null;
|
|
843
|
+
this.latestPushed = null;
|
|
844
|
+
this.liveTheirs.set(null);
|
|
845
|
+
}
|
|
846
|
+
considerLivePush() {
|
|
847
|
+
const pushed = this.latestPushed;
|
|
848
|
+
const current = this.item();
|
|
849
|
+
if (!pushed || !current || this.isSaving() || this.pendingConflict()) return;
|
|
850
|
+
if (pushed.etag === current.etag) {
|
|
851
|
+
this.liveTheirs.set(null);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if (!this.isDirty()) {
|
|
855
|
+
this.item.set(pushed);
|
|
856
|
+
this.initFormData();
|
|
857
|
+
this.liveTheirs.set(null);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
this.liveTheirs.set(pushed);
|
|
861
|
+
}
|
|
862
|
+
isDirty() {
|
|
863
|
+
return canonicalJson(this.formData()) !== canonicalJson(this.formDataFrom(this.item()));
|
|
864
|
+
}
|
|
865
|
+
reviewLiveChange() {
|
|
866
|
+
const theirs = this.liveTheirs();
|
|
867
|
+
if (!theirs) return;
|
|
868
|
+
this.liveTheirs.set(null);
|
|
869
|
+
this.mergeWith(theirs);
|
|
870
|
+
}
|
|
871
|
+
translateOr(key, fallback) {
|
|
872
|
+
const text = this.language.t(key);
|
|
873
|
+
return text && text !== key ? text : fallback;
|
|
811
874
|
}
|
|
812
875
|
async onParamsChange(params) {
|
|
813
876
|
this.type = params.get("type") || "";
|
|
814
877
|
this.id = params.get("id") || "";
|
|
878
|
+
this.stopFollowing();
|
|
815
879
|
try {
|
|
816
880
|
const [types, item] = await Promise.all([this.sparkService.getEntityTypes(), this.sparkService.get(this.type, this.id)]);
|
|
817
881
|
const entityType = types.find((t) => t.id === this.type || t.alias === this.type) || null;
|
|
@@ -819,6 +883,7 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
819
883
|
this.allEntityTypes.set(types);
|
|
820
884
|
this.item.set(item);
|
|
821
885
|
this.initFormData();
|
|
886
|
+
this.follow(entityType);
|
|
822
887
|
} catch (e) {
|
|
823
888
|
const error = e;
|
|
824
889
|
this.validationErrors.set([{
|
|
@@ -950,6 +1015,7 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
950
1015
|
errorMessage: { en: error.error?.result?.error || error.error?.error || error.message || "An unexpected error occurred" },
|
|
951
1016
|
ruleType: "error"
|
|
952
1017
|
}]);
|
|
1018
|
+
this.considerLivePush();
|
|
953
1019
|
}
|
|
954
1020
|
}
|
|
955
1021
|
onCancel() {
|
|
@@ -961,14 +1027,18 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
961
1027
|
]);
|
|
962
1028
|
}
|
|
963
1029
|
async resolveConcurrencyConflict() {
|
|
964
|
-
|
|
965
|
-
if (!base) return;
|
|
1030
|
+
if (!this.item()) return;
|
|
966
1031
|
let theirs;
|
|
967
1032
|
try {
|
|
968
1033
|
theirs = await this.sparkService.get(this.type, this.id);
|
|
969
1034
|
} catch {
|
|
970
1035
|
return;
|
|
971
1036
|
}
|
|
1037
|
+
this.mergeWith(theirs);
|
|
1038
|
+
}
|
|
1039
|
+
mergeWith(theirs) {
|
|
1040
|
+
const base = this.item();
|
|
1041
|
+
if (!base) return;
|
|
972
1042
|
this.changedByName.set(null);
|
|
973
1043
|
if (this.showChangedBy) this.lookUpChangedBy(theirs);
|
|
974
1044
|
const attributes = this.getEditableAttributes();
|
|
@@ -1035,9 +1105,11 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
1035
1105
|
const result = mergeThreeWay(pending.base, pending.mine, pending.theirsForm, pending.schema, choices);
|
|
1036
1106
|
this.pendingConflict.set(null);
|
|
1037
1107
|
this.rebase(pending.theirs, result.merged, result.theirChanges, "common.conflictResolved", result.contributionNotices);
|
|
1108
|
+
this.considerLivePush();
|
|
1038
1109
|
}
|
|
1039
1110
|
onConflictCancelled() {
|
|
1040
1111
|
this.pendingConflict.set(null);
|
|
1112
|
+
this.considerLivePush();
|
|
1041
1113
|
}
|
|
1042
1114
|
rebase(theirs, merged, theirChanges, noticeKey, contributionNotices = []) {
|
|
1043
1115
|
this.item.set(theirs);
|
|
@@ -1076,7 +1148,7 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
1076
1148
|
}
|
|
1077
1149
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
1078
1150
|
minVersion: "12.0.0",
|
|
1079
|
-
version: "22.2.
|
|
1151
|
+
version: "22.2.2",
|
|
1080
1152
|
ngImport: i0,
|
|
1081
1153
|
type: SparkPoEditComponent,
|
|
1082
1154
|
deps: [],
|
|
@@ -1084,7 +1156,7 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
1084
1156
|
});
|
|
1085
1157
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
1086
1158
|
minVersion: "17.0.0",
|
|
1087
|
-
version: "22.2.
|
|
1159
|
+
version: "22.2.2",
|
|
1088
1160
|
type: SparkPoEditComponent,
|
|
1089
1161
|
isStandalone: true,
|
|
1090
1162
|
selector: "spark-po-edit",
|
|
@@ -1100,7 +1172,7 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
1100
1172
|
isSignal: true
|
|
1101
1173
|
}],
|
|
1102
1174
|
ngImport: i0,
|
|
1103
|
-
template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage | resolveTranslation }}\n </bs-alert>\n }\n\n @if (conflictNotice(); as notice) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 spark-conflict-notice\">\n {{ notice }}\n </bs-alert>\n }\n\n <spark-po-conflict-dialog\n [conflicts]=\"pendingConflicts()\"\n [referenceLabels]=\"conflictReferenceLabels()\"\n [audit]=\"conflictAudit()\"\n [theyChanged]=\"theyChanged()\"\n (resolved)=\"onConflictResolved($event)\"\n (cancelled)=\"onConflictCancelled()\" />\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.label | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"formEntityType() ?? et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
|
|
1175
|
+
template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage | resolveTranslation }}\n </bs-alert>\n }\n\n @if (conflictNotice(); as notice) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 spark-conflict-notice\">\n {{ notice }}\n </bs-alert>\n }\n\n <!-- Live (X4): someone else saved while the user has unsaved edits. Non-blocking: the form is untouched. -->\n @if (liveTheirs()) {\n <bs-alert [type]=\"colors.warning\" class=\"mb-3 spark-live-changed\">\n {{ liveChangedText() }}\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary ms-2 spark-live-review\" (click)=\"reviewLiveChange()\">\n {{ liveReviewText() }}\n </button>\n </bs-alert>\n }\n\n <spark-po-conflict-dialog\n [conflicts]=\"pendingConflicts()\"\n [referenceLabels]=\"conflictReferenceLabels()\"\n [audit]=\"conflictAudit()\"\n [theyChanged]=\"theyChanged()\"\n (resolved)=\"onConflictResolved($event)\"\n (cancelled)=\"onConflictCancelled()\" />\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.label | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"formEntityType() ?? et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
|
|
1104
1176
|
dependencies: [
|
|
1105
1177
|
{
|
|
1106
1178
|
kind: "ngmodule",
|
|
@@ -1184,7 +1256,7 @@ var SparkPoEditComponent = class SparkPoEditComponent {
|
|
|
1184
1256
|
};
|
|
1185
1257
|
i0.ɵɵngDeclareClassMetadata({
|
|
1186
1258
|
minVersion: "12.0.0",
|
|
1187
|
-
version: "22.2.
|
|
1259
|
+
version: "22.2.2",
|
|
1188
1260
|
ngImport: i0,
|
|
1189
1261
|
type: SparkPoEditComponent,
|
|
1190
1262
|
decorators: [{
|
|
@@ -1202,7 +1274,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1202
1274
|
TranslateKeyPipe
|
|
1203
1275
|
],
|
|
1204
1276
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1205
|
-
template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage | resolveTranslation }}\n </bs-alert>\n }\n\n @if (conflictNotice(); as notice) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 spark-conflict-notice\">\n {{ notice }}\n </bs-alert>\n }\n\n <spark-po-conflict-dialog\n [conflicts]=\"pendingConflicts()\"\n [referenceLabels]=\"conflictReferenceLabels()\"\n [audit]=\"conflictAudit()\"\n [theyChanged]=\"theyChanged()\"\n (resolved)=\"onConflictResolved($event)\"\n (cancelled)=\"onConflictCancelled()\" />\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.label | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"formEntityType() ?? et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n"
|
|
1277
|
+
template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage | resolveTranslation }}\n </bs-alert>\n }\n\n @if (conflictNotice(); as notice) {\n <bs-alert [type]=\"colors.info\" class=\"mb-3 spark-conflict-notice\">\n {{ notice }}\n </bs-alert>\n }\n\n <!-- Live (X4): someone else saved while the user has unsaved edits. Non-blocking: the form is untouched. -->\n @if (liveTheirs()) {\n <bs-alert [type]=\"colors.warning\" class=\"mb-3 spark-live-changed\">\n {{ liveChangedText() }}\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary ms-2 spark-live-review\" (click)=\"reviewLiveChange()\">\n {{ liveReviewText() }}\n </button>\n </bs-alert>\n }\n\n <spark-po-conflict-dialog\n [conflicts]=\"pendingConflicts()\"\n [referenceLabels]=\"conflictReferenceLabels()\"\n [audit]=\"conflictAudit()\"\n [theyChanged]=\"theyChanged()\"\n (resolved)=\"onConflictResolved($event)\"\n (cancelled)=\"onConflictCancelled()\" />\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.label | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"formEntityType() ?? et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n"
|
|
1206
1278
|
}]
|
|
1207
1279
|
}],
|
|
1208
1280
|
ctorParameters: () => [],
|