@mintplayer/ng-spark 0.3.0 → 22.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs +180 -0
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-icon.mjs +9 -6
- package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs +84 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +91 -74
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +55 -22
- package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +97 -92
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +63 -11
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +71 -36
- package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +138 -126
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +172 -18
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-services.mjs +99 -50
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/package.json +12 -7
- package/types/mintplayer-ng-spark-client-operations.d.ts +170 -0
- package/types/mintplayer-ng-spark-icon.d.ts +1 -1
- package/types/mintplayer-ng-spark-models.d.ts +54 -10
- package/types/mintplayer-ng-spark-pipes.d.ts +8 -0
- package/types/mintplayer-ng-spark-po-create.d.ts +2 -1
- package/types/mintplayer-ng-spark-po-detail.d.ts +14 -16
- package/types/mintplayer-ng-spark-po-edit.d.ts +4 -2
- package/types/mintplayer-ng-spark-po-form.d.ts +10 -9
- package/types/mintplayer-ng-spark-query-list.d.ts +26 -16
- package/types/mintplayer-ng-spark-retry-action-modal.d.ts +33 -4
- package/types/mintplayer-ng-spark-services.d.ts +32 -6
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, signal, ChangeDetectionStrategy, Component, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
// Wire types matching MintPlayer.Spark.Abstractions.ClientOperations on the server.
|
|
5
|
+
// Discriminator is the `type` field. Unknown operation types are silently dropped
|
|
6
|
+
// by the dispatcher (forward-compat: new types can land server-side without updating
|
|
7
|
+
// older clients).
|
|
8
|
+
var NotificationKind;
|
|
9
|
+
(function (NotificationKind) {
|
|
10
|
+
NotificationKind[NotificationKind["Info"] = 0] = "Info";
|
|
11
|
+
NotificationKind[NotificationKind["Success"] = 1] = "Success";
|
|
12
|
+
NotificationKind[NotificationKind["Warning"] = 2] = "Warning";
|
|
13
|
+
NotificationKind[NotificationKind["Error"] = 3] = "Error";
|
|
14
|
+
})(NotificationKind || (NotificationKind = {}));
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Multi-provider token. `provideSparkClientOperations()` registers the
|
|
18
|
+
* built-in handlers; apps can add their own with additional `multi: true`
|
|
19
|
+
* providers using this token.
|
|
20
|
+
*/
|
|
21
|
+
const SPARK_CLIENT_OPERATION_HANDLERS = new InjectionToken('SPARK_CLIENT_OPERATION_HANDLERS');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Routes received operations to registered handlers. Unknown operation types
|
|
25
|
+
* (no matching registration) are silently dropped — this is the forward-compat
|
|
26
|
+
* contract that lets new operation types ship server-side without coordinated
|
|
27
|
+
* client updates.
|
|
28
|
+
*
|
|
29
|
+
* Last-registered-wins on duplicate `type` values, matching standard
|
|
30
|
+
* Angular multi-provider override semantics.
|
|
31
|
+
*
|
|
32
|
+
* R2-H19 — security contract for handler authors:
|
|
33
|
+
* The dispatcher treats handler resolution as allow-list-by-type (unknown
|
|
34
|
+
* types drop). It does NOT validate the *content* of each operation. Handlers
|
|
35
|
+
* that act on URL-shaped fields (navigate, redirect, openWindow) MUST run
|
|
36
|
+
* the value through `sanitizeReturnUrl` from `@mintplayer/ng-spark-auth/models`
|
|
37
|
+
* (or an equivalent same-origin check) before acting on it. Otherwise a
|
|
38
|
+
* single attribute-echo XSS or a single mid-channel byte flip on a non-TLS
|
|
39
|
+
* path lets the server drive client navigation to an attacker host. The
|
|
40
|
+
* built-in `notify` handler renders via Angular interpolation (escaped) so
|
|
41
|
+
* it's safe to pass through, but anything more powerful must validate.
|
|
42
|
+
*/
|
|
43
|
+
class SparkClientOperationDispatcher {
|
|
44
|
+
handlerMap;
|
|
45
|
+
constructor() {
|
|
46
|
+
const registrations = inject(SPARK_CLIENT_OPERATION_HANDLERS, { optional: true }) ?? [];
|
|
47
|
+
const map = new Map();
|
|
48
|
+
for (const { type, handler } of registrations) {
|
|
49
|
+
map.set(type, handler);
|
|
50
|
+
}
|
|
51
|
+
this.handlerMap = map;
|
|
52
|
+
}
|
|
53
|
+
dispatch(operations) {
|
|
54
|
+
if (!operations || operations.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
for (const operation of operations) {
|
|
57
|
+
const handler = this.handlerMap.get(operation.type);
|
|
58
|
+
if (handler) {
|
|
59
|
+
handler(operation);
|
|
60
|
+
}
|
|
61
|
+
// Unknown types: silently dropped (forward-compat).
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkClientOperationDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
65
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkClientOperationDispatcher, providedIn: 'root' });
|
|
66
|
+
}
|
|
67
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkClientOperationDispatcher, decorators: [{
|
|
68
|
+
type: Injectable,
|
|
69
|
+
args: [{ providedIn: 'root' }]
|
|
70
|
+
}], ctorParameters: () => [] });
|
|
71
|
+
|
|
72
|
+
const DEFAULT_DURATION_MS = 4000;
|
|
73
|
+
/**
|
|
74
|
+
* Holds the active toasts as a signal. The `<spark-toast-container>` component
|
|
75
|
+
* renders them; the built-in `notify` operation handler pushes new toasts here.
|
|
76
|
+
*
|
|
77
|
+
* Auto-dismissal: each toast schedules its own removal after `durationMs`. Pass
|
|
78
|
+
* `0` to make a toast sticky (manual dismissal only).
|
|
79
|
+
*/
|
|
80
|
+
class SparkNotificationService {
|
|
81
|
+
_toasts = signal([], /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "_toasts" }] : /* istanbul ignore next */ []));
|
|
83
|
+
toasts = this._toasts.asReadonly();
|
|
84
|
+
show(message, kind = NotificationKind.Info, durationMs) {
|
|
85
|
+
const id = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;
|
|
86
|
+
const effectiveDuration = durationMs ?? DEFAULT_DURATION_MS;
|
|
87
|
+
const toast = { id, message, kind, durationMs: effectiveDuration };
|
|
88
|
+
this._toasts.update(toasts => [...toasts, toast]);
|
|
89
|
+
if (effectiveDuration > 0) {
|
|
90
|
+
setTimeout(() => this.dismiss(id), effectiveDuration);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
dismiss(id) {
|
|
94
|
+
this._toasts.update(toasts => toasts.filter(t => t.id !== id));
|
|
95
|
+
}
|
|
96
|
+
clear() {
|
|
97
|
+
this._toasts.set([]);
|
|
98
|
+
}
|
|
99
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkNotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
100
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkNotificationService, providedIn: 'root' });
|
|
101
|
+
}
|
|
102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkNotificationService, decorators: [{
|
|
103
|
+
type: Injectable,
|
|
104
|
+
args: [{ providedIn: 'root' }]
|
|
105
|
+
}] });
|
|
106
|
+
|
|
107
|
+
class SparkToastContainerComponent {
|
|
108
|
+
notifications = inject(SparkNotificationService);
|
|
109
|
+
Kind = NotificationKind;
|
|
110
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
111
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkToastContainerComponent, isStandalone: true, selector: "spark-toast-container", ngImport: i0, template: `
|
|
112
|
+
<div class="spark-toast-container">
|
|
113
|
+
@for (toast of notifications.toasts(); track toast.id) {
|
|
114
|
+
<div
|
|
115
|
+
class="spark-toast"
|
|
116
|
+
[class.spark-toast--info]="toast.kind === Kind.Info"
|
|
117
|
+
[class.spark-toast--success]="toast.kind === Kind.Success"
|
|
118
|
+
[class.spark-toast--warning]="toast.kind === Kind.Warning"
|
|
119
|
+
[class.spark-toast--error]="toast.kind === Kind.Error"
|
|
120
|
+
(click)="notifications.dismiss(toast.id)"
|
|
121
|
+
>
|
|
122
|
+
{{ toast.message }}
|
|
123
|
+
</div>
|
|
124
|
+
}
|
|
125
|
+
</div>
|
|
126
|
+
`, isInline: true, styles: [".spark-toast-container{position:fixed;top:1rem;right:1rem;z-index:9999;display:flex;flex-direction:column;gap:.5rem;pointer-events:none}.spark-toast{padding:.75rem 1rem;border-radius:4px;box-shadow:0 2px 8px #00000026;cursor:pointer;min-width:220px;max-width:400px;color:#fff;font-size:.95rem;pointer-events:auto;animation:spark-toast-in .18s ease-out}.spark-toast--info{background:#0d6efd}.spark-toast--success{background:#198754}.spark-toast--warning{background:#ffc107;color:#000}.spark-toast--error{background:#dc3545}@keyframes spark-toast-in{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
127
|
+
}
|
|
128
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkToastContainerComponent, decorators: [{
|
|
129
|
+
type: Component,
|
|
130
|
+
args: [{ selector: 'spark-toast-container', standalone: true, template: `
|
|
131
|
+
<div class="spark-toast-container">
|
|
132
|
+
@for (toast of notifications.toasts(); track toast.id) {
|
|
133
|
+
<div
|
|
134
|
+
class="spark-toast"
|
|
135
|
+
[class.spark-toast--info]="toast.kind === Kind.Info"
|
|
136
|
+
[class.spark-toast--success]="toast.kind === Kind.Success"
|
|
137
|
+
[class.spark-toast--warning]="toast.kind === Kind.Warning"
|
|
138
|
+
[class.spark-toast--error]="toast.kind === Kind.Error"
|
|
139
|
+
(click)="notifications.dismiss(toast.id)"
|
|
140
|
+
>
|
|
141
|
+
{{ toast.message }}
|
|
142
|
+
</div>
|
|
143
|
+
}
|
|
144
|
+
</div>
|
|
145
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".spark-toast-container{position:fixed;top:1rem;right:1rem;z-index:9999;display:flex;flex-direction:column;gap:.5rem;pointer-events:none}.spark-toast{padding:.75rem 1rem;border-radius:4px;box-shadow:0 2px 8px #00000026;cursor:pointer;min-width:220px;max-width:400px;color:#fff;font-size:.95rem;pointer-events:auto;animation:spark-toast-in .18s ease-out}.spark-toast--info{background:#0d6efd}.spark-toast--success{background:#198754}.spark-toast--warning{background:#ffc107;color:#000}.spark-toast--error{background:#dc3545}@keyframes spark-toast-in{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}\n"] }]
|
|
146
|
+
}] });
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Registers the built-in client-operation handlers. Currently registers `notify`;
|
|
150
|
+
* additional types (`navigate`, `refreshQuery`, `refreshAttribute`, `disableAction`)
|
|
151
|
+
* land in subsequent commits. Apps add this once in their bootstrap providers.
|
|
152
|
+
*
|
|
153
|
+
* To register custom operation types alongside the built-ins, add additional
|
|
154
|
+
* `multi: true` providers using <see cref="SPARK_CLIENT_OPERATION_HANDLERS" />.
|
|
155
|
+
*/
|
|
156
|
+
function provideSparkClientOperations() {
|
|
157
|
+
return makeEnvironmentProviders([
|
|
158
|
+
{
|
|
159
|
+
provide: SPARK_CLIENT_OPERATION_HANDLERS,
|
|
160
|
+
useFactory: () => {
|
|
161
|
+
const notifications = inject(SparkNotificationService);
|
|
162
|
+
return {
|
|
163
|
+
type: 'notify',
|
|
164
|
+
handler: (operation) => {
|
|
165
|
+
const notify = operation;
|
|
166
|
+
notifications.show(notify.message, notify.kind, notify.durationMs);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
multi: true,
|
|
171
|
+
},
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generated bundle index. Do not edit.
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
export { NotificationKind, SPARK_CLIENT_OPERATION_HANDLERS, SparkClientOperationDispatcher, SparkNotificationService, SparkToastContainerComponent, provideSparkClientOperations };
|
|
180
|
+
//# sourceMappingURL=mintplayer-ng-spark-client-operations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-client-operations.mjs","sources":["../../client-operations/src/operations.ts","../../client-operations/src/handlers.token.ts","../../client-operations/src/dispatcher.service.ts","../../client-operations/src/notification.service.ts","../../client-operations/src/toast-container.component.ts","../../client-operations/src/provide.ts","../../client-operations/mintplayer-ng-spark-client-operations.ts"],"sourcesContent":["// Wire types matching MintPlayer.Spark.Abstractions.ClientOperations on the server.\n// Discriminator is the `type` field. Unknown operation types are silently dropped\n// by the dispatcher (forward-compat: new types can land server-side without updating\n// older clients).\n\nimport type { PersistentObject } from '@mintplayer/ng-spark/models';\n\nexport enum NotificationKind {\n Info = 0,\n Success = 1,\n Warning = 2,\n Error = 3,\n}\n\nexport interface NavigateOperation {\n type: 'navigate';\n objectTypeId?: string;\n id?: string;\n routeName?: string;\n}\n\nexport interface NotifyOperation {\n type: 'notify';\n message: string;\n kind: NotificationKind;\n durationMs?: number;\n}\n\nexport interface RefreshAttributeOperation {\n type: 'refreshAttribute';\n objectTypeId: string;\n id: string;\n attributeName: string;\n value?: unknown;\n}\n\nexport interface RefreshQueryOperation {\n type: 'refreshQuery';\n queryId: string;\n}\n\nexport type DisableTarget =\n | { kind: 'persistentObject'; objectTypeId: string; id: string }\n | { kind: 'query'; queryId: string }\n | { kind: 'currentResponse' }\n | { kind: 'session' };\n\nexport interface DisableActionOperation {\n type: 'disableAction';\n actionName: string;\n target: DisableTarget;\n}\n\nexport interface RetryOperation {\n type: 'retry';\n step: number;\n title: string;\n options: string[];\n defaultOption?: string | null;\n persistentObject?: PersistentObject | null;\n message?: string | null;\n}\n\n/**\n * Discriminated union of known operation types, plus an open shape for unknown\n * future operations. Handlers should narrow via the `type` discriminator before\n * accessing fields specific to their operation type.\n */\nexport type ClientOperation =\n | NavigateOperation\n | NotifyOperation\n | RefreshAttributeOperation\n | RefreshQueryOperation\n | DisableActionOperation\n | RetryOperation\n | { type: string; [key: string]: unknown };\n\n/**\n * Wire envelope returned by every action endpoint. `result` carries the primary\n * payload (the PersistentObject for a Create, the QueryResult for an Execute,\n * etc.); `operations` carries the side-effects the frontend dispatches.\n */\nexport interface ClientOperationEnvelope<T = unknown> {\n result: T | null;\n operations: ClientOperation[];\n}\n","import { InjectionToken } from '@angular/core';\nimport type { ClientOperation } from './operations';\n\n/**\n * A handler for a specific operation type. Receives the operation and\n * executes the side-effect (e.g. show a toast, navigate, refresh a query).\n * Handlers should `as`-narrow the operation to the type they registered for.\n */\nexport type ClientOperationHandler = (operation: ClientOperation) => void;\n\n/**\n * One entry in the multi-provider registration. Apps can register custom\n * handlers alongside the built-in ones to extend the operation set with\n * app-specific operation types.\n */\nexport interface ClientOperationHandlerRegistration {\n type: string;\n handler: ClientOperationHandler;\n}\n\n/**\n * Multi-provider token. `provideSparkClientOperations()` registers the\n * built-in handlers; apps can add their own with additional `multi: true`\n * providers using this token.\n */\nexport const SPARK_CLIENT_OPERATION_HANDLERS = new InjectionToken<readonly ClientOperationHandlerRegistration[]>(\n 'SPARK_CLIENT_OPERATION_HANDLERS',\n);\n","import { Injectable, inject } from '@angular/core';\nimport type { ClientOperation } from './operations';\nimport { SPARK_CLIENT_OPERATION_HANDLERS, type ClientOperationHandler } from './handlers.token';\n\n/**\n * Routes received operations to registered handlers. Unknown operation types\n * (no matching registration) are silently dropped — this is the forward-compat\n * contract that lets new operation types ship server-side without coordinated\n * client updates.\n *\n * Last-registered-wins on duplicate `type` values, matching standard\n * Angular multi-provider override semantics.\n *\n * R2-H19 — security contract for handler authors:\n * The dispatcher treats handler resolution as allow-list-by-type (unknown\n * types drop). It does NOT validate the *content* of each operation. Handlers\n * that act on URL-shaped fields (navigate, redirect, openWindow) MUST run\n * the value through `sanitizeReturnUrl` from `@mintplayer/ng-spark-auth/models`\n * (or an equivalent same-origin check) before acting on it. Otherwise a\n * single attribute-echo XSS or a single mid-channel byte flip on a non-TLS\n * path lets the server drive client navigation to an attacker host. The\n * built-in `notify` handler renders via Angular interpolation (escaped) so\n * it's safe to pass through, but anything more powerful must validate.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkClientOperationDispatcher {\n private readonly handlerMap: ReadonlyMap<string, ClientOperationHandler>;\n\n constructor() {\n const registrations = inject(SPARK_CLIENT_OPERATION_HANDLERS, { optional: true }) ?? [];\n const map = new Map<string, ClientOperationHandler>();\n for (const { type, handler } of registrations) {\n map.set(type, handler);\n }\n this.handlerMap = map;\n }\n\n dispatch(operations: readonly ClientOperation[] | null | undefined): void {\n if (!operations || operations.length === 0) return;\n for (const operation of operations) {\n const handler = this.handlerMap.get(operation.type);\n if (handler) {\n handler(operation);\n }\n // Unknown types: silently dropped (forward-compat).\n }\n }\n}\n","import { Injectable, signal } from '@angular/core';\nimport { NotificationKind } from './operations';\n\nexport interface SparkToast {\n id: string;\n message: string;\n kind: NotificationKind;\n durationMs: number;\n}\n\nconst DEFAULT_DURATION_MS = 4000;\n\n/**\n * Holds the active toasts as a signal. The `<spark-toast-container>` component\n * renders them; the built-in `notify` operation handler pushes new toasts here.\n *\n * Auto-dismissal: each toast schedules its own removal after `durationMs`. Pass\n * `0` to make a toast sticky (manual dismissal only).\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkNotificationService {\n private readonly _toasts = signal<readonly SparkToast[]>([]);\n readonly toasts = this._toasts.asReadonly();\n\n show(message: string, kind: NotificationKind = NotificationKind.Info, durationMs?: number): void {\n const id = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;\n const effectiveDuration = durationMs ?? DEFAULT_DURATION_MS;\n const toast: SparkToast = { id, message, kind, durationMs: effectiveDuration };\n this._toasts.update(toasts => [...toasts, toast]);\n\n if (effectiveDuration > 0) {\n setTimeout(() => this.dismiss(id), effectiveDuration);\n }\n }\n\n dismiss(id: string): void {\n this._toasts.update(toasts => toasts.filter(t => t.id !== id));\n }\n\n clear(): void {\n this._toasts.set([]);\n }\n}\n","import { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { SparkNotificationService } from './notification.service';\nimport { NotificationKind } from './operations';\n\n@Component({\n selector: 'spark-toast-container',\n standalone: true,\n template: `\n <div class=\"spark-toast-container\">\n @for (toast of notifications.toasts(); track toast.id) {\n <div\n class=\"spark-toast\"\n [class.spark-toast--info]=\"toast.kind === Kind.Info\"\n [class.spark-toast--success]=\"toast.kind === Kind.Success\"\n [class.spark-toast--warning]=\"toast.kind === Kind.Warning\"\n [class.spark-toast--error]=\"toast.kind === Kind.Error\"\n (click)=\"notifications.dismiss(toast.id)\"\n >\n {{ toast.message }}\n </div>\n }\n </div>\n `,\n styles: [`\n .spark-toast-container {\n position: fixed;\n top: 1rem;\n right: 1rem;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n pointer-events: none;\n }\n .spark-toast {\n padding: 0.75rem 1rem;\n border-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n cursor: pointer;\n min-width: 220px;\n max-width: 400px;\n color: white;\n font-size: 0.95rem;\n pointer-events: auto;\n animation: spark-toast-in 0.18s ease-out;\n }\n .spark-toast--info { background: #0d6efd; }\n .spark-toast--success { background: #198754; }\n .spark-toast--warning { background: #ffc107; color: #000; }\n .spark-toast--error { background: #dc3545; }\n @keyframes spark-toast-in {\n from { opacity: 0; transform: translateX(8px); }\n to { opacity: 1; transform: translateX(0); }\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SparkToastContainerComponent {\n protected readonly notifications = inject(SparkNotificationService);\n protected readonly Kind = NotificationKind;\n}\n","import { type EnvironmentProviders, inject, makeEnvironmentProviders } from '@angular/core';\nimport type { ClientOperation, NotifyOperation } from './operations';\nimport { SPARK_CLIENT_OPERATION_HANDLERS } from './handlers.token';\nimport { SparkNotificationService } from './notification.service';\n\n/**\n * Registers the built-in client-operation handlers. Currently registers `notify`;\n * additional types (`navigate`, `refreshQuery`, `refreshAttribute`, `disableAction`)\n * land in subsequent commits. Apps add this once in their bootstrap providers.\n *\n * To register custom operation types alongside the built-ins, add additional\n * `multi: true` providers using <see cref=\"SPARK_CLIENT_OPERATION_HANDLERS\" />.\n */\nexport function provideSparkClientOperations(): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SPARK_CLIENT_OPERATION_HANDLERS,\n useFactory: () => {\n const notifications = inject(SparkNotificationService);\n return {\n type: 'notify',\n handler: (operation: ClientOperation) => {\n const notify = operation as NotifyOperation;\n notifications.show(notify.message, notify.kind, notify.durationMs);\n },\n };\n },\n multi: true,\n },\n ]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -4,11 +4,14 @@ import { SparkIconRegistry } from '@mintplayer/ng-spark/services';
|
|
|
4
4
|
|
|
5
5
|
class SparkIconComponent {
|
|
6
6
|
registry = inject(SparkIconRegistry);
|
|
7
|
-
name = input.required(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
name = input.required(/* @ts-ignore */
|
|
8
|
+
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
9
|
+
iconHtml = computed(() => this.registry.get(this.name()), /* @ts-ignore */
|
|
10
|
+
...(ngDevMode ? [{ debugName: "iconHtml" }] : /* istanbul ignore next */ []));
|
|
11
|
+
cssFallbackClass = computed(() => `bi-${this.name()}`, /* @ts-ignore */
|
|
12
|
+
...(ngDevMode ? [{ debugName: "cssFallbackClass" }] : /* istanbul ignore next */ []));
|
|
13
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkIconComponent, isStandalone: true, selector: "spark-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
12
15
|
@if (iconHtml(); as html) {
|
|
13
16
|
<span [innerHTML]="html"></span>
|
|
14
17
|
} @else {
|
|
@@ -16,7 +19,7 @@ class SparkIconComponent {
|
|
|
16
19
|
}
|
|
17
20
|
`, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center}span{display:inline-flex;align-items:center}span ::ng-deep svg{width:1em;height:1em;fill:currentColor}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18
21
|
}
|
|
19
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
22
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkIconComponent, decorators: [{
|
|
20
23
|
type: Component,
|
|
21
24
|
args: [{ selector: 'spark-icon', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
22
25
|
@if (iconHtml(); as html) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-icon.mjs","sources":["../../icon/src/spark-icon.component.ts","../../icon/mintplayer-ng-spark-icon.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { SparkIconRegistry } from '@mintplayer/ng-spark/services';\n\n@Component({\n selector: 'spark-icon',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (iconHtml(); as html) {\n <span [innerHTML]=\"html\"></span>\n } @else {\n <i class=\"bi\" [class]=\"cssFallbackClass()\"></i>\n }\n `,\n styles: [`\n :host {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n span {\n display: inline-flex;\n align-items: center;\n }\n span ::ng-deep svg {\n width: 1em;\n height: 1em;\n fill: currentColor;\n }\n `]\n})\nexport class SparkIconComponent {\n private registry = inject(SparkIconRegistry);\n\n name = input.required<string>();\n\n iconHtml = computed(() => this.registry.get(this.name()));\n\n cssFallbackClass = computed(() => `bi-${this.name()}`);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;MA+Ba,kBAAkB,CAAA;AACrB,IAAA,QAAQ,GAAG,MAAM,CAAC,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-icon.mjs","sources":["../../icon/src/spark-icon.component.ts","../../icon/mintplayer-ng-spark-icon.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { SparkIconRegistry } from '@mintplayer/ng-spark/services';\n\n@Component({\n selector: 'spark-icon',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (iconHtml(); as html) {\n <span [innerHTML]=\"html\"></span>\n } @else {\n <i class=\"bi\" [class]=\"cssFallbackClass()\"></i>\n }\n `,\n styles: [`\n :host {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n span {\n display: inline-flex;\n align-items: center;\n }\n span ::ng-deep svg {\n width: 1em;\n height: 1em;\n fill: currentColor;\n }\n `]\n})\nexport class SparkIconComponent {\n private registry = inject(SparkIconRegistry);\n\n name = input.required<string>();\n\n iconHtml = computed(() => this.registry.get(this.name()));\n\n cssFallbackClass = computed(() => `bi-${this.name()}`);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;MA+Ba,kBAAkB,CAAA;AACrB,IAAA,QAAQ,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAE5C,IAAI,GAAG,KAAK,CAAC,QAAQ;6EAAU;AAE/B,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iFAAC;IAEzD,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAA,GAAA,EAAM,IAAI,CAAC,IAAI,EAAE,CAAA,CAAE;yFAAC;uGAP3C,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAlB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,kBAAkB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAxBnB;;;;;;AAMT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,8KAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAkBU,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBA5B9B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,YAAY,cACV,IAAI,EAAA,eAAA,EACC,uBAAuB,CAAC,MAAM,EAAA,QAAA,EACrC;;;;;;AAMT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,8KAAA,CAAA,EAAA;;;ACbH;;AAEG;;;;"}
|
|
@@ -40,9 +40,92 @@ var ELookupDisplayType;
|
|
|
40
40
|
ELookupDisplayType[ELookupDisplayType["Modal"] = 1] = "Modal";
|
|
41
41
|
})(ELookupDisplayType || (ELookupDisplayType = {}));
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Flattens a nested `PersistentObject` into the plain `Record<string, any>` shape the
|
|
45
|
+
* form state uses throughout ng-spark. Primitive / reference attributes contribute their
|
|
46
|
+
* `value`; nested AsDetail attributes recurse — single becomes an inner dict, array
|
|
47
|
+
* becomes an array of inner dicts. Returns `{}` for `null` / `undefined` input.
|
|
48
|
+
*
|
|
49
|
+
* This is the ONE place that reads the server's new AsDetail wire shape and collapses it
|
|
50
|
+
* back to the flat dict the form components already handle.
|
|
51
|
+
*/
|
|
52
|
+
function nestedPoToDict(po) {
|
|
53
|
+
if (!po)
|
|
54
|
+
return {};
|
|
55
|
+
const dict = {};
|
|
56
|
+
for (const attr of po.attributes ?? []) {
|
|
57
|
+
dict[attr.name] = attributeValueForForm(attr);
|
|
58
|
+
}
|
|
59
|
+
return dict;
|
|
60
|
+
}
|
|
61
|
+
function attributeValueForForm(attr) {
|
|
62
|
+
if (attr.dataType === 'AsDetail') {
|
|
63
|
+
if (attr.isArray)
|
|
64
|
+
return (attr.objects ?? []).map(po => nestedPoToDict(po));
|
|
65
|
+
return attr.object ? nestedPoToDict(attr.object) : null;
|
|
66
|
+
}
|
|
67
|
+
return attr.value;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Builds a nested `PersistentObject` from a flat dict against the schema in
|
|
71
|
+
* <paramref name="entityType"/>. Used when the form is about to save — AsDetail attributes
|
|
72
|
+
* are no longer sent as flat dicts in `attribute.value`; the server now requires
|
|
73
|
+
* `attribute.object` / `attribute.objects` with fully scaffolded nested POs.
|
|
74
|
+
*
|
|
75
|
+
* `resolve` walks through AsDetail types registered elsewhere (usually the full
|
|
76
|
+
* `getEntityTypes()` list, keyed by CLR type name). Nested AsDetail inside AsDetail is
|
|
77
|
+
* handled recursively.
|
|
78
|
+
*/
|
|
79
|
+
function dictToNestedPo(dict, entityType, resolve) {
|
|
80
|
+
const attributes = (entityType.attributes ?? [])
|
|
81
|
+
.map(attrDef => buildAttribute(attrDef, dict?.[attrDef.name], resolve));
|
|
82
|
+
return {
|
|
83
|
+
id: dict?.['Id'] ?? dict?.['id'] ?? '',
|
|
84
|
+
name: entityType.name,
|
|
85
|
+
objectTypeId: entityType.id,
|
|
86
|
+
attributes,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function buildAttribute(attrDef, raw, resolve) {
|
|
90
|
+
const attr = {
|
|
91
|
+
id: attrDef.id,
|
|
92
|
+
name: attrDef.name,
|
|
93
|
+
label: attrDef.label,
|
|
94
|
+
dataType: attrDef.dataType,
|
|
95
|
+
isArray: attrDef.isArray,
|
|
96
|
+
isRequired: attrDef.isRequired,
|
|
97
|
+
isVisible: attrDef.isVisible,
|
|
98
|
+
isReadOnly: attrDef.isReadOnly,
|
|
99
|
+
order: attrDef.order,
|
|
100
|
+
rules: attrDef.rules ?? [],
|
|
101
|
+
isValueChanged: true,
|
|
102
|
+
};
|
|
103
|
+
if (attrDef.dataType === 'AsDetail') {
|
|
104
|
+
// Server expects attr.value null for AsDetail; the nested PO carries the data.
|
|
105
|
+
attr.value = null;
|
|
106
|
+
attr.asDetailType = attrDef.asDetailType;
|
|
107
|
+
const nestedType = attrDef.asDetailType ? resolve(attrDef.asDetailType) : undefined;
|
|
108
|
+
if (!nestedType) {
|
|
109
|
+
attr.object = null;
|
|
110
|
+
attr.objects = attrDef.isArray ? [] : null;
|
|
111
|
+
return attr;
|
|
112
|
+
}
|
|
113
|
+
if (attrDef.isArray) {
|
|
114
|
+
const items = Array.isArray(raw) ? raw : [];
|
|
115
|
+
attr.objects = items.map(item => dictToNestedPo(item ?? {}, nestedType, resolve));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
attr.object = raw ? dictToNestedPo(raw, nestedType, resolve) : null;
|
|
119
|
+
}
|
|
120
|
+
return attr;
|
|
121
|
+
}
|
|
122
|
+
attr.value = raw;
|
|
123
|
+
return attr;
|
|
124
|
+
}
|
|
125
|
+
|
|
43
126
|
/**
|
|
44
127
|
* Generated bundle index. Do not edit.
|
|
45
128
|
*/
|
|
46
129
|
|
|
47
|
-
export { ELookupDisplayType, ShowedOn, currentLanguage, hasShowedOnFlag, resolveTranslation };
|
|
130
|
+
export { ELookupDisplayType, ShowedOn, currentLanguage, dictToNestedPo, hasShowedOnFlag, nestedPoToDict, resolveTranslation };
|
|
48
131
|
//# sourceMappingURL=mintplayer-ng-spark-models.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-models.mjs","sources":["../../models/src/translated-string.ts","../../models/src/showed-on.ts","../../models/src/lookup-reference.ts","../../models/mintplayer-ng-spark-models.ts"],"sourcesContent":["import { signal, type WritableSignal } from '@angular/core';\n\nexport type TranslatedString = Record<string, string>;\n\n/** Global reactive language state — shared across library boundaries via globalThis */\nexport const currentLanguage: WritableSignal<string> =\n ((globalThis as any).__sparkCurrentLanguage ??= signal('en'));\n\nexport function resolveTranslation(ts: TranslatedString | undefined, lang?: string): string {\n if (!ts) return '';\n const language = lang ?? currentLanguage();\n return ts[language] ?? ts['en'] ?? Object.values(ts)[0] ?? '';\n}\n","/**\n * Flags enum controlling on which pages an attribute should be displayed.\n * Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject\n */\nexport enum ShowedOn {\n Query = 1,\n PersistentObject = 2,\n}\n\n/**\n * Helper function to check if a ShowedOn value includes a specific flag.\n */\nexport function hasShowedOnFlag(value: ShowedOn | string | undefined, flag: ShowedOn): boolean {\n if (value === undefined) return true; // Default: show on all pages\n\n // Handle string values from JSON (e.g., \"Query, PersistentObject\")\n if (typeof value === 'string') {\n const parts = value.split(',').map(s => s.trim());\n const flagName = ShowedOn[flag];\n return parts.includes(flagName);\n }\n\n // Handle numeric flag values\n return (value & flag) === flag;\n}\n","import { TranslatedString } from './translated-string';\n\nexport enum ELookupDisplayType {\n Dropdown = 0,\n Modal = 1\n}\n\nexport interface LookupReferenceListItem {\n name: string;\n isTransient: boolean;\n valueCount: number;\n displayType: ELookupDisplayType;\n}\n\nexport interface LookupReference {\n name: string;\n isTransient: boolean;\n displayType: ELookupDisplayType;\n values: LookupReferenceValue[];\n}\n\nexport interface LookupReferenceValue {\n key: string;\n values: TranslatedString;\n isActive: boolean;\n extra?: Record<string, unknown>;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAIA;AACO,MAAM,eAAe,IACxB,UAAkB,CAAC,sBAAsB,KAAK,MAAM,CAAC,IAAI,CAAC;AAExD,SAAU,kBAAkB,CAAC,EAAgC,EAAE,IAAa,EAAA;AAChF,IAAA,IAAI,CAAC,EAAE;AAAE,QAAA,OAAO,EAAE;AAClB,IAAA,MAAM,QAAQ,GAAG,IAAI,IAAI,eAAe,EAAE;IAC1C,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;AAC/D;;ACZA;;;AAGG;IACS;AAAZ,CAAA,UAAY,QAAQ,EAAA;AAClB,IAAA,QAAA,CAAA,QAAA,CAAA,OAAA,CAAA,GAAA,CAAA,CAAA,GAAA,OAAS;AACT,IAAA,QAAA,CAAA,QAAA,CAAA,kBAAA,CAAA,GAAA,CAAA,CAAA,GAAA,kBAAoB;AACtB,CAAC,EAHW,QAAQ,KAAR,QAAQ,GAAA,EAAA,CAAA,CAAA;AAKpB;;AAEG;AACG,SAAU,eAAe,CAAC,KAAoC,EAAE,IAAc,EAAA;IAClF,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;;AAGrC,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AACjD,QAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC;AAC/B,QAAA,OAAO,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACjC;;AAGA,IAAA,OAAO,CAAC,KAAK,GAAG,IAAI,MAAM,IAAI;AAChC;;ICtBY;AAAZ,CAAA,UAAY,kBAAkB,EAAA;AAC5B,IAAA,kBAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,GAAA,CAAA,CAAA,GAAA,UAAY;AACZ,IAAA,kBAAA,CAAA,kBAAA,CAAA,OAAA,CAAA,GAAA,CAAA,CAAA,GAAA,OAAS;AACX,CAAC,EAHW,kBAAkB,KAAlB,kBAAkB,GAAA,EAAA,CAAA,CAAA;;
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-models.mjs","sources":["../../models/src/translated-string.ts","../../models/src/showed-on.ts","../../models/src/lookup-reference.ts","../../models/src/as-detail-conversions.ts","../../models/mintplayer-ng-spark-models.ts"],"sourcesContent":["import { signal, type WritableSignal } from '@angular/core';\n\nexport type TranslatedString = Record<string, string>;\n\n/** Global reactive language state — shared across library boundaries via globalThis */\nexport const currentLanguage: WritableSignal<string> =\n ((globalThis as any).__sparkCurrentLanguage ??= signal('en'));\n\nexport function resolveTranslation(ts: TranslatedString | undefined, lang?: string): string {\n if (!ts) return '';\n const language = lang ?? currentLanguage();\n return ts[language] ?? ts['en'] ?? Object.values(ts)[0] ?? '';\n}\n","/**\n * Flags enum controlling on which pages an attribute should be displayed.\n * Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject\n */\nexport enum ShowedOn {\n Query = 1,\n PersistentObject = 2,\n}\n\n/**\n * Helper function to check if a ShowedOn value includes a specific flag.\n */\nexport function hasShowedOnFlag(value: ShowedOn | string | undefined, flag: ShowedOn): boolean {\n if (value === undefined) return true; // Default: show on all pages\n\n // Handle string values from JSON (e.g., \"Query, PersistentObject\")\n if (typeof value === 'string') {\n const parts = value.split(',').map(s => s.trim());\n const flagName = ShowedOn[flag];\n return parts.includes(flagName);\n }\n\n // Handle numeric flag values\n return (value & flag) === flag;\n}\n","import { TranslatedString } from './translated-string';\n\nexport enum ELookupDisplayType {\n Dropdown = 0,\n Modal = 1\n}\n\nexport interface LookupReferenceListItem {\n name: string;\n isTransient: boolean;\n valueCount: number;\n displayType: ELookupDisplayType;\n}\n\nexport interface LookupReference {\n name: string;\n isTransient: boolean;\n displayType: ELookupDisplayType;\n values: LookupReferenceValue[];\n}\n\nexport interface LookupReferenceValue {\n key: string;\n values: TranslatedString;\n isActive: boolean;\n extra?: Record<string, unknown>;\n}\n","import { EntityAttributeDefinition } from './entity-type';\nimport { EntityType } from './entity-type';\nimport { PersistentObject } from './persistent-object';\nimport { PersistentObjectAttribute } from './persistent-object-attribute';\n\n/**\n * Resolves an `EntityType` by its CLR type name (e.g. `\"HR.Entities.Address\"`).\n * Callers typically close over `sparkService.getEntityTypes()`'s cached list.\n */\nexport type EntityTypeResolver = (clrTypeName: string) => EntityType | undefined;\n\n/**\n * Flattens a nested `PersistentObject` into the plain `Record<string, any>` shape the\n * form state uses throughout ng-spark. Primitive / reference attributes contribute their\n * `value`; nested AsDetail attributes recurse — single becomes an inner dict, array\n * becomes an array of inner dicts. Returns `{}` for `null` / `undefined` input.\n *\n * This is the ONE place that reads the server's new AsDetail wire shape and collapses it\n * back to the flat dict the form components already handle.\n */\nexport function nestedPoToDict(po: PersistentObject | null | undefined): Record<string, any> {\n if (!po) return {};\n const dict: Record<string, any> = {};\n for (const attr of po.attributes ?? []) {\n dict[attr.name] = attributeValueForForm(attr);\n }\n return dict;\n}\n\nfunction attributeValueForForm(attr: PersistentObjectAttribute): any {\n if (attr.dataType === 'AsDetail') {\n if (attr.isArray) return (attr.objects ?? []).map(po => nestedPoToDict(po));\n return attr.object ? nestedPoToDict(attr.object) : null;\n }\n return attr.value;\n}\n\n/**\n * Builds a nested `PersistentObject` from a flat dict against the schema in\n * <paramref name=\"entityType\"/>. Used when the form is about to save — AsDetail attributes\n * are no longer sent as flat dicts in `attribute.value`; the server now requires\n * `attribute.object` / `attribute.objects` with fully scaffolded nested POs.\n *\n * `resolve` walks through AsDetail types registered elsewhere (usually the full\n * `getEntityTypes()` list, keyed by CLR type name). Nested AsDetail inside AsDetail is\n * handled recursively.\n */\nexport function dictToNestedPo(\n dict: Record<string, any> | null | undefined,\n entityType: EntityType,\n resolve: EntityTypeResolver,\n): PersistentObject {\n const attributes: PersistentObjectAttribute[] = (entityType.attributes ?? [])\n .map(attrDef => buildAttribute(attrDef, dict?.[attrDef.name], resolve));\n\n return {\n id: (dict?.['Id'] as string) ?? (dict?.['id'] as string) ?? '',\n name: entityType.name,\n objectTypeId: entityType.id,\n attributes,\n };\n}\n\nfunction buildAttribute(\n attrDef: EntityAttributeDefinition,\n raw: any,\n resolve: EntityTypeResolver,\n): PersistentObjectAttribute {\n const attr: PersistentObjectAttribute = {\n id: attrDef.id,\n name: attrDef.name,\n label: attrDef.label,\n dataType: attrDef.dataType,\n isArray: attrDef.isArray,\n isRequired: attrDef.isRequired,\n isVisible: attrDef.isVisible,\n isReadOnly: attrDef.isReadOnly,\n order: attrDef.order,\n rules: attrDef.rules ?? [],\n isValueChanged: true,\n };\n\n if (attrDef.dataType === 'AsDetail') {\n // Server expects attr.value null for AsDetail; the nested PO carries the data.\n attr.value = null;\n attr.asDetailType = attrDef.asDetailType;\n\n const nestedType = attrDef.asDetailType ? resolve(attrDef.asDetailType) : undefined;\n if (!nestedType) {\n attr.object = null;\n attr.objects = attrDef.isArray ? [] : null;\n return attr;\n }\n\n if (attrDef.isArray) {\n const items: any[] = Array.isArray(raw) ? raw : [];\n attr.objects = items.map(item => dictToNestedPo((item as Record<string, any>) ?? {}, nestedType, resolve));\n } else {\n attr.object = raw ? dictToNestedPo(raw as Record<string, any>, nestedType, resolve) : null;\n }\n return attr;\n }\n\n attr.value = raw;\n return attr;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|