@memberjunction/ng-dashboards 5.36.0 → 5.38.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 +32 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +14 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +450 -292
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.d.ts +73 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.js +512 -127
- package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
- package/dist/ComponentStudio/component-studio-resource.component.d.ts +22 -0
- package/dist/ComponentStudio/component-studio-resource.component.d.ts.map +1 -0
- package/dist/ComponentStudio/component-studio-resource.component.js +55 -0
- package/dist/ComponentStudio/component-studio-resource.component.js.map +1 -0
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +104 -45
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +234 -331
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.d.ts +54 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.js +339 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.js.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.d.ts +65 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.js +492 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.js.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.d.ts +88 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.js +457 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.js.map +1 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts +106 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.js +478 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.js.map +1 -0
- package/dist/ComponentStudio/components/workspace/component-preview.component.d.ts +54 -0
- package/dist/ComponentStudio/components/workspace/component-preview.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/workspace/component-preview.component.js +361 -50
- package/dist/ComponentStudio/components/workspace/component-preview.component.js.map +1 -1
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.d.ts +10 -0
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.js +114 -45
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.js.map +1 -1
- package/dist/ComponentStudio/services/canvas-to-code.d.ts +32 -0
- package/dist/ComponentStudio/services/canvas-to-code.d.ts.map +1 -0
- package/dist/ComponentStudio/services/canvas-to-code.js +347 -0
- package/dist/ComponentStudio/services/canvas-to-code.js.map +1 -0
- package/dist/ComponentStudio/services/code-to-canvas.d.ts +32 -0
- package/dist/ComponentStudio/services/code-to-canvas.d.ts.map +1 -0
- package/dist/ComponentStudio/services/code-to-canvas.js +92 -0
- package/dist/ComponentStudio/services/code-to-canvas.js.map +1 -0
- package/dist/ComponentStudio/services/component-studio-state.service.d.ts +29 -0
- package/dist/ComponentStudio/services/component-studio-state.service.d.ts.map +1 -1
- package/dist/ComponentStudio/services/component-studio-state.service.js +76 -0
- package/dist/ComponentStudio/services/component-studio-state.service.js.map +1 -1
- package/dist/ComponentStudio/services/entity-form-override.service.d.ts +86 -0
- package/dist/ComponentStudio/services/entity-form-override.service.d.ts.map +1 -0
- package/dist/ComponentStudio/services/entity-form-override.service.js +246 -0
- package/dist/ComponentStudio/services/entity-form-override.service.js.map +1 -0
- package/dist/ComponentStudio/services/field-binding-scanner.d.ts +29 -0
- package/dist/ComponentStudio/services/field-binding-scanner.d.ts.map +1 -0
- package/dist/ComponentStudio/services/field-binding-scanner.js +110 -0
- package/dist/ComponentStudio/services/field-binding-scanner.js.map +1 -0
- package/dist/ComponentStudio/services/form-canvas-model.d.ts +56 -0
- package/dist/ComponentStudio/services/form-canvas-model.d.ts.map +1 -0
- package/dist/ComponentStudio/services/form-canvas-model.js +35 -0
- package/dist/ComponentStudio/services/form-canvas-model.js.map +1 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.d.ts +10 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.d.ts.map +1 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.js +10 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.js.map +1 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.js +2 -2
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.d.ts +964 -0
- package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -0
- package/dist/FormBuilder/form-builder-resource.component.js +4487 -0
- package/dist/FormBuilder/form-builder-resource.component.js.map +1 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.d.ts +55 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.d.ts.map +1 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.js +73 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.js.map +1 -0
- package/dist/Home/home-application.d.ts +21 -1
- package/dist/Home/home-application.d.ts.map +1 -1
- package/dist/Home/home-application.js +60 -8
- package/dist/Home/home-application.js.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.d.ts +14 -14
- package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js +11 -10
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/component-studio-dashboards.module.d.ts +34 -22
- package/dist/component-studio-dashboards.module.d.ts.map +1 -1
- package/dist/component-studio-dashboards.module.js +65 -9
- package/dist/component-studio-dashboards.module.js.map +1 -1
- package/package.json +54 -53
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the Form Builder cockpit's Version Rail.
|
|
3
|
+
*
|
|
4
|
+
* The rail's data load is a two-call dance:
|
|
5
|
+
* 1. Components by Name lineage (returns all versions for the active form)
|
|
6
|
+
* 2. Overrides by ComponentID-IN-list (for flagging Active / Pending per user)
|
|
7
|
+
*
|
|
8
|
+
* The join is small but easy to break in subtle ways. Extracting it as a
|
|
9
|
+
* pure function lets us unit-test the IsActive / IsPending flagging
|
|
10
|
+
* without booting Angular's DOM machinery. Retrospective fix #12.
|
|
11
|
+
*/
|
|
12
|
+
/** Shape of a `MJ: Components` simple-row result the rail consumes. */
|
|
13
|
+
export interface ComponentRailRow {
|
|
14
|
+
ID: string;
|
|
15
|
+
Name: string;
|
|
16
|
+
Version: string;
|
|
17
|
+
VersionSequence: number;
|
|
18
|
+
Status: string;
|
|
19
|
+
__mj_UpdatedAt: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** Shape of a `MJ: Entity Form Overrides` simple-row result the rail consumes. */
|
|
22
|
+
export interface OverrideRailRow {
|
|
23
|
+
ComponentID: string;
|
|
24
|
+
Status: string;
|
|
25
|
+
}
|
|
26
|
+
/** Final shape rendered by the Version Rail. */
|
|
27
|
+
export interface ComponentVersionRow {
|
|
28
|
+
ID: string;
|
|
29
|
+
Name: string;
|
|
30
|
+
Version: string;
|
|
31
|
+
VersionSequence: number;
|
|
32
|
+
Status: string;
|
|
33
|
+
UpdatedAt: Date | null;
|
|
34
|
+
/** True when the user's Active override currently points at this Component. */
|
|
35
|
+
IsActive: boolean;
|
|
36
|
+
/** True when the user has a Pending override pointing at this Component. */
|
|
37
|
+
IsPending: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Join Components-by-Name results against per-user Overrides to produce
|
|
41
|
+
* the final rail rows. The Overrides query is expected to be pre-filtered
|
|
42
|
+
* to the calling user. When more than one override row points at the
|
|
43
|
+
* same Component (e.g. after a Restore repoints the Active override at
|
|
44
|
+
* a previously-Inactive Component's lineage), we collapse using the
|
|
45
|
+
* Active > Pending > Inactive precedence — highest rank wins.
|
|
46
|
+
*/
|
|
47
|
+
export declare function joinVersionsWithOverrides(components: ComponentRailRow[], overrides: OverrideRailRow[]): ComponentVersionRow[];
|
|
48
|
+
/**
|
|
49
|
+
* Pick the version ID the rail should highlight as "currently active". Falls
|
|
50
|
+
* back to the highest-VersionSequence row when no override flags Active —
|
|
51
|
+
* common case for a form that's still in draft Pending state without an
|
|
52
|
+
* Active override yet.
|
|
53
|
+
*/
|
|
54
|
+
export declare function pickActiveVersionID(rows: ComponentVersionRow[]): string | null;
|
|
55
|
+
//# sourceMappingURL=form-builder-version-rail.helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-builder-version-rail.helpers.d.ts","sourceRoot":"","sources":["../../src/FormBuilder/form-builder-version-rail.helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uEAAuE;AACvE,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAC;CACtB;AAiBD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,gBAAgB,EAAE,EAC9B,SAAS,EAAE,eAAe,EAAE,GAC7B,mBAAmB,EAAE,CAuBvB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,EAAE,GAAG,MAAM,GAAG,IAAI,CAM9E"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the Form Builder cockpit's Version Rail.
|
|
3
|
+
*
|
|
4
|
+
* The rail's data load is a two-call dance:
|
|
5
|
+
* 1. Components by Name lineage (returns all versions for the active form)
|
|
6
|
+
* 2. Overrides by ComponentID-IN-list (for flagging Active / Pending per user)
|
|
7
|
+
*
|
|
8
|
+
* The join is small but easy to break in subtle ways. Extracting it as a
|
|
9
|
+
* pure function lets us unit-test the IsActive / IsPending flagging
|
|
10
|
+
* without booting Angular's DOM machinery. Retrospective fix #12.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Rank for collapsing multiple override rows targeting the same Component.
|
|
14
|
+
* Higher wins. Comes up in the Restore-path: when an older version is
|
|
15
|
+
* promoted back to Active, the existing Active override is repointed at
|
|
16
|
+
* that older Component, leaving the original Inactive override row in
|
|
17
|
+
* place ALSO pointing at the same Component. The rail must report
|
|
18
|
+
* "Active" for that Component, not "Inactive" (last-write-wins on a
|
|
19
|
+
* naive Map would corrupt the display).
|
|
20
|
+
*/
|
|
21
|
+
const OVERRIDE_STATUS_RANK = {
|
|
22
|
+
Active: 3,
|
|
23
|
+
Pending: 2,
|
|
24
|
+
Inactive: 1,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Join Components-by-Name results against per-user Overrides to produce
|
|
28
|
+
* the final rail rows. The Overrides query is expected to be pre-filtered
|
|
29
|
+
* to the calling user. When more than one override row points at the
|
|
30
|
+
* same Component (e.g. after a Restore repoints the Active override at
|
|
31
|
+
* a previously-Inactive Component's lineage), we collapse using the
|
|
32
|
+
* Active > Pending > Inactive precedence — highest rank wins.
|
|
33
|
+
*/
|
|
34
|
+
export function joinVersionsWithOverrides(components, overrides) {
|
|
35
|
+
const overrideByComponent = new Map();
|
|
36
|
+
for (const o of overrides) {
|
|
37
|
+
const prev = overrideByComponent.get(o.ComponentID);
|
|
38
|
+
const prevRank = prev ? (OVERRIDE_STATUS_RANK[prev] ?? 0) : -1;
|
|
39
|
+
const currRank = OVERRIDE_STATUS_RANK[o.Status] ?? 0;
|
|
40
|
+
if (currRank > prevRank) {
|
|
41
|
+
overrideByComponent.set(o.ComponentID, o.Status);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return components.map(c => {
|
|
45
|
+
const status = overrideByComponent.get(c.ID);
|
|
46
|
+
return {
|
|
47
|
+
ID: c.ID,
|
|
48
|
+
Name: c.Name,
|
|
49
|
+
Version: c.Version,
|
|
50
|
+
VersionSequence: c.VersionSequence,
|
|
51
|
+
Status: c.Status,
|
|
52
|
+
UpdatedAt: c.__mj_UpdatedAt ? new Date(c.__mj_UpdatedAt) : null,
|
|
53
|
+
IsActive: status === 'Active',
|
|
54
|
+
IsPending: status === 'Pending',
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Pick the version ID the rail should highlight as "currently active". Falls
|
|
60
|
+
* back to the highest-VersionSequence row when no override flags Active —
|
|
61
|
+
* common case for a form that's still in draft Pending state without an
|
|
62
|
+
* Active override yet.
|
|
63
|
+
*/
|
|
64
|
+
export function pickActiveVersionID(rows) {
|
|
65
|
+
const active = rows.find(r => r.IsActive);
|
|
66
|
+
if (active)
|
|
67
|
+
return active.ID;
|
|
68
|
+
if (rows.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
// Highest VersionSequence — rows arrive ordered DESC; return the first.
|
|
71
|
+
return rows[0].ID;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=form-builder-version-rail.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-builder-version-rail.helpers.js","sourceRoot":"","sources":["../../src/FormBuilder/form-builder-version-rail.helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgCH;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAA2B;IACjD,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;CACd,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CACrC,UAA8B,EAC9B,SAA4B;IAE5B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,oBAAoB,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC;YACtB,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC;IACL,CAAC;IACD,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QACtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7C,OAAO;YACH,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,SAAS,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI;YAC/D,QAAQ,EAAE,MAAM,KAAK,QAAQ;YAC7B,SAAS,EAAE,MAAM,KAAK,SAAS;SAClC,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAA2B;IAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,EAAE,CAAC;IAC7B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,wEAAwE;IACxE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACtB,CAAC","sourcesContent":["/**\n * Pure helpers for the Form Builder cockpit's Version Rail.\n *\n * The rail's data load is a two-call dance:\n * 1. Components by Name lineage (returns all versions for the active form)\n * 2. Overrides by ComponentID-IN-list (for flagging Active / Pending per user)\n *\n * The join is small but easy to break in subtle ways. Extracting it as a\n * pure function lets us unit-test the IsActive / IsPending flagging\n * without booting Angular's DOM machinery. Retrospective fix #12.\n */\n\n/** Shape of a `MJ: Components` simple-row result the rail consumes. */\nexport interface ComponentRailRow {\n ID: string;\n Name: string;\n Version: string;\n VersionSequence: number;\n Status: string;\n __mj_UpdatedAt: string | null;\n}\n\n/** Shape of a `MJ: Entity Form Overrides` simple-row result the rail consumes. */\nexport interface OverrideRailRow {\n ComponentID: string;\n Status: string;\n}\n\n/** Final shape rendered by the Version Rail. */\nexport interface ComponentVersionRow {\n ID: string;\n Name: string;\n Version: string;\n VersionSequence: number;\n Status: string;\n UpdatedAt: Date | null;\n /** True when the user's Active override currently points at this Component. */\n IsActive: boolean;\n /** True when the user has a Pending override pointing at this Component. */\n IsPending: boolean;\n}\n\n/**\n * Rank for collapsing multiple override rows targeting the same Component.\n * Higher wins. Comes up in the Restore-path: when an older version is\n * promoted back to Active, the existing Active override is repointed at\n * that older Component, leaving the original Inactive override row in\n * place ALSO pointing at the same Component. The rail must report\n * \"Active\" for that Component, not \"Inactive\" (last-write-wins on a\n * naive Map would corrupt the display).\n */\nconst OVERRIDE_STATUS_RANK: Record<string, number> = {\n Active: 3,\n Pending: 2,\n Inactive: 1,\n};\n\n/**\n * Join Components-by-Name results against per-user Overrides to produce\n * the final rail rows. The Overrides query is expected to be pre-filtered\n * to the calling user. When more than one override row points at the\n * same Component (e.g. after a Restore repoints the Active override at\n * a previously-Inactive Component's lineage), we collapse using the\n * Active > Pending > Inactive precedence — highest rank wins.\n */\nexport function joinVersionsWithOverrides(\n components: ComponentRailRow[],\n overrides: OverrideRailRow[],\n): ComponentVersionRow[] {\n const overrideByComponent = new Map<string, string>();\n for (const o of overrides) {\n const prev = overrideByComponent.get(o.ComponentID);\n const prevRank = prev ? (OVERRIDE_STATUS_RANK[prev] ?? 0) : -1;\n const currRank = OVERRIDE_STATUS_RANK[o.Status] ?? 0;\n if (currRank > prevRank) {\n overrideByComponent.set(o.ComponentID, o.Status);\n }\n }\n return components.map(c => {\n const status = overrideByComponent.get(c.ID);\n return {\n ID: c.ID,\n Name: c.Name,\n Version: c.Version,\n VersionSequence: c.VersionSequence,\n Status: c.Status,\n UpdatedAt: c.__mj_UpdatedAt ? new Date(c.__mj_UpdatedAt) : null,\n IsActive: status === 'Active',\n IsPending: status === 'Pending',\n };\n });\n}\n\n/**\n * Pick the version ID the rail should highlight as \"currently active\". Falls\n * back to the highest-VersionSequence row when no override flags Active —\n * common case for a form that's still in draft Pending state without an\n * Active override yet.\n */\nexport function pickActiveVersionID(rows: ComponentVersionRow[]): string | null {\n const active = rows.find(r => r.IsActive);\n if (active) return active.ID;\n if (rows.length === 0) return null;\n // Highest VersionSequence — rows arrive ordered DESC; return the first.\n return rows[0].ID;\n}\n"]}
|
|
@@ -25,11 +25,16 @@ export declare class HomeApplication extends BaseApplication {
|
|
|
25
25
|
private recentOrphanStack;
|
|
26
26
|
private _storageLoadPromise;
|
|
27
27
|
/**
|
|
28
|
-
* Fingerprint of the last-processed active tab state (tabId::resourceRecordId).
|
|
28
|
+
* Fingerprint of the last-processed active tab state (tabId::resourceRecordId::title).
|
|
29
29
|
* Guards against redundant stack mutations while still detecting content changes.
|
|
30
30
|
* In single-resource mode the tab ID stays constant when navigating between records
|
|
31
31
|
* (OpenTab replaces the temp tab's content but keeps its ID), so we must also
|
|
32
32
|
* include the resourceRecordId to detect when the tab shows a different record.
|
|
33
|
+
*
|
|
34
|
+
* Title is also included so an in-place display-name change (e.g. user edits a
|
|
35
|
+
* record's Name field and saves — tabId and resourceRecordId stay the same but the
|
|
36
|
+
* title changes) triggers a re-evaluation. Without this, the dynamic nav item keeps
|
|
37
|
+
* showing the pre-edit name forever.
|
|
33
38
|
*/
|
|
34
39
|
private _lastSeenTabFingerprint;
|
|
35
40
|
/**
|
|
@@ -74,6 +79,21 @@ export declare class HomeApplication extends BaseApplication {
|
|
|
74
79
|
* between existing dynamic nav items.
|
|
75
80
|
*/
|
|
76
81
|
private updateRecentStack;
|
|
82
|
+
/**
|
|
83
|
+
* Refreshes an already-tracked snapshot's `resolvedLabel` against the latest cached
|
|
84
|
+
* record name. Called unconditionally at the top of `updateRecentStack` (BEFORE the
|
|
85
|
+
* fingerprint bail), so even no-op-fingerprint calls don't render stale labels.
|
|
86
|
+
*
|
|
87
|
+
* Why this matters: when a record is saved, `configuration$` typically emits multiple
|
|
88
|
+
* times in quick succession (UpdateTabTitle, UpdateConfiguration, etc.). AppNav's
|
|
89
|
+
* subscription kicks off a `updateCachedData` for each. The first call wins the
|
|
90
|
+
* fingerprint race and starts the slow async `createSnapshotIfOrphan` chain;
|
|
91
|
+
* subsequent calls bail at fingerprint and race ahead with the still-unrefreshed
|
|
92
|
+
* snapshot. AppNav's `_updateGeneration` then discards the slow call's results.
|
|
93
|
+
* Without this method, the freshly-saved label lands in the snapshot — but never
|
|
94
|
+
* makes it to the rendered nav until some unrelated event triggers another cycle.
|
|
95
|
+
*/
|
|
96
|
+
private refreshExistingSnapshotLabel;
|
|
77
97
|
/**
|
|
78
98
|
* Creates an OrphanResourceSnapshot from a tab if it qualifies as an orphan resource.
|
|
79
99
|
* Returns null if the tab is a static nav item, has no resource type, or is an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-application.d.ts","sourceRoot":"","sources":["../../src/Home/home-application.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAkB,OAAO,EAAE,qBAAqB,EAAgB,MAAM,qCAAqC,CAAC;AACpI,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAiC1D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,eAAgB,SAAQ,eAAe;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAE7C,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,mBAAmB,CAA8B;IAEzD
|
|
1
|
+
{"version":3,"file":"home-application.d.ts","sourceRoot":"","sources":["../../src/Home/home-application.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAkB,OAAO,EAAE,qBAAqB,EAAgB,MAAM,qCAAqC,CAAC;AACpI,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAiC1D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,eAAgB,SAAQ,eAAe;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAE7C,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,mBAAmB,CAA8B;IAEzD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,uBAAuB,CAAuB;IAEtD;;;OAGG;IACI,mBAAmB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAOhE;;OAEG;IACI,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAIrD;;;;OAIG;IACY,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAwBhD;;;;OAIG;IACI,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAiBhD;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAe7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAuBnB;;;;;;OAMG;YACW,iBAAiB;IAqE/B;;;;;;;;;;;;;OAaG;YACW,4BAA4B;IAsB1C;;;;OAIG;YACW,sBAAsB;IAgCpC;;OAEG;YACW,oBAAoB;IAoBlC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAsB5B;;;;;;;;;;OAUG;YACW,kBAAkB;IAwBhC;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;;OAGG;IACH,OAAO,CAAC,SAAS;IAiBjB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA4B1B;;;OAGG;YACW,oBAAoB;CAsBnC"}
|
|
@@ -39,11 +39,16 @@ let HomeApplication = class HomeApplication extends BaseApplication {
|
|
|
39
39
|
recentOrphanStack = [];
|
|
40
40
|
_storageLoadPromise = null;
|
|
41
41
|
/**
|
|
42
|
-
* Fingerprint of the last-processed active tab state (tabId::resourceRecordId).
|
|
42
|
+
* Fingerprint of the last-processed active tab state (tabId::resourceRecordId::title).
|
|
43
43
|
* Guards against redundant stack mutations while still detecting content changes.
|
|
44
44
|
* In single-resource mode the tab ID stays constant when navigating between records
|
|
45
45
|
* (OpenTab replaces the temp tab's content but keeps its ID), so we must also
|
|
46
46
|
* include the resourceRecordId to detect when the tab shows a different record.
|
|
47
|
+
*
|
|
48
|
+
* Title is also included so an in-place display-name change (e.g. user edits a
|
|
49
|
+
* record's Name field and saves — tabId and resourceRecordId stay the same but the
|
|
50
|
+
* title changes) triggers a re-evaluation. Without this, the dynamic nav item keeps
|
|
51
|
+
* showing the pre-edit name forever.
|
|
47
52
|
*/
|
|
48
53
|
_lastSeenTabFingerprint = null;
|
|
49
54
|
/**
|
|
@@ -178,10 +183,24 @@ let HomeApplication = class HomeApplication extends BaseApplication {
|
|
|
178
183
|
if (!activeTab || !UUIDsEqual(activeTab.applicationId, this.ID)) {
|
|
179
184
|
return;
|
|
180
185
|
}
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
186
|
+
// ALWAYS refresh the matching snapshot's label first — BEFORE the fingerprint
|
|
187
|
+
// bail. A burst of configuration$ emissions (typical on save) causes multiple
|
|
188
|
+
// concurrent updateCachedData calls; the first one proceeds past the fingerprint
|
|
189
|
+
// check and gets stuck awaiting createSnapshotIfOrphan, while the rest bail at
|
|
190
|
+
// fingerprint and race ahead with the *stale* snapshot. By the time the first
|
|
191
|
+
// call's async work completes and updates the snapshot, the gen-check in
|
|
192
|
+
// AppNav.updateCachedData has already discarded its results. The user is left
|
|
193
|
+
// with the right data in memory but rendered against the wrong label.
|
|
194
|
+
//
|
|
195
|
+
// Refreshing here is cheap (cache hit after my save-handler's pre-warm) and
|
|
196
|
+
// ensures every call — even fast bailers — produces fresh dynamic nav items.
|
|
197
|
+
await this.refreshExistingSnapshotLabel(activeTab);
|
|
198
|
+
// Build a fingerprint from tab ID + record content + title. In single-resource
|
|
199
|
+
// mode, OpenTab replaces the temp tab's content but keeps the same tab ID, so we
|
|
200
|
+
// need the resourceRecordId to detect when a different record is shown. Title is
|
|
201
|
+
// included so an in-place rename (record's Name field edited and saved — tabId
|
|
202
|
+
// and resourceRecordId stay the same but title changes) also triggers a refresh.
|
|
203
|
+
const fingerprint = `${activeTab.id}::${activeTab.resourceRecordId ?? ''}::${activeTab.title ?? ''}`;
|
|
185
204
|
if (fingerprint === this._lastSeenTabFingerprint) {
|
|
186
205
|
return;
|
|
187
206
|
}
|
|
@@ -192,9 +211,13 @@ let HomeApplication = class HomeApplication extends BaseApplication {
|
|
|
192
211
|
// Active tab is a static resource (Home dashboard, etc.) — leave stack as-is
|
|
193
212
|
return;
|
|
194
213
|
}
|
|
195
|
-
// If this item is already in the stack, leave the
|
|
196
|
-
//
|
|
197
|
-
//
|
|
214
|
+
// If this item is already in the stack, leave the ORDER unchanged (reordering
|
|
215
|
+
// on every click is visually jarring) — but DO refresh its `resolvedLabel` if the
|
|
216
|
+
// underlying display name has changed. Without this, editing a record's Name
|
|
217
|
+
// field would leave the dynamic nav item showing the pre-edit name forever.
|
|
218
|
+
// Already-tracked snapshots are kept in their existing position (no jarring
|
|
219
|
+
// reorder) and have their label kept fresh by refreshExistingSnapshotLabel
|
|
220
|
+
// above — so here we only need to handle the NEW-snapshot case.
|
|
198
221
|
const alreadyTracked = this.recentOrphanStack.some(s => s.resourceRecordId === snapshot.resourceRecordId);
|
|
199
222
|
if (alreadyTracked) {
|
|
200
223
|
return;
|
|
@@ -206,6 +229,35 @@ let HomeApplication = class HomeApplication extends BaseApplication {
|
|
|
206
229
|
}
|
|
207
230
|
this.saveStackToStorage();
|
|
208
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Refreshes an already-tracked snapshot's `resolvedLabel` against the latest cached
|
|
234
|
+
* record name. Called unconditionally at the top of `updateRecentStack` (BEFORE the
|
|
235
|
+
* fingerprint bail), so even no-op-fingerprint calls don't render stale labels.
|
|
236
|
+
*
|
|
237
|
+
* Why this matters: when a record is saved, `configuration$` typically emits multiple
|
|
238
|
+
* times in quick succession (UpdateTabTitle, UpdateConfiguration, etc.). AppNav's
|
|
239
|
+
* subscription kicks off a `updateCachedData` for each. The first call wins the
|
|
240
|
+
* fingerprint race and starts the slow async `createSnapshotIfOrphan` chain;
|
|
241
|
+
* subsequent calls bail at fingerprint and race ahead with the still-unrefreshed
|
|
242
|
+
* snapshot. AppNav's `_updateGeneration` then discards the slow call's results.
|
|
243
|
+
* Without this method, the freshly-saved label lands in the snapshot — but never
|
|
244
|
+
* makes it to the rendered nav until some unrelated event triggers another cycle.
|
|
245
|
+
*/
|
|
246
|
+
async refreshExistingSnapshotLabel(activeTab) {
|
|
247
|
+
if (!activeTab.resourceRecordId) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const existing = this.recentOrphanStack.find(s => s.resourceRecordId === activeTab.resourceRecordId);
|
|
251
|
+
if (!existing || !existing.entityName) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const newLabel = await this.resolveRecordLabel(existing.entityName, activeTab.resourceRecordId, activeTab.title);
|
|
255
|
+
if (newLabel && newLabel !== existing.resolvedLabel) {
|
|
256
|
+
existing.resolvedLabel = newLabel;
|
|
257
|
+
existing.title = activeTab.title;
|
|
258
|
+
this.saveStackToStorage();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
209
261
|
/**
|
|
210
262
|
* Creates an OrphanResourceSnapshot from a tab if it qualifies as an orphan resource.
|
|
211
263
|
* Returns null if the tab is a static nav item, has no resource type, or is an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-application.js","sourceRoot":"","sources":["../../src/Home/home-application.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAgE,MAAM,qCAAqC,CAAC;AAEpI,OAAO,EAAY,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEpF,sEAAsE;AACtE,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAChD,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAyB3C,+CAA+C;AAC/C,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,eAAe;;IAC1C,MAAM,CAAU,gBAAgB,GAAG,CAAC,CAAC;IAErC,gBAAgB,GAAiC,IAAI,CAAC;IACtD,aAAa,GAAyB,IAAI,CAAC;IAC3C,iBAAiB,GAA6B,EAAE,CAAC;IACjD,mBAAmB,GAAyB,IAAI,CAAC;IAEzD;;;;;;OAMG;IACK,uBAAuB,GAAkB,IAAI,CAAC;IAEtD;;;OAGG;IACI,mBAAmB,CAAC,OAA8B;QACvD,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC9B,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;QACzD,CAAC;IACH,CAAC;IAED;;OAEG;IACI,gBAAgB,CAAC,OAAsB;QAC5C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACM,KAAK,CAAC,WAAW;QACxB,8DAA8D;QAC9D,iEAAiE;QACjE,qEAAqE;QACrE,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,mBAAmB,CAAC;QACjC,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC;QAE9C,sDAAsD;QACtD,6FAA6F;QAC7F,8FAA8F;QAC9F,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAE/B,yCAAyC;QACzC,MAAM,YAAY,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;QACjD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,YAAY,CAAC,CAAC;QAC3C,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACI,oBAAoB,CAAC,IAAa;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAC5C,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,KAAK,IAAI,CAAC,QAAQ,CAC1C,CAAC;QACF,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAE1B,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,IAAa;QACzC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,CAAC;QACxD,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,WAAW,CAAC,CAAC;QACrE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,SAAS,CAAC,gBAAgB,KAAK,IAAI,CAAC,QAAQ,CAAC;IACtD,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,gBAAgB,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;YACxC,IAAI,UAAU,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACxC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,2CAA2C;IAC3C,kBAAkB;IAClB,2CAA2C;IAE3C;;;OAGG;IACK,WAAW,CAAC,UAA8B,EAAE,gBAAwB;QAC1E,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;YACzB,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC;YACrD,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QAED,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;YAC7E,IAAI,YAAY,EAAE,IAAI,EAAE,CAAC;gBACvB,OAAO,YAAY,CAAC,IAAI,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,2CAA2C;IAC3C,0BAA0B;IAC1B,2CAA2C;IAE3C;;;;;;OAMG;IACK,KAAK,CAAC,iBAAiB;QAC7B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,CAAC;QACxD,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,WAAW,CAAC,CAAC;QACrE,IAAI,CAAC,SAAS,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAChE,OAAO;QACT,CAAC;QAED,6EAA6E;QAC7E,2EAA2E;QAC3E,wEAAwE;QACxE,MAAM,WAAW,GAAG,GAAG,SAAS,CAAC,EAAE,KAAK,SAAS,CAAC,gBAAgB,IAAI,EAAE,EAAE,CAAC;QAC3E,IAAI,WAAW,KAAK,IAAI,CAAC,uBAAuB,EAAE,CAAC;YACjD,OAAO;QACT,CAAC;QACD,IAAI,CAAC,uBAAuB,GAAG,WAAW,CAAC;QAE3C,sDAAsD;QACtD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,6EAA6E;YAC7E,OAAO;QACT,CAAC;QAED,mEAAmE;QACnE,qEAAqE;QACrE,yDAAyD;QACzD,MAAM,cAAc,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAChD,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,KAAK,QAAQ,CAAC,gBAAgB,CACtD,CAAC;QACF,IAAI,cAAc,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QAED,2EAA2E;QAC3E,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,iBAAiB,CAAC,MAAM,GAAG,iBAAe,CAAC,gBAAgB,EAAE,CAAC;YACrE,IAAI,CAAC,iBAAiB,CAAC,MAAM,GAAG,iBAAe,CAAC,gBAAgB,CAAC;QACnE,CAAC;QAED,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,sBAAsB,CAAC,GAAiB;QACpD,MAAM,YAAY,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC,cAAc,CAAuB,CAAC;QAC/E,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,+EAA+E;QAC/E,IAAI,YAAY,CAAC,WAAW,EAAE,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,uCAAuC;QACvC,IAAI,MAAM,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,yDAAyD;QACzD,MAAM,UAAU,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC,QAAQ,CAAuB,CAAC;QACvE,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,GAAG,CAAC,gBAAgB,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAEjG,OAAO;YACL,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;YACtC,YAAY;YACZ,UAAU;YACV,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,aAAa;YACb,aAAa,EAAE,GAAG,CAAC,aAAwC;YAC3D,KAAK,EAAE,GAAG,CAAC,EAAE;YACb,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,oBAAoB,CAAC,GAAiB;QAClD,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9C,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAC7B,IAAI,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9D,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;gBAChF,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAED,2CAA2C;IAC3C,2BAA2B;IAC3B,2CAA2C;IAE3C;;;OAGG;IACK,oBAAoB;QAC1B,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrF,CAAC;IAED;;;OAGG;IACK,oBAAoB,CAAC,QAAgC;QAC3D,OAAO;YACL,KAAK,EAAE,QAAQ,CAAC,aAAa;YAC7B,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,YAAY,CAAC;YAClE,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB;YACnC,aAAa,EAAE,QAAQ,CAAC,aAAa;YACrC,SAAS,EAAE,IAAI;YACf,aAAa,EAAE,CAAC,GAAY,EAAE,EAAE;gBAC9B,MAAM,KAAK,GAAG,GAAmB,CAAC;gBAClC,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;oBAC9B,OAAO,KAAK,CAAC,gBAAgB,KAAK,QAAQ,CAAC,gBAAgB,CAAC;gBAC9D,CAAC;gBACD,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,KAAK,CAAC;YACrC,CAAC;SACF,CAAC;IACJ,CAAC;IAED,2CAA2C;IAC3C,mBAAmB;IACnB,2CAA2C;IAE3C;;;;;;;;;;OAUG;IACK,KAAK,CAAC,kBAAkB,CAAC,UAA8B,EAAE,WAA+B,EAAE,QAAgB;QAChH,IAAI,CAAC,UAAU,IAAI,CAAC,WAAW,EAAE,CAAC;YAChC,OAAO,QAAQ,IAAI,EAAE,CAAC;QACxB,CAAC;QAED,4CAA4C;QAC5C,MAAM,YAAY,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAChD,YAAY,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC;QACnD,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;QAElE,wBAAwB;QACxB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,UAAU,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;YAC3F,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,uCAAuC,EAAE,KAAK,CAAC,CAAC;QAC/D,CAAC;QAED,uGAAuG;QACvG,OAAO,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,QAAQ,CAAC;IACvE,CAAC;IAED;;;;;;;;OAQG;IACK,kBAAkB,CAAC,UAAkB,EAAE,GAAiB;QAC9D,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3C,OAAO,GAAG,UAAU,SAAS,CAAC;QAChC,CAAC;QAED,IAAI,GAAG,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO,GAAG,UAAU,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,CAAC;QAED,2DAA2D;QAC3D,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChF,OAAO,GAAG,UAAU,KAAK,MAAM,EAAE,CAAC;IACpC,CAAC;IAED;;;OAGG;IACK,SAAS,CAAC,KAAa,EAAE,YAAoB,CAAC;QACpD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;YAChC,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC;IACjD,CAAC;IAED,2CAA2C;IAC3C,sBAAsB;IACtB,2CAA2C;IAE3C;;;OAGG;IACK,kBAAkB;QACxB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,6EAA6E;YAC7E,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACpD,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;gBACpC,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,aAAa,EAAE,CAAC,CAAC,aAAa;gBAC9B,aAAa,EAAE,CAAC,CAAC,aAAa;gBAC9B,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,CAAC,CAAC,SAAS;aACvB,CAAC,CAAC,CAAC;YAEJ,kFAAkF;YAClF,QAAQ,CAAC,OAAO,CAA2B,WAAW,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;gBAClG,OAAO,CAAC,IAAI,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,oBAAoB;QAChC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,oGAAoG;YACpG,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAA2B,WAAW,EAAE,gBAAgB,CAAC,CAAC;YAC/F,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtC,OAAO;YACT,CAAC;YAED,6BAA6B;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,IAAI,CAAC,iBAAiB,GAAG,MAAM;iBAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,mBAAmB,CAAC;iBAC3F,KAAK,CAAC,CAAC,EAAE,iBAAe,CAAC,gBAAgB,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,+CAA+C,EAAE,GAAG,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;;AApbU,eAAe;IAD3B,aAAa,CAAC,eAAe,EAAE,iBAAiB,CAAC;GACrC,eAAe,CAqb3B","sourcesContent":["import { RegisterClass, UUIDsEqual } from '@memberjunction/global';\nimport { BaseApplication, DynamicNavItem, NavItem, WorkspaceStateManager, WorkspaceTab } from '@memberjunction/ng-base-application';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { Metadata, CompositeKey, FieldValueCollection } from '@memberjunction/core';\n\n/** Storage key and category for persisting the recent orphan stack */\nconst STORAGE_KEY = 'HomeApp_RecentOrphanStack';\nconst STORAGE_CATEGORY = 'HomeApplication';\n\n/**\n * Snapshot of an orphan resource tab for the recent navigation stack.\n * Persisted to localStorage (via Provider) so it survives page reloads.\n */\ninterface OrphanResourceSnapshot {\n /** URL-encoded composite key segment — primary dedup key */\n resourceRecordId: string;\n /** Resource type name (e.g., 'record', 'Dashboards') */\n resourceType: string;\n /** Entity name from tab configuration (for record name resolution) */\n entityName?: string;\n /** Tab title at time of capture (fallback label) */\n title: string;\n /** Resolved display label (cached record name). Persisted to avoid async lookups on reload. */\n resolvedLabel: string;\n /** Full tab configuration (passed to DynamicNavItem for re-opening) */\n configuration: Record<string, unknown>;\n /** Tab ID for fallback matching when no resourceRecordId */\n tabId: string;\n /** Timestamp (ms since epoch) when this snapshot was created or last promoted */\n timestamp: number;\n}\n\n/** Max age for persisted snapshots — 7 days */\nconst MAX_SNAPSHOT_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\n/**\n * Home Application - Provides dynamic navigation items for orphan resources.\n *\n * Maintains a recency-ordered stack of up to 3 recently visited orphan resources.\n * When the user opens a record/view/dashboard from within the Home app, the resource\n * appears as a dynamic nav item. When they navigate away (e.g., click Home), the\n * nav item persists so they can quickly jump back. Older items drop off as new ones\n * are added.\n *\n * The stack is persisted to localStorage (via Metadata.Provider.LocalStorageProvider)\n * so it survives page reloads. Resolved record labels are stored alongside each\n * snapshot to avoid async lookups when restoring from storage.\n *\n * @example\n * User opens \"John Smith\" contact, then \"Acme Corp\" company:\n * - Nav shows: [Home] [Favorites] | [person] \"Acme Corp\" [building] \"John Smith\"\n * - Clicking \"John Smith\" re-opens that record\n */\n@RegisterClass(BaseApplication, 'HomeApplication')\nexport class HomeApplication extends BaseApplication {\n private static readonly MAX_RECENT_ITEMS = 3;\n\n private workspaceManager: WorkspaceStateManager | null = null;\n private sharedService: SharedService | null = null;\n private recentOrphanStack: OrphanResourceSnapshot[] = [];\n private _storageLoadPromise: Promise<void> | null = null;\n\n /**\n * Fingerprint of the last-processed active tab state (tabId::resourceRecordId).\n * Guards against redundant stack mutations while still detecting content changes.\n * In single-resource mode the tab ID stays constant when navigating between records\n * (OpenTab replaces the temp tab's content but keeps its ID), so we must also\n * include the resourceRecordId to detect when the tab shows a different record.\n */\n private _lastSeenTabFingerprint: string | null = null;\n\n /**\n * Inject WorkspaceStateManager for accessing current tab state.\n * Also triggers loading the persisted stack from storage on first call.\n */\n public SetWorkspaceManager(manager: WorkspaceStateManager): void {\n this.workspaceManager = manager;\n if (!this._storageLoadPromise) {\n this._storageLoadPromise = this.loadStackFromStorage();\n }\n }\n\n /**\n * Inject SharedService for accessing ResourceTypes metadata\n */\n public SetSharedService(service: SharedService): void {\n this.sharedService = service;\n }\n\n /**\n * Returns navigation items including dynamic items for recently visited orphan resources.\n * Static items come from DefaultNavItems in metadata.\n * Dynamic items are generated from the recent orphan stack (up to 3).\n */\n override async GetNavItems(): Promise<NavItem[]> {\n // Ensure persisted stack is loaded before building nav items.\n // On first call after page load, this awaits the IndexedDB read;\n // on subsequent calls the promise is already resolved (no-op await).\n if (this._storageLoadPromise) {\n await this._storageLoadPromise;\n }\n\n const staticItems = await super.GetNavItems();\n\n // Update the recent stack based on current active tab\n // Must await — updateRecentStack is async (calls matchesStaticNavItem → super.GetNavItems())\n // and buildDynamicNavItems reads recentOrphanStack, so the stack must be fully updated first.\n await this.updateRecentStack();\n\n // Build dynamic nav items from the stack\n const dynamicItems = this.buildDynamicNavItems();\n if (dynamicItems.length > 0) {\n return [...staticItems, ...dynamicItems];\n }\n\n return staticItems;\n }\n\n /**\n * Removes a dynamic nav item from the recent stack by RecordID.\n * Called by AppNavComponent when the user clicks the dismiss X button.\n * If the dismissed item is currently active, navigates back to the app's default tab.\n */\n public RemoveDynamicNavItem(item: NavItem): void {\n const index = this.recentOrphanStack.findIndex(\n s => s.resourceRecordId === item.RecordID\n );\n if (index < 0) {\n return;\n }\n\n const wasActive = this.isDismissedItemActive(item);\n this.recentOrphanStack.splice(index, 1);\n this.saveStackToStorage();\n\n if (wasActive) {\n this.navigateToDefault();\n }\n }\n\n /**\n * Checks whether the dismissed nav item matches the currently active tab.\n */\n private isDismissedItemActive(item: NavItem): boolean {\n if (!this.workspaceManager) {\n return false;\n }\n const config = this.workspaceManager.GetConfiguration();\n if (!config?.activeTabId) {\n return false;\n }\n const activeTab = config.tabs.find(t => t.id === config.activeTabId);\n if (!activeTab) {\n return false;\n }\n return activeTab.resourceRecordId === item.RecordID;\n }\n\n /**\n * Navigates back to the app's default tab (first static nav item / Home dashboard).\n */\n private navigateToDefault(): void {\n if (!this.workspaceManager) {\n return;\n }\n this.CreateDefaultTab().then(tabRequest => {\n if (tabRequest && this.workspaceManager) {\n this.workspaceManager.OpenTab(tabRequest, this.GetColor());\n }\n });\n }\n\n // ========================================\n // Icon Resolution\n // ========================================\n\n /**\n * Resolves the best icon for a dynamic nav item.\n * Priority: entity-specific icon from metadata → resource type icon → generic fallback.\n */\n private resolveIcon(entityName: string | undefined, resourceTypeName: string): string {\n if (entityName) {\n const md = this.Provider;\n const entityIcon = md.EntityByName(entityName)?.Icon;\n if (entityIcon) {\n return entityIcon;\n }\n }\n\n if (this.sharedService) {\n const resourceType = this.sharedService.ResourceTypeByName(resourceTypeName);\n if (resourceType?.Icon) {\n return resourceType.Icon;\n }\n }\n\n return 'fa-solid fa-file';\n }\n\n // ========================================\n // Recent Stack Management\n // ========================================\n\n /**\n * Updates the recent orphan stack based on the currently active tab.\n * If the active tab is a new orphan resource (not already in the stack and not\n * matching any static nav item), it gets added to the front. Items already in the\n * stack keep their position to avoid visually jarring reordering when users click\n * between existing dynamic nav items.\n */\n private async updateRecentStack(): Promise<void> {\n if (!this.workspaceManager) {\n return;\n }\n\n const config = this.workspaceManager.GetConfiguration();\n if (!config?.activeTabId) {\n return;\n }\n\n const activeTab = config.tabs.find(t => t.id === config.activeTabId);\n if (!activeTab || !UUIDsEqual(activeTab.applicationId, this.ID)) {\n return;\n }\n\n // Build a fingerprint from tab ID + record content. In single-resource mode,\n // OpenTab replaces the temp tab's content but keeps the same tab ID, so we\n // need the resourceRecordId to detect when a different record is shown.\n const fingerprint = `${activeTab.id}::${activeTab.resourceRecordId ?? ''}`;\n if (fingerprint === this._lastSeenTabFingerprint) {\n return;\n }\n this._lastSeenTabFingerprint = fingerprint;\n\n // Check if active tab qualifies as an orphan resource\n const snapshot = await this.createSnapshotIfOrphan(activeTab);\n if (!snapshot) {\n // Active tab is a static resource (Home dashboard, etc.) — leave stack as-is\n return;\n }\n\n // If this item is already in the stack, leave the order unchanged.\n // Reordering on every click is visually jarring — items shift around\n // while the user is clicking between existing nav items.\n const alreadyTracked = this.recentOrphanStack.some(\n s => s.resourceRecordId === snapshot.resourceRecordId\n );\n if (alreadyTracked) {\n return;\n }\n\n // New orphan — push to front (most recent first) and trim oldest if needed\n this.recentOrphanStack.unshift(snapshot);\n if (this.recentOrphanStack.length > HomeApplication.MAX_RECENT_ITEMS) {\n this.recentOrphanStack.length = HomeApplication.MAX_RECENT_ITEMS;\n }\n\n this.saveStackToStorage();\n }\n\n /**\n * Creates an OrphanResourceSnapshot from a tab if it qualifies as an orphan resource.\n * Returns null if the tab is a static nav item, has no resource type, or is an\n * app-level custom dashboard (no record ID).\n */\n private async createSnapshotIfOrphan(tab: WorkspaceTab): Promise<OrphanResourceSnapshot | null> {\n const resourceType = tab.configuration?.['resourceType'] as string | undefined;\n if (!resourceType) {\n return null;\n }\n\n // Skip 'custom' resource type without a specific record (app-level dashboards)\n if (resourceType.toLowerCase() === 'custom' && !tab.resourceRecordId) {\n return null;\n }\n\n // Skip if it matches a static nav item\n if (await this.matchesStaticNavItem(tab)) {\n return null;\n }\n\n // Resolve the label now so it's stored with the snapshot\n const entityName = tab.configuration?.['Entity'] as string | undefined;\n const resolvedLabel = await this.resolveRecordLabel(entityName, tab.resourceRecordId, tab.title);\n\n return {\n resourceRecordId: tab.resourceRecordId,\n resourceType,\n entityName,\n title: tab.title,\n resolvedLabel,\n configuration: tab.configuration as Record<string, unknown>,\n tabId: tab.id,\n timestamp: Date.now()\n };\n }\n\n /**\n * Checks whether a tab matches any static nav item (by label, route, or driver class).\n */\n private async matchesStaticNavItem(tab: WorkspaceTab): Promise<boolean> {\n const staticItems = await super.GetNavItems();\n return staticItems.some(item => {\n if (item.Label === tab.title) {\n return true;\n }\n if (item.Route && tab.configuration?.['route'] === item.Route) {\n return true;\n }\n if (item.DriverClass && tab.configuration?.['driverClass'] === item.DriverClass) {\n return true;\n }\n return false;\n });\n }\n\n // ========================================\n // Dynamic NavItem Building\n // ========================================\n\n /**\n * Creates DynamicNavItem entries for each item in the recent orphan stack.\n * Uses the pre-resolved label from the snapshot (no async needed).\n */\n private buildDynamicNavItems(): DynamicNavItem[] {\n return this.recentOrphanStack.map(snapshot => this.createDynamicNavItem(snapshot));\n }\n\n /**\n * Creates a single DynamicNavItem from an OrphanResourceSnapshot.\n * Uses the snapshot's resolvedLabel directly — no async lookup needed.\n */\n private createDynamicNavItem(snapshot: OrphanResourceSnapshot): DynamicNavItem {\n return {\n Label: snapshot.resolvedLabel,\n Icon: this.resolveIcon(snapshot.entityName, snapshot.resourceType),\n ResourceType: snapshot.resourceType,\n RecordID: snapshot.resourceRecordId,\n Configuration: snapshot.configuration,\n isDynamic: true,\n isActiveMatch: (tab: unknown) => {\n const wsTab = tab as WorkspaceTab;\n if (snapshot.resourceRecordId) {\n return wsTab.resourceRecordId === snapshot.resourceRecordId;\n }\n return wsTab.id === snapshot.tabId;\n }\n };\n }\n\n // ========================================\n // Label Resolution\n // ========================================\n\n /**\n * Resolves the best available display label for an entity record nav item.\n * Priority order:\n * 1. Cached record name (from EntityRecordNameCache, populated by entity Load/Save)\n * 2. Tab title (whatever the workspace assigned when the tab was opened)\n * 3. Generated default (entity name + truncated primary key values)\n *\n * @param entityName - Entity name from tab configuration, may be undefined for non-entity resources\n * @param tabRecordId - URL-encoded composite key segment (e.g. \"ID|abc-123\" or \"Field1|val1||Field2|val2\")\n * @param tabTitle - Fallback title assigned by the workspace when the tab was opened\n */\n private async resolveRecordLabel(entityName: string | undefined, tabRecordId: string | undefined, tabTitle: string): Promise<string> {\n if (!entityName || !tabRecordId) {\n return tabTitle || '';\n }\n\n // Parse the URL segment into a CompositeKey\n const fvCollection = new FieldValueCollection();\n fvCollection.SimpleLoadFromURLSegment(tabRecordId);\n const compositeKey = new CompositeKey(fvCollection.KeyValuePairs);\n\n // Check the cache first\n try {\n const cachedName = await this.Provider.GetCachedRecordName(entityName, compositeKey, true);\n if (cachedName) {\n return cachedName;\n }\n } catch (error) {\n console.warn('Failed to look up cached record name:', error);\n }\n\n // Fall back generated default from the key values, then tabTitle if we have no default label(unlikely)\n return this.createDefaultLabel(entityName, compositeKey) || tabTitle;\n }\n\n /**\n * Generates a fallback label when no cached record name or tab title is available.\n * Format: \"EntityName: <truncated key value(s)>\"\n *\n * @example\n * // Single key: \"Contacts: abc1234...\"\n * // Multi key: \"Order Details: abc1..., 42\"\n * // No key: \"Contacts record\"\n */\n private createDefaultLabel(entityName: string, key: CompositeKey): string {\n if (!key || key.KeyValuePairs.length === 0) {\n return `${entityName} record`;\n }\n\n if (key.KeyValuePairs.length === 1) {\n return `${entityName}: ${this.trimValue(key.KeyValuePairs[0].Value)}`;\n }\n\n // Multiple keys - show each value trimmed, comma-separated\n const values = key.KeyValuePairs.map(kv => this.trimValue(kv.Value)).join(', ');\n return `${entityName}: ${values}`;\n }\n\n /**\n * Truncates a primary key value for display, appending \"...\" if it exceeds maxLength.\n * Returns the value as-is if it's short enough or empty.\n */\n private trimValue(value: string, maxLength: number = 8): string {\n if (!value || value.trim().length === 0) {\n return value;\n }\n\n const trimmed = value.trim();\n if (trimmed.length <= maxLength) {\n return trimmed;\n }\n\n return trimmed.substring(0, maxLength) + '...';\n }\n\n // ========================================\n // Storage Persistence\n // ========================================\n\n /**\n * Persists the current recentOrphanStack to localStorage via Provider.\n * Fire-and-forget — errors are logged but don't affect the UX.\n */\n private saveStackToStorage(): void {\n try {\n const provider = this.Provider.LocalStorageProvider;\n if (!provider) {\n return;\n }\n\n // Strip non-serializable fields (isActiveMatch closures are rebuilt on load)\n const serializable = this.recentOrphanStack.map(s => ({\n resourceRecordId: s.resourceRecordId,\n resourceType: s.resourceType,\n entityName: s.entityName,\n title: s.title,\n resolvedLabel: s.resolvedLabel,\n configuration: s.configuration,\n tabId: s.tabId,\n timestamp: s.timestamp\n }));\n\n // Native object storage — provider handles any required serialization internally.\n provider.SetItem<OrphanResourceSnapshot[]>(STORAGE_KEY, serializable, STORAGE_CATEGORY).catch(err => {\n console.warn('Failed to persist recent nav stack:', err);\n });\n } catch (err) {\n console.warn('Failed to persist recent nav stack:', err);\n }\n }\n\n /**\n * Loads the recent orphan stack from localStorage via Provider.\n * Filters out stale entries (older than MAX_SNAPSHOT_AGE_MS).\n */\n private async loadStackFromStorage(): Promise<void> {\n try {\n const provider = this.Provider.LocalStorageProvider;\n if (!provider) {\n return;\n }\n\n // Native object read — IDB returns the array directly; localStorage / Redis JSON-decode internally.\n const parsed = await provider.GetItem<OrphanResourceSnapshot[]>(STORAGE_KEY, STORAGE_CATEGORY);\n if (!parsed || !Array.isArray(parsed)) {\n return;\n }\n\n // Filter out stale snapshots\n const now = Date.now();\n this.recentOrphanStack = parsed\n .filter(s => s.resourceRecordId && s.timestamp && (now - s.timestamp) < MAX_SNAPSHOT_AGE_MS)\n .slice(0, HomeApplication.MAX_RECENT_ITEMS);\n } catch (err) {\n console.warn('Failed to load recent nav stack from storage:', err);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"home-application.js","sourceRoot":"","sources":["../../src/Home/home-application.ts"],"names":[],"mappings":"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","sourcesContent":["import { RegisterClass, UUIDsEqual } from '@memberjunction/global';\nimport { BaseApplication, DynamicNavItem, NavItem, WorkspaceStateManager, WorkspaceTab } from '@memberjunction/ng-base-application';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { Metadata, CompositeKey, FieldValueCollection } from '@memberjunction/core';\n\n/** Storage key and category for persisting the recent orphan stack */\nconst STORAGE_KEY = 'HomeApp_RecentOrphanStack';\nconst STORAGE_CATEGORY = 'HomeApplication';\n\n/**\n * Snapshot of an orphan resource tab for the recent navigation stack.\n * Persisted to localStorage (via Provider) so it survives page reloads.\n */\ninterface OrphanResourceSnapshot {\n /** URL-encoded composite key segment — primary dedup key */\n resourceRecordId: string;\n /** Resource type name (e.g., 'record', 'Dashboards') */\n resourceType: string;\n /** Entity name from tab configuration (for record name resolution) */\n entityName?: string;\n /** Tab title at time of capture (fallback label) */\n title: string;\n /** Resolved display label (cached record name). Persisted to avoid async lookups on reload. */\n resolvedLabel: string;\n /** Full tab configuration (passed to DynamicNavItem for re-opening) */\n configuration: Record<string, unknown>;\n /** Tab ID for fallback matching when no resourceRecordId */\n tabId: string;\n /** Timestamp (ms since epoch) when this snapshot was created or last promoted */\n timestamp: number;\n}\n\n/** Max age for persisted snapshots — 7 days */\nconst MAX_SNAPSHOT_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\n/**\n * Home Application - Provides dynamic navigation items for orphan resources.\n *\n * Maintains a recency-ordered stack of up to 3 recently visited orphan resources.\n * When the user opens a record/view/dashboard from within the Home app, the resource\n * appears as a dynamic nav item. When they navigate away (e.g., click Home), the\n * nav item persists so they can quickly jump back. Older items drop off as new ones\n * are added.\n *\n * The stack is persisted to localStorage (via Metadata.Provider.LocalStorageProvider)\n * so it survives page reloads. Resolved record labels are stored alongside each\n * snapshot to avoid async lookups when restoring from storage.\n *\n * @example\n * User opens \"John Smith\" contact, then \"Acme Corp\" company:\n * - Nav shows: [Home] [Favorites] | [person] \"Acme Corp\" [building] \"John Smith\"\n * - Clicking \"John Smith\" re-opens that record\n */\n@RegisterClass(BaseApplication, 'HomeApplication')\nexport class HomeApplication extends BaseApplication {\n private static readonly MAX_RECENT_ITEMS = 3;\n\n private workspaceManager: WorkspaceStateManager | null = null;\n private sharedService: SharedService | null = null;\n private recentOrphanStack: OrphanResourceSnapshot[] = [];\n private _storageLoadPromise: Promise<void> | null = null;\n\n /**\n * Fingerprint of the last-processed active tab state (tabId::resourceRecordId::title).\n * Guards against redundant stack mutations while still detecting content changes.\n * In single-resource mode the tab ID stays constant when navigating between records\n * (OpenTab replaces the temp tab's content but keeps its ID), so we must also\n * include the resourceRecordId to detect when the tab shows a different record.\n *\n * Title is also included so an in-place display-name change (e.g. user edits a\n * record's Name field and saves — tabId and resourceRecordId stay the same but the\n * title changes) triggers a re-evaluation. Without this, the dynamic nav item keeps\n * showing the pre-edit name forever.\n */\n private _lastSeenTabFingerprint: string | null = null;\n\n /**\n * Inject WorkspaceStateManager for accessing current tab state.\n * Also triggers loading the persisted stack from storage on first call.\n */\n public SetWorkspaceManager(manager: WorkspaceStateManager): void {\n this.workspaceManager = manager;\n if (!this._storageLoadPromise) {\n this._storageLoadPromise = this.loadStackFromStorage();\n }\n }\n\n /**\n * Inject SharedService for accessing ResourceTypes metadata\n */\n public SetSharedService(service: SharedService): void {\n this.sharedService = service;\n }\n\n /**\n * Returns navigation items including dynamic items for recently visited orphan resources.\n * Static items come from DefaultNavItems in metadata.\n * Dynamic items are generated from the recent orphan stack (up to 3).\n */\n override async GetNavItems(): Promise<NavItem[]> {\n // Ensure persisted stack is loaded before building nav items.\n // On first call after page load, this awaits the IndexedDB read;\n // on subsequent calls the promise is already resolved (no-op await).\n if (this._storageLoadPromise) {\n await this._storageLoadPromise;\n }\n\n const staticItems = await super.GetNavItems();\n\n // Update the recent stack based on current active tab\n // Must await — updateRecentStack is async (calls matchesStaticNavItem → super.GetNavItems())\n // and buildDynamicNavItems reads recentOrphanStack, so the stack must be fully updated first.\n await this.updateRecentStack();\n\n // Build dynamic nav items from the stack\n const dynamicItems = this.buildDynamicNavItems();\n if (dynamicItems.length > 0) {\n return [...staticItems, ...dynamicItems];\n }\n\n return staticItems;\n }\n\n /**\n * Removes a dynamic nav item from the recent stack by RecordID.\n * Called by AppNavComponent when the user clicks the dismiss X button.\n * If the dismissed item is currently active, navigates back to the app's default tab.\n */\n public RemoveDynamicNavItem(item: NavItem): void {\n const index = this.recentOrphanStack.findIndex(\n s => s.resourceRecordId === item.RecordID\n );\n if (index < 0) {\n return;\n }\n\n const wasActive = this.isDismissedItemActive(item);\n this.recentOrphanStack.splice(index, 1);\n this.saveStackToStorage();\n\n if (wasActive) {\n this.navigateToDefault();\n }\n }\n\n /**\n * Checks whether the dismissed nav item matches the currently active tab.\n */\n private isDismissedItemActive(item: NavItem): boolean {\n if (!this.workspaceManager) {\n return false;\n }\n const config = this.workspaceManager.GetConfiguration();\n if (!config?.activeTabId) {\n return false;\n }\n const activeTab = config.tabs.find(t => t.id === config.activeTabId);\n if (!activeTab) {\n return false;\n }\n return activeTab.resourceRecordId === item.RecordID;\n }\n\n /**\n * Navigates back to the app's default tab (first static nav item / Home dashboard).\n */\n private navigateToDefault(): void {\n if (!this.workspaceManager) {\n return;\n }\n this.CreateDefaultTab().then(tabRequest => {\n if (tabRequest && this.workspaceManager) {\n this.workspaceManager.OpenTab(tabRequest, this.GetColor());\n }\n });\n }\n\n // ========================================\n // Icon Resolution\n // ========================================\n\n /**\n * Resolves the best icon for a dynamic nav item.\n * Priority: entity-specific icon from metadata → resource type icon → generic fallback.\n */\n private resolveIcon(entityName: string | undefined, resourceTypeName: string): string {\n if (entityName) {\n const md = this.Provider;\n const entityIcon = md.EntityByName(entityName)?.Icon;\n if (entityIcon) {\n return entityIcon;\n }\n }\n\n if (this.sharedService) {\n const resourceType = this.sharedService.ResourceTypeByName(resourceTypeName);\n if (resourceType?.Icon) {\n return resourceType.Icon;\n }\n }\n\n return 'fa-solid fa-file';\n }\n\n // ========================================\n // Recent Stack Management\n // ========================================\n\n /**\n * Updates the recent orphan stack based on the currently active tab.\n * If the active tab is a new orphan resource (not already in the stack and not\n * matching any static nav item), it gets added to the front. Items already in the\n * stack keep their position to avoid visually jarring reordering when users click\n * between existing dynamic nav items.\n */\n private async updateRecentStack(): Promise<void> {\n if (!this.workspaceManager) {\n return;\n }\n\n const config = this.workspaceManager.GetConfiguration();\n if (!config?.activeTabId) {\n return;\n }\n\n const activeTab = config.tabs.find(t => t.id === config.activeTabId);\n if (!activeTab || !UUIDsEqual(activeTab.applicationId, this.ID)) {\n return;\n }\n\n // ALWAYS refresh the matching snapshot's label first — BEFORE the fingerprint\n // bail. A burst of configuration$ emissions (typical on save) causes multiple\n // concurrent updateCachedData calls; the first one proceeds past the fingerprint\n // check and gets stuck awaiting createSnapshotIfOrphan, while the rest bail at\n // fingerprint and race ahead with the *stale* snapshot. By the time the first\n // call's async work completes and updates the snapshot, the gen-check in\n // AppNav.updateCachedData has already discarded its results. The user is left\n // with the right data in memory but rendered against the wrong label.\n //\n // Refreshing here is cheap (cache hit after my save-handler's pre-warm) and\n // ensures every call — even fast bailers — produces fresh dynamic nav items.\n await this.refreshExistingSnapshotLabel(activeTab);\n\n // Build a fingerprint from tab ID + record content + title. In single-resource\n // mode, OpenTab replaces the temp tab's content but keeps the same tab ID, so we\n // need the resourceRecordId to detect when a different record is shown. Title is\n // included so an in-place rename (record's Name field edited and saved — tabId\n // and resourceRecordId stay the same but title changes) also triggers a refresh.\n const fingerprint = `${activeTab.id}::${activeTab.resourceRecordId ?? ''}::${activeTab.title ?? ''}`;\n if (fingerprint === this._lastSeenTabFingerprint) {\n return;\n }\n this._lastSeenTabFingerprint = fingerprint;\n\n // Check if active tab qualifies as an orphan resource\n const snapshot = await this.createSnapshotIfOrphan(activeTab);\n if (!snapshot) {\n // Active tab is a static resource (Home dashboard, etc.) — leave stack as-is\n return;\n }\n\n // If this item is already in the stack, leave the ORDER unchanged (reordering\n // on every click is visually jarring) — but DO refresh its `resolvedLabel` if the\n // underlying display name has changed. Without this, editing a record's Name\n // field would leave the dynamic nav item showing the pre-edit name forever.\n // Already-tracked snapshots are kept in their existing position (no jarring\n // reorder) and have their label kept fresh by refreshExistingSnapshotLabel\n // above — so here we only need to handle the NEW-snapshot case.\n const alreadyTracked = this.recentOrphanStack.some(\n s => s.resourceRecordId === snapshot.resourceRecordId\n );\n if (alreadyTracked) {\n return;\n }\n\n // New orphan — push to front (most recent first) and trim oldest if needed\n this.recentOrphanStack.unshift(snapshot);\n if (this.recentOrphanStack.length > HomeApplication.MAX_RECENT_ITEMS) {\n this.recentOrphanStack.length = HomeApplication.MAX_RECENT_ITEMS;\n }\n\n this.saveStackToStorage();\n }\n\n /**\n * Refreshes an already-tracked snapshot's `resolvedLabel` against the latest cached\n * record name. Called unconditionally at the top of `updateRecentStack` (BEFORE the\n * fingerprint bail), so even no-op-fingerprint calls don't render stale labels.\n *\n * Why this matters: when a record is saved, `configuration$` typically emits multiple\n * times in quick succession (UpdateTabTitle, UpdateConfiguration, etc.). AppNav's\n * subscription kicks off a `updateCachedData` for each. The first call wins the\n * fingerprint race and starts the slow async `createSnapshotIfOrphan` chain;\n * subsequent calls bail at fingerprint and race ahead with the still-unrefreshed\n * snapshot. AppNav's `_updateGeneration` then discards the slow call's results.\n * Without this method, the freshly-saved label lands in the snapshot — but never\n * makes it to the rendered nav until some unrelated event triggers another cycle.\n */\n private async refreshExistingSnapshotLabel(activeTab: WorkspaceTab): Promise<void> {\n if (!activeTab.resourceRecordId) {\n return;\n }\n const existing = this.recentOrphanStack.find(\n s => s.resourceRecordId === activeTab.resourceRecordId\n );\n if (!existing || !existing.entityName) {\n return;\n }\n const newLabel = await this.resolveRecordLabel(\n existing.entityName,\n activeTab.resourceRecordId,\n activeTab.title\n );\n if (newLabel && newLabel !== existing.resolvedLabel) {\n existing.resolvedLabel = newLabel;\n existing.title = activeTab.title;\n this.saveStackToStorage();\n }\n }\n\n /**\n * Creates an OrphanResourceSnapshot from a tab if it qualifies as an orphan resource.\n * Returns null if the tab is a static nav item, has no resource type, or is an\n * app-level custom dashboard (no record ID).\n */\n private async createSnapshotIfOrphan(tab: WorkspaceTab): Promise<OrphanResourceSnapshot | null> {\n const resourceType = tab.configuration?.['resourceType'] as string | undefined;\n if (!resourceType) {\n return null;\n }\n\n // Skip 'custom' resource type without a specific record (app-level dashboards)\n if (resourceType.toLowerCase() === 'custom' && !tab.resourceRecordId) {\n return null;\n }\n\n // Skip if it matches a static nav item\n if (await this.matchesStaticNavItem(tab)) {\n return null;\n }\n\n // Resolve the label now so it's stored with the snapshot\n const entityName = tab.configuration?.['Entity'] as string | undefined;\n const resolvedLabel = await this.resolveRecordLabel(entityName, tab.resourceRecordId, tab.title);\n\n return {\n resourceRecordId: tab.resourceRecordId,\n resourceType,\n entityName,\n title: tab.title,\n resolvedLabel,\n configuration: tab.configuration as Record<string, unknown>,\n tabId: tab.id,\n timestamp: Date.now()\n };\n }\n\n /**\n * Checks whether a tab matches any static nav item (by label, route, or driver class).\n */\n private async matchesStaticNavItem(tab: WorkspaceTab): Promise<boolean> {\n const staticItems = await super.GetNavItems();\n return staticItems.some(item => {\n if (item.Label === tab.title) {\n return true;\n }\n if (item.Route && tab.configuration?.['route'] === item.Route) {\n return true;\n }\n if (item.DriverClass && tab.configuration?.['driverClass'] === item.DriverClass) {\n return true;\n }\n return false;\n });\n }\n\n // ========================================\n // Dynamic NavItem Building\n // ========================================\n\n /**\n * Creates DynamicNavItem entries for each item in the recent orphan stack.\n * Uses the pre-resolved label from the snapshot (no async needed).\n */\n private buildDynamicNavItems(): DynamicNavItem[] {\n return this.recentOrphanStack.map(snapshot => this.createDynamicNavItem(snapshot));\n }\n\n /**\n * Creates a single DynamicNavItem from an OrphanResourceSnapshot.\n * Uses the snapshot's resolvedLabel directly — no async lookup needed.\n */\n private createDynamicNavItem(snapshot: OrphanResourceSnapshot): DynamicNavItem {\n return {\n Label: snapshot.resolvedLabel,\n Icon: this.resolveIcon(snapshot.entityName, snapshot.resourceType),\n ResourceType: snapshot.resourceType,\n RecordID: snapshot.resourceRecordId,\n Configuration: snapshot.configuration,\n isDynamic: true,\n isActiveMatch: (tab: unknown) => {\n const wsTab = tab as WorkspaceTab;\n if (snapshot.resourceRecordId) {\n return wsTab.resourceRecordId === snapshot.resourceRecordId;\n }\n return wsTab.id === snapshot.tabId;\n }\n };\n }\n\n // ========================================\n // Label Resolution\n // ========================================\n\n /**\n * Resolves the best available display label for an entity record nav item.\n * Priority order:\n * 1. Cached record name (from EntityRecordNameCache, populated by entity Load/Save)\n * 2. Tab title (whatever the workspace assigned when the tab was opened)\n * 3. Generated default (entity name + truncated primary key values)\n *\n * @param entityName - Entity name from tab configuration, may be undefined for non-entity resources\n * @param tabRecordId - URL-encoded composite key segment (e.g. \"ID|abc-123\" or \"Field1|val1||Field2|val2\")\n * @param tabTitle - Fallback title assigned by the workspace when the tab was opened\n */\n private async resolveRecordLabel(entityName: string | undefined, tabRecordId: string | undefined, tabTitle: string): Promise<string> {\n if (!entityName || !tabRecordId) {\n return tabTitle || '';\n }\n\n // Parse the URL segment into a CompositeKey\n const fvCollection = new FieldValueCollection();\n fvCollection.SimpleLoadFromURLSegment(tabRecordId);\n const compositeKey = new CompositeKey(fvCollection.KeyValuePairs);\n\n // Check the cache first\n try {\n const cachedName = await this.Provider.GetCachedRecordName(entityName, compositeKey, true);\n if (cachedName) {\n return cachedName;\n }\n } catch (error) {\n console.warn('Failed to look up cached record name:', error);\n }\n\n // Fall back generated default from the key values, then tabTitle if we have no default label(unlikely)\n return this.createDefaultLabel(entityName, compositeKey) || tabTitle;\n }\n\n /**\n * Generates a fallback label when no cached record name or tab title is available.\n * Format: \"EntityName: <truncated key value(s)>\"\n *\n * @example\n * // Single key: \"Contacts: abc1234...\"\n * // Multi key: \"Order Details: abc1..., 42\"\n * // No key: \"Contacts record\"\n */\n private createDefaultLabel(entityName: string, key: CompositeKey): string {\n if (!key || key.KeyValuePairs.length === 0) {\n return `${entityName} record`;\n }\n\n if (key.KeyValuePairs.length === 1) {\n return `${entityName}: ${this.trimValue(key.KeyValuePairs[0].Value)}`;\n }\n\n // Multiple keys - show each value trimmed, comma-separated\n const values = key.KeyValuePairs.map(kv => this.trimValue(kv.Value)).join(', ');\n return `${entityName}: ${values}`;\n }\n\n /**\n * Truncates a primary key value for display, appending \"...\" if it exceeds maxLength.\n * Returns the value as-is if it's short enough or empty.\n */\n private trimValue(value: string, maxLength: number = 8): string {\n if (!value || value.trim().length === 0) {\n return value;\n }\n\n const trimmed = value.trim();\n if (trimmed.length <= maxLength) {\n return trimmed;\n }\n\n return trimmed.substring(0, maxLength) + '...';\n }\n\n // ========================================\n // Storage Persistence\n // ========================================\n\n /**\n * Persists the current recentOrphanStack to localStorage via Provider.\n * Fire-and-forget — errors are logged but don't affect the UX.\n */\n private saveStackToStorage(): void {\n try {\n const provider = this.Provider.LocalStorageProvider;\n if (!provider) {\n return;\n }\n\n // Strip non-serializable fields (isActiveMatch closures are rebuilt on load)\n const serializable = this.recentOrphanStack.map(s => ({\n resourceRecordId: s.resourceRecordId,\n resourceType: s.resourceType,\n entityName: s.entityName,\n title: s.title,\n resolvedLabel: s.resolvedLabel,\n configuration: s.configuration,\n tabId: s.tabId,\n timestamp: s.timestamp\n }));\n\n // Native object storage — provider handles any required serialization internally.\n provider.SetItem<OrphanResourceSnapshot[]>(STORAGE_KEY, serializable, STORAGE_CATEGORY).catch(err => {\n console.warn('Failed to persist recent nav stack:', err);\n });\n } catch (err) {\n console.warn('Failed to persist recent nav stack:', err);\n }\n }\n\n /**\n * Loads the recent orphan stack from localStorage via Provider.\n * Filters out stale entries (older than MAX_SNAPSHOT_AGE_MS).\n */\n private async loadStackFromStorage(): Promise<void> {\n try {\n const provider = this.Provider.LocalStorageProvider;\n if (!provider) {\n return;\n }\n\n // Native object read — IDB returns the array directly; localStorage / Redis JSON-decode internally.\n const parsed = await provider.GetItem<OrphanResourceSnapshot[]>(STORAGE_KEY, STORAGE_CATEGORY);\n if (!parsed || !Array.isArray(parsed)) {\n return;\n }\n\n // Filter out stale snapshots\n const now = Date.now();\n this.recentOrphanStack = parsed\n .filter(s => s.resourceRecordId && s.timestamp && (now - s.timestamp) < MAX_SNAPSHOT_AGE_MS)\n .slice(0, HomeApplication.MAX_RECENT_ITEMS);\n } catch (err) {\n console.warn('Failed to load recent nav stack from storage:', err);\n }\n }\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { OnInit, OnDestroy, ChangeDetectorRef, ElementRef, NgZone } from '@angular/core';
|
|
2
2
|
import { Subject } from 'rxjs';
|
|
3
3
|
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
4
|
-
import {
|
|
4
|
+
import { CompositeKey } from '@memberjunction/core';
|
|
5
5
|
import { TreeBranchConfig } from '@memberjunction/ng-trees';
|
|
6
|
-
import { ResourceData } from '@memberjunction/core-entities';
|
|
6
|
+
import { ResourceData, MJQueryEntityExtended, MJQueryCategoryEntity } from '@memberjunction/core-entities';
|
|
7
7
|
import { QueryEntityLinkClickEvent, QueryRowClickEvent } from '@memberjunction/ng-query-viewer';
|
|
8
8
|
import { CompositionTokenClickEvent } from '@memberjunction/ng-code-editor';
|
|
9
9
|
import * as i0 from "@angular/core";
|
|
@@ -11,9 +11,9 @@ import * as i0 from "@angular/core";
|
|
|
11
11
|
* Tree node for the query category hierarchy
|
|
12
12
|
*/
|
|
13
13
|
interface CategoryNode {
|
|
14
|
-
category:
|
|
14
|
+
category: MJQueryCategoryEntity;
|
|
15
15
|
children: CategoryNode[];
|
|
16
|
-
queries:
|
|
16
|
+
queries: MJQueryEntityExtended[];
|
|
17
17
|
expanded: boolean;
|
|
18
18
|
level: number;
|
|
19
19
|
}
|
|
@@ -36,13 +36,13 @@ export declare class QueryBrowserResourceComponent extends BaseResourceComponent
|
|
|
36
36
|
private static readonly MIN_PANEL_WIDTH;
|
|
37
37
|
private static readonly MAX_PANEL_WIDTH;
|
|
38
38
|
isLoading: boolean;
|
|
39
|
-
categories:
|
|
39
|
+
categories: MJQueryCategoryEntity[];
|
|
40
40
|
categoryTree: CategoryNode[];
|
|
41
41
|
/** All queries the user has permission to run */
|
|
42
|
-
queries:
|
|
43
|
-
filteredQueries:
|
|
42
|
+
queries: MJQueryEntityExtended[];
|
|
43
|
+
filteredQueries: MJQueryEntityExtended[];
|
|
44
44
|
private filteredQueryIds;
|
|
45
|
-
selectedQuery:
|
|
45
|
+
selectedQuery: MJQueryEntityExtended | null;
|
|
46
46
|
searchText: string;
|
|
47
47
|
PanelWidth: number;
|
|
48
48
|
IsResizing: boolean;
|
|
@@ -109,8 +109,8 @@ export declare class QueryBrowserResourceComponent extends BaseResourceComponent
|
|
|
109
109
|
toggleExpand(node: CategoryNode, event?: Event): void;
|
|
110
110
|
expandAll(): void;
|
|
111
111
|
collapseAll(): void;
|
|
112
|
-
selectQuery(query:
|
|
113
|
-
isQueryVisible(query:
|
|
112
|
+
selectQuery(query: MJQueryEntityExtended, event?: Event): void;
|
|
113
|
+
isQueryVisible(query: MJQueryEntityExtended): boolean;
|
|
114
114
|
hasVisibleContent(node: CategoryNode): boolean;
|
|
115
115
|
onEntityLinkClick(event: QueryEntityLinkClickEvent): void;
|
|
116
116
|
onRowDoubleClick(event: QueryRowClickEvent): void;
|
|
@@ -137,10 +137,10 @@ export declare class QueryBrowserResourceComponent extends BaseResourceComponent
|
|
|
137
137
|
/** Open the drawer in create mode, optionally pre-selecting a category. */
|
|
138
138
|
OpenCreateDrawer(categoryID?: string): void;
|
|
139
139
|
/**
|
|
140
|
-
* Open the drawer in edit mode, pre-populated from a
|
|
140
|
+
* Open the drawer in edit mode, pre-populated from a MJQueryEntityExtended.
|
|
141
141
|
* Stops event propagation so clicking the edit icon doesn't also select the query.
|
|
142
142
|
*/
|
|
143
|
-
OpenEditDrawer(query:
|
|
143
|
+
OpenEditDrawer(query: MJQueryEntityExtended, event?: Event): void;
|
|
144
144
|
/**
|
|
145
145
|
* Close the drawer. If dirty, ask for confirmation unless force=true.
|
|
146
146
|
*/
|
|
@@ -162,7 +162,7 @@ export declare class QueryBrowserResourceComponent extends BaseResourceComponent
|
|
|
162
162
|
getNodeQueryCount(node: CategoryNode): number;
|
|
163
163
|
refresh(): void;
|
|
164
164
|
trackByCategory(index: number, node: CategoryNode): string;
|
|
165
|
-
trackByQuery(index: number, query:
|
|
165
|
+
trackByQuery(index: number, query: MJQueryEntityExtended): string;
|
|
166
166
|
/**
|
|
167
167
|
* Select a query by ID, expanding the tree to show it.
|
|
168
168
|
* Clears selection if no matching query is found.
|
|
@@ -188,7 +188,7 @@ export declare class QueryBrowserResourceComponent extends BaseResourceComponent
|
|
|
188
188
|
private onResizeMove;
|
|
189
189
|
private onResizeEnd;
|
|
190
190
|
/** Case-insensitive UUID check whether a query is the currently selected query. */
|
|
191
|
-
IsQuerySelected(query:
|
|
191
|
+
IsQuerySelected(query: MJQueryEntityExtended): boolean;
|
|
192
192
|
static ɵfac: i0.ɵɵFactoryDeclaration<QueryBrowserResourceComponent, never>;
|
|
193
193
|
static ɵcmp: i0.ɵɵComponentDeclaration<QueryBrowserResourceComponent, "mj-query-browser-resource", never, {}, {}, never, never, false, never>;
|
|
194
194
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-browser-resource.component.d.ts","sourceRoot":"","sources":["../../src/QueryBrowser/query-browser-resource.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAA2B,UAAU,EAAE,MAAM,EAA2B,MAAM,eAAe,CAAC;AAEtJ,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAY,
|
|
1
|
+
{"version":3,"file":"query-browser-resource.component.d.ts","sourceRoot":"","sources":["../../src/QueryBrowser/query-browser-resource.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,8 +9,8 @@ import { Component, ChangeDetectionStrategy, HostListener, ViewChild } from '@an
|
|
|
9
9
|
import { Subject } from 'rxjs';
|
|
10
10
|
import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
|
|
11
11
|
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
12
|
-
import {
|
|
13
|
-
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
12
|
+
import { CompositeKey } from '@memberjunction/core';
|
|
13
|
+
import { UserInfoEngine, QueryEngine } from '@memberjunction/core-entities';
|
|
14
14
|
import * as i0 from "@angular/core";
|
|
15
15
|
import * as i1 from "@angular/common";
|
|
16
16
|
import * as i2 from "@angular/forms";
|
|
@@ -618,13 +618,14 @@ let QueryBrowserResourceComponent = class QueryBrowserResourceComponent extends
|
|
|
618
618
|
try {
|
|
619
619
|
this.isLoading = true;
|
|
620
620
|
this.cdr.markForCheck();
|
|
621
|
-
// Force
|
|
621
|
+
// Force QueryEngine refresh when explicitly refreshing (user clicked Refresh)
|
|
622
622
|
if (forceRefresh) {
|
|
623
|
-
await
|
|
623
|
+
await QueryEngine.Instance.Config(true);
|
|
624
624
|
}
|
|
625
|
-
// Load all queries
|
|
626
|
-
|
|
627
|
-
this.
|
|
625
|
+
// Load all queries from QueryEngine (event-driven cache, returns MJQueryEntityExtended[])
|
|
626
|
+
const qe = QueryEngine.Instance;
|
|
627
|
+
this.categories = qe.Categories || [];
|
|
628
|
+
this.queries = (qe.Queries || []).filter(q => q.UserCanRun(this.metadata.CurrentUser));
|
|
628
629
|
this.applyFilters();
|
|
629
630
|
this.buildCategoryTree();
|
|
630
631
|
// Mark data as loaded
|
|
@@ -680,11 +681,11 @@ let QueryBrowserResourceComponent = class QueryBrowserResourceComponent extends
|
|
|
680
681
|
// Add uncategorized queries to a virtual root
|
|
681
682
|
const uncategorizedQueries = visibleQueries.filter(q => !q.CategoryID);
|
|
682
683
|
if (uncategorizedQueries.length > 0) {
|
|
683
|
-
const uncategorizedCategory =
|
|
684
|
+
const uncategorizedCategory = {
|
|
684
685
|
ID: '__uncategorized__',
|
|
685
686
|
Name: 'Uncategorized',
|
|
686
687
|
Description: 'Queries without a category'
|
|
687
|
-
}
|
|
688
|
+
};
|
|
688
689
|
roots.push({
|
|
689
690
|
category: uncategorizedCategory,
|
|
690
691
|
children: [],
|
|
@@ -1006,7 +1007,7 @@ let QueryBrowserResourceComponent = class QueryBrowserResourceComponent extends
|
|
|
1006
1007
|
setTimeout(() => this.drawerSqlEditor?.setValue(''), 0);
|
|
1007
1008
|
}
|
|
1008
1009
|
/**
|
|
1009
|
-
* Open the drawer in edit mode, pre-populated from a
|
|
1010
|
+
* Open the drawer in edit mode, pre-populated from a MJQueryEntityExtended.
|
|
1010
1011
|
* Stops event propagation so clicking the edit icon doesn't also select the query.
|
|
1011
1012
|
*/
|
|
1012
1013
|
OpenEditDrawer(query, event) {
|