@memberjunction/ng-explorer-core 5.49.0 → 5.51.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/dist/generated/lazy-feature-config.d.ts +11 -2
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +79 -23
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/omnibar/omnibar-palette.component.js +98 -96
- package/dist/lib/omnibar/omnibar-palette.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts +19 -16
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +127 -121
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/services/lazy-module-registry.d.ts +48 -8
- package/dist/lib/services/lazy-module-registry.d.ts.map +1 -1
- package/dist/lib/services/lazy-module-registry.js +103 -23
- package/dist/lib/services/lazy-module-registry.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +84 -9
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +380 -41
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.d.ts +159 -29
- package/dist/lib/shell/components/header/app-switcher.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.js +822 -116
- package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts +10 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js +30 -4
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +3 -0
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +22 -12
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/shell/shell.module.d.ts +1 -1
- package/dist/lib/shell/shell.module.d.ts.map +1 -1
- package/dist/lib/shell/shell.module.js +3 -1
- package/dist/lib/shell/shell.module.js.map +1 -1
- package/package.json +51 -51
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* One lazy-loadable chunk: a stable identity plus the dynamic import that loads it.
|
|
4
|
+
*
|
|
5
|
+
* Many compound keys map to the same chunk, so `chunkId` is what the registry dedupes
|
|
6
|
+
* loads by — it must uniquely identify the chunk. The generated LAZY_FEATURE_CONFIG
|
|
7
|
+
* uses the dynamic import specifier (e.g. '@memberjunction/ng-dashboards/ai-dashboards.module').
|
|
8
|
+
*/
|
|
9
|
+
export interface LazyFeatureChunk {
|
|
10
|
+
/** Stable, unique chunk identity. Also the label dev tools show for the chunk. */
|
|
11
|
+
readonly chunkId: string;
|
|
12
|
+
/** Imports the chunk, which runs the `@RegisterClass` decorators inside it. */
|
|
13
|
+
readonly load: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Registry that maps compound keys (BaseClassName::Key) to the chunk that provides them.
|
|
4
17
|
*
|
|
5
18
|
* Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`
|
|
6
19
|
* or `CreateInstanceAsync()` call that misses synchronously will automatically trigger
|
|
@@ -10,18 +23,35 @@ import * as i0 from "@angular/core";
|
|
|
10
23
|
*/
|
|
11
24
|
export declare class LazyModuleRegistry {
|
|
12
25
|
private registry;
|
|
26
|
+
/**
|
|
27
|
+
* Secondary index: subclass key → (compound key → chunk), ignoring the base-class half.
|
|
28
|
+
*
|
|
29
|
+
* Needed because the base-class half of a compound key is NOT stable at runtime. ClassFactory
|
|
30
|
+
* builds the lookup from `baseClass.name`, but the emitted identifier varies by build mode —
|
|
31
|
+
* for the same `BaseResourceComponent` we have observed `_BaseResourceComponent` (unminified,
|
|
32
|
+
* Angular's named class expression) and `BaseResourceComponent2` (minified, esbuild's collision
|
|
33
|
+
* rename) — while the generated LAZY_FEATURE_CONFIG keys use the TypeScript source name. Any
|
|
34
|
+
* mismatch made the primary lookup miss, so NO chunk was ever imported and every lazily
|
|
35
|
+
* provided class silently failed to resolve.
|
|
36
|
+
*
|
|
37
|
+
* The inner map is keyed by compound key (not a flat Set of chunks) so that re-registering a
|
|
38
|
+
* compound key replaces its contribution here too, matching `registry`'s replace semantics.
|
|
39
|
+
*/
|
|
40
|
+
private chunksBySubclassKey;
|
|
41
|
+
/** chunkIds that have finished loading. */
|
|
13
42
|
private loadedChunks;
|
|
43
|
+
/** In-flight loads, keyed by chunkId, so concurrent callers share one import. */
|
|
14
44
|
private pendingLoads;
|
|
15
45
|
/**
|
|
16
|
-
* Register a single compound key with
|
|
46
|
+
* Register a single compound key with the chunk that provides it.
|
|
17
47
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
18
|
-
* @param
|
|
48
|
+
* @param chunk The chunk descriptor (stable id + dynamic import)
|
|
19
49
|
*/
|
|
20
|
-
Register(compoundKey: string,
|
|
50
|
+
Register(compoundKey: string, chunk: LazyFeatureChunk): void;
|
|
21
51
|
/**
|
|
22
52
|
* Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).
|
|
23
53
|
*/
|
|
24
|
-
RegisterBulk(mappings: Record<string,
|
|
54
|
+
RegisterBulk(mappings: Record<string, LazyFeatureChunk>): void;
|
|
25
55
|
/**
|
|
26
56
|
* Wires this registry to ClassFactory as a lazy loader.
|
|
27
57
|
* After calling this, any `ClassFactory.GetRegistrationAsync()` or `CreateInstanceAsync()`
|
|
@@ -31,8 +61,8 @@ export declare class LazyModuleRegistry {
|
|
|
31
61
|
WireToClassFactory(): void;
|
|
32
62
|
/**
|
|
33
63
|
* Read-only snapshot of the registry state — for diagnostic tools.
|
|
34
|
-
* Groups compound keys by
|
|
35
|
-
*
|
|
64
|
+
* Groups compound keys by `chunkId` so inspectors can show "X chunks, Y loaded"
|
|
65
|
+
* plus the keys covered by each.
|
|
36
66
|
*/
|
|
37
67
|
GetSnapshot(): {
|
|
38
68
|
registered: string[];
|
|
@@ -42,7 +72,8 @@ export declare class LazyModuleRegistry {
|
|
|
42
72
|
loaded: boolean;
|
|
43
73
|
keys: string[];
|
|
44
74
|
}>;
|
|
45
|
-
|
|
75
|
+
/** How many chunks have finished loading. The total is `chunks.length`. */
|
|
76
|
+
loadedChunkCount: number;
|
|
46
77
|
};
|
|
47
78
|
/**
|
|
48
79
|
* Programmatically trigger a lazy chunk load by compound key. Returns true
|
|
@@ -57,6 +88,15 @@ export declare class LazyModuleRegistry {
|
|
|
57
88
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
58
89
|
*/
|
|
59
90
|
Load(compoundKey: string): Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* The distinct chunks (by `chunkId`) registered under a subclass key, in registration order.
|
|
93
|
+
*/
|
|
94
|
+
private candidateChunksFor;
|
|
95
|
+
/**
|
|
96
|
+
* Imports a chunk once, sharing in-flight loads and remembering completed ones.
|
|
97
|
+
* Resolves when the chunk is loaded; rejects if the import fails.
|
|
98
|
+
*/
|
|
99
|
+
private loadChunk;
|
|
60
100
|
static ɵfac: i0.ɵɵFactoryDeclaration<LazyModuleRegistry, never>;
|
|
61
101
|
static ɵprov: i0.ɵɵInjectableDeclaration<LazyModuleRegistry>;
|
|
62
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-module-registry.d.ts","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":";AAGA;;;;;;;;GAQG;AACH,qBACa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"lazy-module-registry.d.ts","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":";AAGA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAQD;;;;;;;;GAQG;AACH,qBACa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAuC;IACvD;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,2CAA2C;IAC3C,OAAO,CAAC,YAAY,CAAqB;IACzC,iFAAiF;IACjF,OAAO,CAAC,YAAY,CAAoC;IAExD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAQ5D;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI;IAM9D;;;;;OAKG;IACH,kBAAkB,IAAI,IAAI;IAU1B;;;;OAIG;IACH,WAAW,IAAI;QACb,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAC;QACpE,2EAA2E;QAC3E,gBAAgB,EAAE,MAAM,CAAC;KAC1B;IA6BD;;;OAGG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD;;;;;;OAMG;IACG,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgCjD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;;OAGG;YACW,SAAS;yCArKZ,kBAAkB;6CAAlB,kBAAkB;CAwM9B"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
2
|
import { MJGlobal } from '@memberjunction/global';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
|
+
/** Extracts the subclass-key half of a `"BaseClassName::Key"` compound key. */
|
|
5
|
+
function subclassKeyOf(compoundKey) {
|
|
6
|
+
const separator = compoundKey.indexOf('::');
|
|
7
|
+
return separator < 0 ? compoundKey : compoundKey.slice(separator + 2);
|
|
8
|
+
}
|
|
4
9
|
/**
|
|
5
|
-
* Registry that maps compound keys (BaseClassName::Key) to
|
|
10
|
+
* Registry that maps compound keys (BaseClassName::Key) to the chunk that provides them.
|
|
6
11
|
*
|
|
7
12
|
* Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`
|
|
8
13
|
* or `CreateInstanceAsync()` call that misses synchronously will automatically trigger
|
|
@@ -12,22 +17,43 @@ import * as i0 from "@angular/core";
|
|
|
12
17
|
*/
|
|
13
18
|
export class LazyModuleRegistry {
|
|
14
19
|
registry = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Secondary index: subclass key → (compound key → chunk), ignoring the base-class half.
|
|
22
|
+
*
|
|
23
|
+
* Needed because the base-class half of a compound key is NOT stable at runtime. ClassFactory
|
|
24
|
+
* builds the lookup from `baseClass.name`, but the emitted identifier varies by build mode —
|
|
25
|
+
* for the same `BaseResourceComponent` we have observed `_BaseResourceComponent` (unminified,
|
|
26
|
+
* Angular's named class expression) and `BaseResourceComponent2` (minified, esbuild's collision
|
|
27
|
+
* rename) — while the generated LAZY_FEATURE_CONFIG keys use the TypeScript source name. Any
|
|
28
|
+
* mismatch made the primary lookup miss, so NO chunk was ever imported and every lazily
|
|
29
|
+
* provided class silently failed to resolve.
|
|
30
|
+
*
|
|
31
|
+
* The inner map is keyed by compound key (not a flat Set of chunks) so that re-registering a
|
|
32
|
+
* compound key replaces its contribution here too, matching `registry`'s replace semantics.
|
|
33
|
+
*/
|
|
34
|
+
chunksBySubclassKey = new Map();
|
|
35
|
+
/** chunkIds that have finished loading. */
|
|
15
36
|
loadedChunks = new Set();
|
|
37
|
+
/** In-flight loads, keyed by chunkId, so concurrent callers share one import. */
|
|
16
38
|
pendingLoads = new Map();
|
|
17
39
|
/**
|
|
18
|
-
* Register a single compound key with
|
|
40
|
+
* Register a single compound key with the chunk that provides it.
|
|
19
41
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
20
|
-
* @param
|
|
42
|
+
* @param chunk The chunk descriptor (stable id + dynamic import)
|
|
21
43
|
*/
|
|
22
|
-
Register(compoundKey,
|
|
23
|
-
this.registry.set(compoundKey,
|
|
44
|
+
Register(compoundKey, chunk) {
|
|
45
|
+
this.registry.set(compoundKey, chunk);
|
|
46
|
+
const subclassKey = subclassKeyOf(compoundKey);
|
|
47
|
+
const byCompoundKey = this.chunksBySubclassKey.get(subclassKey) ?? new Map();
|
|
48
|
+
byCompoundKey.set(compoundKey, chunk);
|
|
49
|
+
this.chunksBySubclassKey.set(subclassKey, byCompoundKey);
|
|
24
50
|
}
|
|
25
51
|
/**
|
|
26
52
|
* Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).
|
|
27
53
|
*/
|
|
28
54
|
RegisterBulk(mappings) {
|
|
29
|
-
for (const [compoundKey,
|
|
30
|
-
this.
|
|
55
|
+
for (const [compoundKey, chunk] of Object.entries(mappings)) {
|
|
56
|
+
this.Register(compoundKey, chunk);
|
|
31
57
|
}
|
|
32
58
|
}
|
|
33
59
|
/**
|
|
@@ -45,16 +71,15 @@ export class LazyModuleRegistry {
|
|
|
45
71
|
}
|
|
46
72
|
/**
|
|
47
73
|
* Read-only snapshot of the registry state — for diagnostic tools.
|
|
48
|
-
* Groups compound keys by
|
|
49
|
-
*
|
|
74
|
+
* Groups compound keys by `chunkId` so inspectors can show "X chunks, Y loaded"
|
|
75
|
+
* plus the keys covered by each.
|
|
50
76
|
*/
|
|
51
77
|
GetSnapshot() {
|
|
52
78
|
const byChunk = new Map();
|
|
53
|
-
for (const [compoundKey,
|
|
54
|
-
const
|
|
55
|
-
const arr = byChunk.get(id) ?? [];
|
|
79
|
+
for (const [compoundKey, chunk] of this.registry.entries()) {
|
|
80
|
+
const arr = byChunk.get(chunk.chunkId) ?? [];
|
|
56
81
|
arr.push(compoundKey);
|
|
57
|
-
byChunk.set(
|
|
82
|
+
byChunk.set(chunk.chunkId, arr);
|
|
58
83
|
}
|
|
59
84
|
const chunks = Array.from(byChunk.entries())
|
|
60
85
|
.map(([chunkId, keys]) => ({
|
|
@@ -72,7 +97,7 @@ export class LazyModuleRegistry {
|
|
|
72
97
|
registered: Array.from(this.registry.keys()).sort(),
|
|
73
98
|
loaded: loaded.sort(),
|
|
74
99
|
chunks,
|
|
75
|
-
|
|
100
|
+
loadedChunkCount: this.loadedChunks.size
|
|
76
101
|
};
|
|
77
102
|
}
|
|
78
103
|
/**
|
|
@@ -90,27 +115,82 @@ export class LazyModuleRegistry {
|
|
|
90
115
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
91
116
|
*/
|
|
92
117
|
async Load(compoundKey) {
|
|
93
|
-
|
|
94
|
-
|
|
118
|
+
// Primary: exact compound key. Correct and precise when the runtime base-class name
|
|
119
|
+
// happens to match the generated config.
|
|
120
|
+
const exact = this.registry.get(compoundKey);
|
|
121
|
+
if (exact) {
|
|
122
|
+
await this.loadChunk(exact);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
// Fallback: match on the subclass key alone, because the base-class half is not stable
|
|
126
|
+
// across build modes (see `chunksBySubclassKey`). Subclass keys are unique per chunk in
|
|
127
|
+
// practice; if several chunks ever claim one, load them all so resolution can't silently
|
|
128
|
+
// pick wrong — ClassFactory still decides the winner by base class afterwards.
|
|
129
|
+
const candidates = this.candidateChunksFor(subclassKeyOf(compoundKey));
|
|
130
|
+
if (candidates.length === 0)
|
|
95
131
|
return false;
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
const
|
|
132
|
+
// One candidate failing must not skip the rest — a later one may hold the class. Only
|
|
133
|
+
// surface a failure if NOTHING loaded, so a partial failure can't mask a success.
|
|
134
|
+
const errors = [];
|
|
135
|
+
let loaded = false;
|
|
136
|
+
for (const chunk of candidates) {
|
|
137
|
+
try {
|
|
138
|
+
await this.loadChunk(chunk);
|
|
139
|
+
loaded = true;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
errors.push(error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!loaded)
|
|
146
|
+
throw errors[0];
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The distinct chunks (by `chunkId`) registered under a subclass key, in registration order.
|
|
151
|
+
*/
|
|
152
|
+
candidateChunksFor(subclassKey) {
|
|
153
|
+
const byCompoundKey = this.chunksBySubclassKey.get(subclassKey);
|
|
154
|
+
if (!byCompoundKey)
|
|
155
|
+
return [];
|
|
156
|
+
const distinct = new Map();
|
|
157
|
+
for (const chunk of byCompoundKey.values()) {
|
|
158
|
+
distinct.set(chunk.chunkId, chunk);
|
|
159
|
+
}
|
|
160
|
+
return Array.from(distinct.values());
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Imports a chunk once, sharing in-flight loads and remembering completed ones.
|
|
164
|
+
* Resolves when the chunk is loaded; rejects if the import fails.
|
|
165
|
+
*/
|
|
166
|
+
async loadChunk(chunk) {
|
|
167
|
+
// Dedupe on the chunk's declared id — multiple compound keys share one chunk.
|
|
168
|
+
// This MUST be a value that differs between chunks; deriving it from the loader
|
|
169
|
+
// function (e.g. Function.toString()) collapses every chunk into one, so the first
|
|
170
|
+
// chunk loaded makes all the others look already-loaded and their classes never register.
|
|
171
|
+
const chunkKey = chunk.chunkId;
|
|
99
172
|
if (this.loadedChunks.has(chunkKey))
|
|
100
|
-
return
|
|
173
|
+
return;
|
|
101
174
|
// Deduplicate concurrent loads
|
|
102
175
|
const pending = this.pendingLoads.get(chunkKey);
|
|
103
176
|
if (pending) {
|
|
104
177
|
await pending;
|
|
105
|
-
return
|
|
178
|
+
return;
|
|
106
179
|
}
|
|
107
|
-
const loadPromise =
|
|
180
|
+
const loadPromise = chunk.load().then(() => {
|
|
108
181
|
this.loadedChunks.add(chunkKey);
|
|
109
182
|
this.pendingLoads.delete(chunkKey);
|
|
183
|
+
}, (error) => {
|
|
184
|
+
// Drop the failed attempt so a later navigation retries. Chunk fetches fail
|
|
185
|
+
// transiently in normal use — a dev-server rebuild renames every lazy chunk, so a
|
|
186
|
+
// page loaded before the rebuild 404s once. Left in `pendingLoads`, the rejected
|
|
187
|
+
// promise would be re-awaited (and re-thrown) forever, killing the chunk for the
|
|
188
|
+
// rest of the page session. The rejection still propagates to this caller.
|
|
189
|
+
this.pendingLoads.delete(chunkKey);
|
|
190
|
+
throw error;
|
|
110
191
|
});
|
|
111
192
|
this.pendingLoads.set(chunkKey, loadPromise);
|
|
112
193
|
await loadPromise;
|
|
113
|
-
return true;
|
|
114
194
|
}
|
|
115
195
|
static ɵfac = function LazyModuleRegistry_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || LazyModuleRegistry)(); };
|
|
116
196
|
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: LazyModuleRegistry, factory: LazyModuleRegistry.ɵfac, providedIn: 'root' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-module-registry.js","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { MJGlobal } from '@memberjunction/global';\n\n/**\n * Registry that maps compound keys (BaseClassName::Key) to dynamic import() loaders.\n *\n * Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`\n * or `CreateInstanceAsync()` call that misses synchronously will automatically trigger\n * the correct lazy chunk load. This replaces the previous pattern where individual\n * components (ResourceContainerComponent, TabContainerComponent, etc.) each had their\n * own retry logic.\n */\n@Injectable({ providedIn: 'root' })\nexport class LazyModuleRegistry {\n private registry = new Map<string, () => Promise<void>>();\n private loadedChunks = new Set<string>();\n private pendingLoads = new Map<string, Promise<void>>();\n\n /**\n * Register a single compound key with its loader function.\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n * @param loader Dynamic import function that loads the chunk containing the class\n */\n Register(compoundKey: string, loader: () => Promise<void>): void {\n this.registry.set(compoundKey, loader);\n }\n\n /**\n * Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).\n */\n RegisterBulk(mappings: Record<string, () => Promise<void>>): void {\n for (const [compoundKey, loader] of Object.entries(mappings)) {\n this.registry.set(compoundKey, loader);\n }\n }\n\n /**\n * Wires this registry to ClassFactory as a lazy loader.\n * After calling this, any `ClassFactory.GetRegistrationAsync()` or `CreateInstanceAsync()`\n * that fails to find a registration synchronously will call back into this registry\n * with the compound key (baseClassName::key) to trigger lazy loading.\n */\n WireToClassFactory(): void {\n MJGlobal.Instance.ClassFactory.RegisterLazyLoader(\n (baseClassName: string, key: string) => this.Load(`${baseClassName}::${key}`)\n );\n // Publish to a well-known global so introspection tools (e.g. the Admin\n // app's \"Lazy Loading\" inspector in ng-dashboards) can read the snapshot\n // without creating a hard package dependency on explorer-core. Dev tools only.\n (globalThis as { __mj_lazy_registry__?: LazyModuleRegistry }).__mj_lazy_registry__ = this;\n }\n\n /**\n * Read-only snapshot of the registry state — for diagnostic tools.\n * Groups compound keys by chunk (the underlying loader function) so\n * inspectors can show \"X chunks, Y loaded\" plus the keys covered by each.\n */\n GetSnapshot(): {\n registered: string[];\n loaded: string[];\n chunks: Array<{ chunkId: string; loaded: boolean; keys: string[] }>;\n chunkCount: number;\n } {\n const byChunk = new Map<string, string[]>();\n for (const [compoundKey, loader] of this.registry.entries()) {\n const id = loader.toString();\n const arr = byChunk.get(id) ?? [];\n arr.push(compoundKey);\n byChunk.set(id, arr);\n }\n\n const chunks = Array.from(byChunk.entries())\n .map(([chunkId, keys]) => ({\n chunkId,\n loaded: this.loadedChunks.has(chunkId),\n keys: keys.sort()\n }))\n .sort((a, b) => b.keys.length - a.keys.length);\n\n const loaded: string[] = [];\n for (const c of chunks) {\n if (c.loaded) loaded.push(...c.keys);\n }\n\n return {\n registered: Array.from(this.registry.keys()).sort(),\n loaded: loaded.sort(),\n chunks,\n chunkCount: this.loadedChunks.size\n };\n }\n\n /**\n * Programmatically trigger a lazy chunk load by compound key. Returns true\n * on success. Useful for dev tools that want to \"preload\" a chunk.\n */\n async ForceLoad(compoundKey: string): Promise<boolean> {\n return this.Load(compoundKey);\n }\n\n /**\n * Attempt to lazy-load the chunk for a given compound key.\n * Returns true if the key was found in the registry and loaded.\n * Deduplicates concurrent loads of the same chunk.\n *\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n */\n async Load(compoundKey: string): Promise<boolean> {\n const loader = this.registry.get(compoundKey);\n if (!loader) return false;\n\n // Use the loader function's string representation as chunk key for deduplication\n // (multiple compound keys can share the same loader/chunk)\n const chunkKey = loader.toString();\n\n if (this.loadedChunks.has(chunkKey)) return true;\n\n // Deduplicate concurrent loads\n const pending = this.pendingLoads.get(chunkKey);\n if (pending) {\n await pending;\n return true;\n }\n\n const loadPromise = loader().then(() => {\n this.loadedChunks.add(chunkKey);\n this.pendingLoads.delete(chunkKey);\n });\n\n this.pendingLoads.set(chunkKey, loadPromise);\n await loadPromise;\n return true;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lazy-module-registry.js","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { MJGlobal } from '@memberjunction/global';\n\n/**\n * One lazy-loadable chunk: a stable identity plus the dynamic import that loads it.\n *\n * Many compound keys map to the same chunk, so `chunkId` is what the registry dedupes\n * loads by — it must uniquely identify the chunk. The generated LAZY_FEATURE_CONFIG\n * uses the dynamic import specifier (e.g. '@memberjunction/ng-dashboards/ai-dashboards.module').\n */\nexport interface LazyFeatureChunk {\n /** Stable, unique chunk identity. Also the label dev tools show for the chunk. */\n readonly chunkId: string;\n /** Imports the chunk, which runs the `@RegisterClass` decorators inside it. */\n readonly load: () => Promise<void>;\n}\n\n/** Extracts the subclass-key half of a `\"BaseClassName::Key\"` compound key. */\nfunction subclassKeyOf(compoundKey: string): string {\n const separator = compoundKey.indexOf('::');\n return separator < 0 ? compoundKey : compoundKey.slice(separator + 2);\n}\n\n/**\n * Registry that maps compound keys (BaseClassName::Key) to the chunk that provides them.\n *\n * Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`\n * or `CreateInstanceAsync()` call that misses synchronously will automatically trigger\n * the correct lazy chunk load. This replaces the previous pattern where individual\n * components (ResourceContainerComponent, TabContainerComponent, etc.) each had their\n * own retry logic.\n */\n@Injectable({ providedIn: 'root' })\nexport class LazyModuleRegistry {\n private registry = new Map<string, LazyFeatureChunk>();\n /**\n * Secondary index: subclass key → (compound key → chunk), ignoring the base-class half.\n *\n * Needed because the base-class half of a compound key is NOT stable at runtime. ClassFactory\n * builds the lookup from `baseClass.name`, but the emitted identifier varies by build mode —\n * for the same `BaseResourceComponent` we have observed `_BaseResourceComponent` (unminified,\n * Angular's named class expression) and `BaseResourceComponent2` (minified, esbuild's collision\n * rename) — while the generated LAZY_FEATURE_CONFIG keys use the TypeScript source name. Any\n * mismatch made the primary lookup miss, so NO chunk was ever imported and every lazily\n * provided class silently failed to resolve.\n *\n * The inner map is keyed by compound key (not a flat Set of chunks) so that re-registering a\n * compound key replaces its contribution here too, matching `registry`'s replace semantics.\n */\n private chunksBySubclassKey = new Map<string, Map<string, LazyFeatureChunk>>();\n /** chunkIds that have finished loading. */\n private loadedChunks = new Set<string>();\n /** In-flight loads, keyed by chunkId, so concurrent callers share one import. */\n private pendingLoads = new Map<string, Promise<void>>();\n\n /**\n * Register a single compound key with the chunk that provides it.\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n * @param chunk The chunk descriptor (stable id + dynamic import)\n */\n Register(compoundKey: string, chunk: LazyFeatureChunk): void {\n this.registry.set(compoundKey, chunk);\n const subclassKey = subclassKeyOf(compoundKey);\n const byCompoundKey = this.chunksBySubclassKey.get(subclassKey) ?? new Map<string, LazyFeatureChunk>();\n byCompoundKey.set(compoundKey, chunk);\n this.chunksBySubclassKey.set(subclassKey, byCompoundKey);\n }\n\n /**\n * Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).\n */\n RegisterBulk(mappings: Record<string, LazyFeatureChunk>): void {\n for (const [compoundKey, chunk] of Object.entries(mappings)) {\n this.Register(compoundKey, chunk);\n }\n }\n\n /**\n * Wires this registry to ClassFactory as a lazy loader.\n * After calling this, any `ClassFactory.GetRegistrationAsync()` or `CreateInstanceAsync()`\n * that fails to find a registration synchronously will call back into this registry\n * with the compound key (baseClassName::key) to trigger lazy loading.\n */\n WireToClassFactory(): void {\n MJGlobal.Instance.ClassFactory.RegisterLazyLoader(\n (baseClassName: string, key: string) => this.Load(`${baseClassName}::${key}`)\n );\n // Publish to a well-known global so introspection tools (e.g. the Admin\n // app's \"Lazy Loading\" inspector in ng-dashboards) can read the snapshot\n // without creating a hard package dependency on explorer-core. Dev tools only.\n (globalThis as { __mj_lazy_registry__?: LazyModuleRegistry }).__mj_lazy_registry__ = this;\n }\n\n /**\n * Read-only snapshot of the registry state — for diagnostic tools.\n * Groups compound keys by `chunkId` so inspectors can show \"X chunks, Y loaded\"\n * plus the keys covered by each.\n */\n GetSnapshot(): {\n registered: string[];\n loaded: string[];\n chunks: Array<{ chunkId: string; loaded: boolean; keys: string[] }>;\n /** How many chunks have finished loading. The total is `chunks.length`. */\n loadedChunkCount: number;\n } {\n const byChunk = new Map<string, string[]>();\n for (const [compoundKey, chunk] of this.registry.entries()) {\n const arr = byChunk.get(chunk.chunkId) ?? [];\n arr.push(compoundKey);\n byChunk.set(chunk.chunkId, arr);\n }\n\n const chunks = Array.from(byChunk.entries())\n .map(([chunkId, keys]) => ({\n chunkId,\n loaded: this.loadedChunks.has(chunkId),\n keys: keys.sort()\n }))\n .sort((a, b) => b.keys.length - a.keys.length);\n\n const loaded: string[] = [];\n for (const c of chunks) {\n if (c.loaded) loaded.push(...c.keys);\n }\n\n return {\n registered: Array.from(this.registry.keys()).sort(),\n loaded: loaded.sort(),\n chunks,\n loadedChunkCount: this.loadedChunks.size\n };\n }\n\n /**\n * Programmatically trigger a lazy chunk load by compound key. Returns true\n * on success. Useful for dev tools that want to \"preload\" a chunk.\n */\n async ForceLoad(compoundKey: string): Promise<boolean> {\n return this.Load(compoundKey);\n }\n\n /**\n * Attempt to lazy-load the chunk for a given compound key.\n * Returns true if the key was found in the registry and loaded.\n * Deduplicates concurrent loads of the same chunk.\n *\n * @param compoundKey Format: \"BaseClassName::Key\" (e.g., \"BaseResourceComponent::HomeDashboard\")\n */\n async Load(compoundKey: string): Promise<boolean> {\n // Primary: exact compound key. Correct and precise when the runtime base-class name\n // happens to match the generated config.\n const exact = this.registry.get(compoundKey);\n if (exact) {\n await this.loadChunk(exact);\n return true;\n }\n\n // Fallback: match on the subclass key alone, because the base-class half is not stable\n // across build modes (see `chunksBySubclassKey`). Subclass keys are unique per chunk in\n // practice; if several chunks ever claim one, load them all so resolution can't silently\n // pick wrong — ClassFactory still decides the winner by base class afterwards.\n const candidates = this.candidateChunksFor(subclassKeyOf(compoundKey));\n if (candidates.length === 0) return false;\n\n // One candidate failing must not skip the rest — a later one may hold the class. Only\n // surface a failure if NOTHING loaded, so a partial failure can't mask a success.\n const errors: unknown[] = [];\n let loaded = false;\n for (const chunk of candidates) {\n try {\n await this.loadChunk(chunk);\n loaded = true;\n } catch (error) {\n errors.push(error);\n }\n }\n if (!loaded) throw errors[0];\n return true;\n }\n\n /**\n * The distinct chunks (by `chunkId`) registered under a subclass key, in registration order.\n */\n private candidateChunksFor(subclassKey: string): LazyFeatureChunk[] {\n const byCompoundKey = this.chunksBySubclassKey.get(subclassKey);\n if (!byCompoundKey) return [];\n\n const distinct = new Map<string, LazyFeatureChunk>();\n for (const chunk of byCompoundKey.values()) {\n distinct.set(chunk.chunkId, chunk);\n }\n return Array.from(distinct.values());\n }\n\n /**\n * Imports a chunk once, sharing in-flight loads and remembering completed ones.\n * Resolves when the chunk is loaded; rejects if the import fails.\n */\n private async loadChunk(chunk: LazyFeatureChunk): Promise<void> {\n // Dedupe on the chunk's declared id — multiple compound keys share one chunk.\n // This MUST be a value that differs between chunks; deriving it from the loader\n // function (e.g. Function.toString()) collapses every chunk into one, so the first\n // chunk loaded makes all the others look already-loaded and their classes never register.\n const chunkKey = chunk.chunkId;\n\n if (this.loadedChunks.has(chunkKey)) return;\n\n // Deduplicate concurrent loads\n const pending = this.pendingLoads.get(chunkKey);\n if (pending) {\n await pending;\n return;\n }\n\n const loadPromise = chunk.load().then(\n () => {\n this.loadedChunks.add(chunkKey);\n this.pendingLoads.delete(chunkKey);\n },\n (error: unknown) => {\n // Drop the failed attempt so a later navigation retries. Chunk fetches fail\n // transiently in normal use — a dev-server rebuild renames every lazy chunk, so a\n // page loaded before the rebuild 404s once. Left in `pendingLoads`, the rejected\n // promise would be re-awaited (and re-thrown) forever, killing the chunk for the\n // rest of the page session. The rejection still propagates to this caller.\n this.pendingLoads.delete(chunkKey);\n throw error;\n }\n );\n\n this.pendingLoads.set(chunkKey, loadPromise);\n await loadPromise;\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventEmitter, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
|
1
|
+
import { EventEmitter, OnInit, OnDestroy, AfterViewInit, ChangeDetectorRef, ElementRef } from '@angular/core';
|
|
2
2
|
import { BaseApplication, NavItem, WorkspaceStateManager } from '@memberjunction/ng-base-application';
|
|
3
3
|
import { SharedService } from '@memberjunction/ng-shared';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
@@ -12,15 +12,21 @@ export interface NavItemClickEvent {
|
|
|
12
12
|
/**
|
|
13
13
|
* Horizontal navigation items for the current app.
|
|
14
14
|
* Uses OnPush change detection and reactive state management for optimal performance.
|
|
15
|
+
*
|
|
16
|
+
* Overflow behavior (priority+ pattern): when the header can't fit every nav item,
|
|
17
|
+
* trailing items collapse into a "More" dropdown instead of squeezing the header's
|
|
18
|
+
* action cluster off-screen. Item widths are measured once per nav-items change
|
|
19
|
+
* (all items render for one frame, widths are cached), then every resize recomputes
|
|
20
|
+
* the fit from the cache — no re-measure, no layout thrash.
|
|
15
21
|
*/
|
|
16
|
-
export declare class AppNavComponent implements OnInit, OnDestroy {
|
|
22
|
+
export declare class AppNavComponent implements OnInit, OnDestroy, AfterViewInit {
|
|
17
23
|
private workspaceManager;
|
|
18
24
|
private sharedService;
|
|
19
25
|
private cdr;
|
|
26
|
+
private host;
|
|
20
27
|
private destroy$;
|
|
21
28
|
private _app;
|
|
22
29
|
private _cachedNavItems;
|
|
23
|
-
private _cachedAppColor;
|
|
24
30
|
private _servicesInjected;
|
|
25
31
|
/**
|
|
26
32
|
* Monotonically increasing counter used to detect and discard stale async results.
|
|
@@ -38,20 +44,93 @@ export declare class AppNavComponent implements OnInit, OnDestroy {
|
|
|
38
44
|
*/
|
|
39
45
|
private _updateGeneration;
|
|
40
46
|
private activeStateMap;
|
|
47
|
+
/** Cached natural pixel width of each nav item, parallel to navItems order */
|
|
48
|
+
private itemWidths;
|
|
49
|
+
/** Measured width of the More button (fallback used until first render) */
|
|
50
|
+
private moreBtnWidth;
|
|
51
|
+
/** Left offset (px, relative to host) where the More dropdown anchors */
|
|
52
|
+
MoreDropdownLeft: number;
|
|
53
|
+
/** True when even one item + More can't fit — the lone visible pill
|
|
54
|
+
* shrinks with an ellipsis instead of clipping the More button. */
|
|
55
|
+
Tight: boolean;
|
|
56
|
+
/** Flex gap between nav items — measured from the rendered row (falls back
|
|
57
|
+
* to --mj-space-1's default of 4px until first measurement) */
|
|
58
|
+
private itemGap;
|
|
59
|
+
/** How many leading nav items are currently visible inline */
|
|
60
|
+
VisibleCount: number;
|
|
61
|
+
/** Whether the More dropdown is open */
|
|
62
|
+
MoreOpen: boolean;
|
|
63
|
+
/** True while nav items are being (re)loaded for skeleton display */
|
|
64
|
+
Loading: boolean;
|
|
65
|
+
private resizeObserver;
|
|
66
|
+
private _overflowEnabled;
|
|
67
|
+
private navListRef?;
|
|
68
|
+
private moreBtnRef?;
|
|
41
69
|
navItemClick: EventEmitter<NavItemClickEvent>;
|
|
42
70
|
navItemDismiss: EventEmitter<NavItem>;
|
|
43
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Disable the priority+ overflow behavior. Used by the mobile drawer instance,
|
|
73
|
+
* where items stack vertically and can never overflow horizontally.
|
|
74
|
+
*/
|
|
75
|
+
set OverflowEnabled(value: boolean);
|
|
76
|
+
get OverflowEnabled(): boolean;
|
|
77
|
+
constructor(workspaceManager: WorkspaceStateManager, sharedService: SharedService, cdr: ChangeDetectorRef, host: ElementRef<HTMLElement>);
|
|
44
78
|
/**
|
|
45
79
|
* Input setter for app - triggers cache update when app changes
|
|
46
80
|
*/
|
|
47
81
|
set app(value: BaseApplication | null);
|
|
48
82
|
get app(): BaseApplication | null;
|
|
49
83
|
ngOnInit(): void;
|
|
84
|
+
ngAfterViewInit(): void;
|
|
50
85
|
ngOnDestroy(): void;
|
|
51
86
|
/**
|
|
52
87
|
* Update cached nav items and app color when app changes
|
|
53
88
|
*/
|
|
54
89
|
private updateCachedData;
|
|
90
|
+
/**
|
|
91
|
+
* Cache the natural width of every nav item. Called in the frame after a new
|
|
92
|
+
* item set renders (all items visible, flex-shrink: 0, so widths are natural
|
|
93
|
+
* even if the row is clipped by overflow: hidden).
|
|
94
|
+
*/
|
|
95
|
+
private measureItemWidths;
|
|
96
|
+
/**
|
|
97
|
+
* Decide how many items fit in the host's current width; the rest collapse
|
|
98
|
+
* into the More dropdown. Pure arithmetic over cached widths — safe to call
|
|
99
|
+
* on every resize.
|
|
100
|
+
*/
|
|
101
|
+
private recomputeFit;
|
|
102
|
+
/**
|
|
103
|
+
* Self-heal for the item-width cache: if it's stale or missing (the
|
|
104
|
+
* measurement frame can race the first render after a reload/app switch),
|
|
105
|
+
* expand to the full item set, re-measure, and retry next frame until it
|
|
106
|
+
* converges. Returns true when the cache matches the current item set.
|
|
107
|
+
*/
|
|
108
|
+
private ensureFreshWidthCache;
|
|
109
|
+
/** Pure fit arithmetic: how many leading items fit beside the More button */
|
|
110
|
+
private computeFitCount;
|
|
111
|
+
/** Commit a fit result, closing More if nothing overflows; CD only on change */
|
|
112
|
+
private applyFit;
|
|
113
|
+
/**
|
|
114
|
+
* markForCheck + synchronous detectChanges. In Angular 21 zoneless mode,
|
|
115
|
+
* markForCheck() alone is unreliable when the trigger is an RxJS
|
|
116
|
+
* subscription or rAF callback not tracked by the zoneless scheduler — the
|
|
117
|
+
* dirty flag is set but no follow-up tick is scheduled. detectChanges()
|
|
118
|
+
* throws if invoked re-entrantly during an in-flight CD pass; that's
|
|
119
|
+
* harmless (the in-flight pass picks up our markForCheck), hence the catch.
|
|
120
|
+
*/
|
|
121
|
+
private syncChangeDetection;
|
|
122
|
+
/** Items rendered inline in the header row */
|
|
123
|
+
get InlineItems(): NavItem[];
|
|
124
|
+
/** Items collapsed into the More dropdown */
|
|
125
|
+
get OverflowItems(): NavItem[];
|
|
126
|
+
/** True when any overflowed item is the active one (More button shows active tint) */
|
|
127
|
+
get OverflowHasActive(): boolean;
|
|
128
|
+
/** Open/close the More dropdown, anchoring it under the More button and clamping it inside the host */
|
|
129
|
+
ToggleMore(event: MouseEvent): void;
|
|
130
|
+
/** Close the More dropdown on any outside click or Escape */
|
|
131
|
+
onDocumentClick(event: MouseEvent): void;
|
|
132
|
+
/** Close the More dropdown on Escape */
|
|
133
|
+
onEscape(): void;
|
|
55
134
|
/**
|
|
56
135
|
* Update active state map based on current workspace configuration
|
|
57
136
|
*/
|
|
@@ -73,10 +152,6 @@ export declare class AppNavComponent implements OnInit, OnDestroy {
|
|
|
73
152
|
* Get cached navigation items (no computation in getter)
|
|
74
153
|
*/
|
|
75
154
|
get navItems(): NavItem[];
|
|
76
|
-
/**
|
|
77
|
-
* Get cached app color (no computation in getter)
|
|
78
|
-
*/
|
|
79
|
-
get appColor(): string;
|
|
80
155
|
/**
|
|
81
156
|
* Check if nav item is active (uses cached state from Map)
|
|
82
157
|
*/
|
|
@@ -96,6 +171,6 @@ export declare class AppNavComponent implements OnInit, OnDestroy {
|
|
|
96
171
|
*/
|
|
97
172
|
onDismiss(item: NavItem, event: MouseEvent): void;
|
|
98
173
|
static ɵfac: i0.ɵɵFactoryDeclaration<AppNavComponent, never>;
|
|
99
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<AppNavComponent, "mj-app-nav", never, { "app": { "alias": "app"; "required": false; }; }, { "navItemClick": "navItemClick"; "navItemDismiss": "navItemDismiss"; }, never, never, false, never>;
|
|
174
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<AppNavComponent, "mj-app-nav", never, { "OverflowEnabled": { "alias": "OverflowEnabled"; "required": false; }; "app": { "alias": "app"; "required": false; }; }, { "navItemClick": "navItemClick"; "navItemDismiss": "navItemDismiss"; }, never, never, false, never>;
|
|
100
175
|
}
|
|
101
176
|
//# sourceMappingURL=app-nav.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-nav.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-nav.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAA2B,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"app-nav.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-nav.component.ts"],"names":[],"mappings":"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"}
|