@memberjunction/ng-base-forms 6.1.0-edge.2 → 6.1.0-edge.3
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/LICENSE +180 -4
- package/README.md +4 -1
- package/dist/lib/base-form-component.d.ts +93 -3
- package/dist/lib/base-form-component.d.ts.map +1 -1
- package/dist/lib/base-form-component.js +240 -9
- package/dist/lib/base-form-component.js.map +1 -1
- package/dist/lib/chrome/base-form-policy.d.ts +26 -0
- package/dist/lib/chrome/base-form-policy.d.ts.map +1 -0
- package/dist/lib/chrome/base-form-policy.js +9 -0
- package/dist/lib/chrome/base-form-policy.js.map +1 -0
- package/dist/lib/chrome/clipped-title.d.ts +8 -0
- package/dist/lib/chrome/clipped-title.d.ts.map +1 -0
- package/dist/lib/chrome/clipped-title.js +18 -0
- package/dist/lib/chrome/clipped-title.js.map +1 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.d.ts +35 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.d.ts.map +1 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.js +96 -0
- package/dist/lib/chrome/form-chrome-coordinator.service.js.map +1 -0
- package/dist/lib/chrome/form-chrome-rail-pref.d.ts +24 -0
- package/dist/lib/chrome/form-chrome-rail-pref.d.ts.map +1 -0
- package/dist/lib/chrome/form-chrome-rail-pref.js +52 -0
- package/dist/lib/chrome/form-chrome-rail-pref.js.map +1 -0
- package/dist/lib/chrome/form-chrome.d.ts +59 -0
- package/dist/lib/chrome/form-chrome.d.ts.map +1 -0
- package/dist/lib/chrome/form-chrome.js +37 -0
- package/dist/lib/chrome/form-chrome.js.map +1 -0
- package/dist/lib/chrome/load-form-chrome-rules.d.ts +8 -0
- package/dist/lib/chrome/load-form-chrome-rules.d.ts.map +1 -0
- package/dist/lib/chrome/load-form-chrome-rules.js +70 -0
- package/dist/lib/chrome/load-form-chrome-rules.js.map +1 -0
- package/dist/lib/chrome/resolve-form-chrome.d.ts +114 -0
- package/dist/lib/chrome/resolve-form-chrome.d.ts.map +1 -0
- package/dist/lib/chrome/resolve-form-chrome.js +713 -0
- package/dist/lib/chrome/resolve-form-chrome.js.map +1 -0
- package/dist/lib/container/record-form-container.component.d.ts +170 -6
- package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
- package/dist/lib/container/record-form-container.component.js +1194 -62
- package/dist/lib/container/record-form-container.component.js.map +1 -1
- package/dist/lib/explorer-entity-data-grid.component.d.ts +54 -1
- package/dist/lib/explorer-entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/explorer-entity-data-grid.component.js +143 -7
- package/dist/lib/explorer-entity-data-grid.component.js.map +1 -1
- package/dist/lib/form-record-refresh.coordinator.d.ts +27 -0
- package/dist/lib/form-record-refresh.coordinator.d.ts.map +1 -0
- package/dist/lib/form-record-refresh.coordinator.js +33 -0
- package/dist/lib/form-record-refresh.coordinator.js.map +1 -0
- package/dist/lib/form-state.interface.d.ts +4 -0
- package/dist/lib/form-state.interface.d.ts.map +1 -1
- package/dist/lib/form-state.interface.js.map +1 -1
- package/dist/lib/form-state.service.d.ts +5 -0
- package/dist/lib/form-state.service.d.ts.map +1 -1
- package/dist/lib/form-state.service.js +23 -2
- package/dist/lib/form-state.service.js.map +1 -1
- package/dist/lib/host/entity-form-host.component.d.ts +5 -1
- package/dist/lib/host/entity-form-host.component.d.ts.map +1 -1
- package/dist/lib/host/entity-form-host.component.js +16 -2
- package/dist/lib/host/entity-form-host.component.js.map +1 -1
- package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts +6 -2
- package/dist/lib/isa-related-panel/isa-related-panel.component.d.ts.map +1 -1
- package/dist/lib/isa-related-panel/isa-related-panel.component.js +15 -1
- package/dist/lib/isa-related-panel/isa-related-panel.component.js.map +1 -1
- package/dist/lib/panel/collapsible-panel.component.d.ts +24 -2
- package/dist/lib/panel/collapsible-panel.component.d.ts.map +1 -1
- package/dist/lib/panel/collapsible-panel.component.js +104 -10
- package/dist/lib/panel/collapsible-panel.component.js.map +1 -1
- package/dist/lib/panel-slot/base-form-panel.d.ts +85 -1
- package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -1
- package/dist/lib/panel-slot/base-form-panel.js +72 -3
- package/dist/lib/panel-slot/base-form-panel.js.map +1 -1
- package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts +8 -0
- package/dist/lib/panel-slot/collect-form-panel-registrations.d.ts.map +1 -0
- package/dist/lib/panel-slot/collect-form-panel-registrations.js +19 -0
- package/dist/lib/panel-slot/collect-form-panel-registrations.js.map +1 -0
- package/dist/lib/panel-slot/form-contribution.d.ts +79 -0
- package/dist/lib/panel-slot/form-contribution.d.ts.map +1 -0
- package/dist/lib/panel-slot/form-contribution.js +253 -0
- package/dist/lib/panel-slot/form-contribution.js.map +1 -0
- package/dist/lib/panel-slot/form-contributions.component.d.ts +27 -0
- package/dist/lib/panel-slot/form-contributions.component.d.ts.map +1 -0
- package/dist/lib/panel-slot/form-contributions.component.js +83 -0
- package/dist/lib/panel-slot/form-contributions.component.js.map +1 -0
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts +2 -0
- package/dist/lib/panel-slot/form-panel-slot.component.d.ts.map +1 -1
- package/dist/lib/panel-slot/form-panel-slot.component.js +35 -4
- package/dist/lib/panel-slot/form-panel-slot.component.js.map +1 -1
- package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts +22 -0
- package/dist/lib/panel-slot/related-entity-grid-panel.component.d.ts.map +1 -0
- package/dist/lib/panel-slot/related-entity-grid-panel.component.js +78 -0
- package/dist/lib/panel-slot/related-entity-grid-panel.component.js.map +1 -0
- package/dist/lib/related-grid-height.d.ts +20 -0
- package/dist/lib/related-grid-height.d.ts.map +1 -0
- package/dist/lib/related-grid-height.js +30 -0
- package/dist/lib/related-grid-height.js.map +1 -0
- package/dist/lib/section-manager/section-manager.component.d.ts +20 -1
- package/dist/lib/section-manager/section-manager.component.d.ts.map +1 -1
- package/dist/lib/section-manager/section-manager.component.js +204 -25
- package/dist/lib/section-manager/section-manager.component.js.map +1 -1
- package/dist/lib/toolbar/form-toolbar.component.d.ts +55 -4
- package/dist/lib/toolbar/form-toolbar.component.d.ts.map +1 -1
- package/dist/lib/toolbar/form-toolbar.component.js +734 -306
- package/dist/lib/toolbar/form-toolbar.component.js.map +1 -1
- package/dist/lib/types/form-events.d.ts +35 -0
- package/dist/lib/types/form-events.d.ts.map +1 -1
- package/dist/lib/types/form-events.js +45 -0
- package/dist/lib/types/form-events.js.map +1 -1
- package/dist/lib/types/form-toolbar-item.d.ts +145 -0
- package/dist/lib/types/form-toolbar-item.d.ts.map +1 -0
- package/dist/lib/types/form-toolbar-item.js +2 -0
- package/dist/lib/types/form-toolbar-item.js.map +1 -0
- package/dist/lib/types/form-types.d.ts +8 -0
- package/dist/lib/types/form-types.d.ts.map +1 -1
- package/dist/lib/types/form-types.js.map +1 -1
- package/dist/lib/types/toolbar-config.d.ts +7 -2
- package/dist/lib/types/toolbar-config.d.ts.map +1 -1
- package/dist/lib/types/toolbar-config.js +6 -0
- package/dist/lib/types/toolbar-config.js.map +1 -1
- package/dist/module.d.ts +17 -14
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +14 -0
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +13 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +14 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +19 -18
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import { ApplyFormChromeRules, ContributionInclusionFromRules, ResolveFormLayout, ReadRelationshipSortKey, ResolveRelatedFormRoles, } from '@memberjunction/core';
|
|
2
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
3
|
+
import { RelatedEntitySectionKey } from '../panel-slot/form-contribution';
|
|
4
|
+
import { BaseFormPolicy } from './base-form-policy';
|
|
5
|
+
import { DETAILS_SECTION_KEY, MORE_SECTION_KEY, HumanizeEntityTitle, IsAlwaysMoreSection, } from './form-chrome';
|
|
6
|
+
/**
|
|
7
|
+
* Last-wins `BaseFormPolicy` for this entity, or `null` when none is registered.
|
|
8
|
+
*/
|
|
9
|
+
export function ResolveFormPolicy(entityName) {
|
|
10
|
+
const registrations = MJGlobal.Instance.ClassFactory.GetAllRegistrationsByMetadata(BaseFormPolicy, (metadata) => {
|
|
11
|
+
if (!metadata)
|
|
12
|
+
return false;
|
|
13
|
+
const entity = metadata.entity;
|
|
14
|
+
return typeof entity === 'string' && entity === entityName;
|
|
15
|
+
});
|
|
16
|
+
if (registrations.length === 0)
|
|
17
|
+
return null;
|
|
18
|
+
const winner = [...registrations].sort((a, b) => (a.Priority ?? 0) - (b.Priority ?? 0)).at(-1);
|
|
19
|
+
if (!winner?.SubClass)
|
|
20
|
+
return null;
|
|
21
|
+
const Ctor = winner.SubClass;
|
|
22
|
+
return new Ctor();
|
|
23
|
+
}
|
|
24
|
+
export function BuildRelatedFormRoleCandidates(entity, relatedSchemaByEntityId, inboundCountByRelatedEntityId = new Map()) {
|
|
25
|
+
return entity.RelatedEntities.map((rel) => toCandidate(rel, relatedSchemaByEntityId, inboundCountByRelatedEntityId));
|
|
26
|
+
}
|
|
27
|
+
export function ResolveFormChrome(input) {
|
|
28
|
+
const formConfig = input.Entity.ConfigurationObject?.UI?.Form ?? null;
|
|
29
|
+
const ranked = ResolveRelatedFormRoles(input.Entity.SchemaName, formConfig, BuildRelatedFormRoleCandidates(input.Entity, input.RelatedSchemaByEntityId, input.InboundRelationshipCountByEntityId));
|
|
30
|
+
const assignments = ApplyFormChromeRules(input.Entity.ID ?? '', ranked.Assignments, input.ChromeRules ?? []);
|
|
31
|
+
const resolution = { ...ranked, Assignments: assignments };
|
|
32
|
+
const relatedRoles = mapRelatedRoles(input.Entity, assignments);
|
|
33
|
+
const contributionGroups = applyContributionRules(input.Entity.ID ?? '', input.ContributionSectionKeys ?? [], input.ContributionChromeGroupByKey ?? new Map(), input.ContributionInclusionByKey ?? new Map(), input.ChromeRules ?? []);
|
|
34
|
+
const hidden = new Set(input.HiddenSectionKeys ?? []);
|
|
35
|
+
for (const key of displayInFormFalseSectionKeys(input.Entity))
|
|
36
|
+
hidden.add(key);
|
|
37
|
+
for (const key of noneInclusionSectionKeys(input.Entity, assignments))
|
|
38
|
+
hidden.add(key);
|
|
39
|
+
for (const key of contributionGroups.hiddenKeys)
|
|
40
|
+
hidden.add(key);
|
|
41
|
+
const visiblePanels = input.Panels.filter((p) => !hidden.has(p.SectionKey));
|
|
42
|
+
const defaultSpec = BuildDefaultChromeSpec(visiblePanels, relatedRoles, formConfig, input.ContributionSectionKeys, contributionGroups.chromeGroupByKey);
|
|
43
|
+
if (input.IncludeUnbakedRelated !== false) {
|
|
44
|
+
addMissingRelatedGroups(defaultSpec, input.Entity, relatedRoles, hidden);
|
|
45
|
+
}
|
|
46
|
+
applyRelatedDisplayNames(defaultSpec, input.Entity);
|
|
47
|
+
mergeRelatedGroupsByEntity(defaultSpec, input.Entity);
|
|
48
|
+
sortFirstClassRelatedGroups(defaultSpec, input.Entity, assignments, input.ContributionSectionKeys, contributionGroups.leadKeys, input.ContributionSortKeyByKey ?? new Map());
|
|
49
|
+
ApplyUserChromeMembership(defaultSpec, input.Membership, visiblePanels);
|
|
50
|
+
const policy = ResolveFormPolicy(input.Entity.Name);
|
|
51
|
+
if (policy) {
|
|
52
|
+
const ctx = {
|
|
53
|
+
Entity: input.Entity,
|
|
54
|
+
RelatedRoles: resolution,
|
|
55
|
+
Panels: input.Panels,
|
|
56
|
+
PrimarySectionCount: countFirstClass(defaultSpec),
|
|
57
|
+
};
|
|
58
|
+
const decorated = policy.DecorateChrome(defaultSpec, ctx);
|
|
59
|
+
const spec = TakeDecoratedChrome(defaultSpec, decorated ?? defaultSpec);
|
|
60
|
+
ApplyUserChromeMembership(spec, input.Membership, visiblePanels);
|
|
61
|
+
ApplyFormChromeRuleTitles(spec, input.Entity, input.ChromeRules ?? []);
|
|
62
|
+
return { Spec: spec, RelatedRoles: resolution, PolicyUsed: true };
|
|
63
|
+
}
|
|
64
|
+
ApplyFormChromeRuleTitles(defaultSpec, input.Entity, input.ChromeRules ?? []);
|
|
65
|
+
return { Spec: defaultSpec, RelatedRoles: resolution, PolicyUsed: false };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Policy may rename groups and swap icons. Membership (section keys and
|
|
69
|
+
* More vs first-class) is data — a violating decorate is ignored.
|
|
70
|
+
*/
|
|
71
|
+
export function TakeDecoratedChrome(base, decorated) {
|
|
72
|
+
return sameChromeMembership(base, decorated) ? decorated : base;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* L3 Title overlay. Last Sequence wins. Blank Title is ignored so an
|
|
76
|
+
* incomplete rule cannot wipe DisplayName. Applied after policy decorate
|
|
77
|
+
* so an admin title survives an OpenApp policy rename.
|
|
78
|
+
*/
|
|
79
|
+
export function ApplyFormChromeRuleTitles(spec, entity, rules) {
|
|
80
|
+
const parent = (entity.ID ?? '').trim().toLowerCase();
|
|
81
|
+
if (!parent)
|
|
82
|
+
return;
|
|
83
|
+
const titled = rules
|
|
84
|
+
.filter((rule) => chromeIdsEqual(rule.EntityID, parent) && hasChromeTitle(rule.Title))
|
|
85
|
+
.sort((a, b) => (a.Sequence ?? 0) - (b.Sequence ?? 0));
|
|
86
|
+
if (titled.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
const displayInForm = entity.RelatedEntities.filter((rel) => rel.DisplayInForm);
|
|
89
|
+
const keysByRelatedId = new Map();
|
|
90
|
+
for (const rel of displayInForm) {
|
|
91
|
+
const id = (rel.RelatedEntityID ?? '').trim().toLowerCase();
|
|
92
|
+
if (!id)
|
|
93
|
+
continue;
|
|
94
|
+
const key = RelatedEntitySectionKey(rel, displayInForm);
|
|
95
|
+
const list = keysByRelatedId.get(id) ?? [];
|
|
96
|
+
list.push(key);
|
|
97
|
+
keysByRelatedId.set(id, list);
|
|
98
|
+
}
|
|
99
|
+
const titleByKey = new Map(spec.TitleBySectionKey);
|
|
100
|
+
for (const rule of titled) {
|
|
101
|
+
const title = (rule.Title ?? '').trim();
|
|
102
|
+
if (rule.TargetKind === 'Contribution') {
|
|
103
|
+
const key = (rule.ContributionKey ?? '').trim();
|
|
104
|
+
if (key)
|
|
105
|
+
titleByKey.set(key, title);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const relatedId = (rule.RelatedEntityID ?? '').trim().toLowerCase();
|
|
109
|
+
for (const key of keysByRelatedId.get(relatedId) ?? []) {
|
|
110
|
+
titleByKey.set(key, title);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (titleByKey.size === 0)
|
|
114
|
+
return;
|
|
115
|
+
spec.TitleBySectionKey = titleByKey;
|
|
116
|
+
for (const group of spec.Groups) {
|
|
117
|
+
if (group.IsMore)
|
|
118
|
+
continue;
|
|
119
|
+
const override = group.SectionKeys
|
|
120
|
+
.map((key) => titleByKey.get(key))
|
|
121
|
+
.find((value) => !!value);
|
|
122
|
+
if (override)
|
|
123
|
+
group.Title = override;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function hasChromeTitle(title) {
|
|
127
|
+
return (title ?? '').trim().length > 0;
|
|
128
|
+
}
|
|
129
|
+
function chromeIdsEqual(a, b) {
|
|
130
|
+
const left = (a ?? '').trim().toLowerCase();
|
|
131
|
+
const right = (b ?? '').trim().toLowerCase();
|
|
132
|
+
return left.length > 0 && left === right;
|
|
133
|
+
}
|
|
134
|
+
function sameChromeMembership(a, b) {
|
|
135
|
+
return keyFingerprint(collectSectionKeys(a)) === keyFingerprint(collectSectionKeys(b))
|
|
136
|
+
&& keyFingerprint(a.MoreSectionKeys) === keyFingerprint(b.MoreSectionKeys);
|
|
137
|
+
}
|
|
138
|
+
function collectSectionKeys(spec) {
|
|
139
|
+
return [...spec.Groups.flatMap((g) => g.SectionKeys), ...spec.MoreSectionKeys];
|
|
140
|
+
}
|
|
141
|
+
function keyFingerprint(keys) {
|
|
142
|
+
return [...new Set(keys.filter((k) => !!k))].sort().join('\0');
|
|
143
|
+
}
|
|
144
|
+
function applyContributionRules(parentEntityId, contributionSectionKeys, declared, l1InclusionByKey, rules) {
|
|
145
|
+
const chromeGroupByKey = new Map(declared);
|
|
146
|
+
const hiddenKeys = [];
|
|
147
|
+
const leadKeys = [];
|
|
148
|
+
for (const key of contributionSectionKeys) {
|
|
149
|
+
const inclusion = ContributionInclusionFromRules(parentEntityId, key, rules)
|
|
150
|
+
?? l1InclusionByKey.get(key)
|
|
151
|
+
?? null;
|
|
152
|
+
applyContributionInclusion(key, inclusion, chromeGroupByKey, hiddenKeys);
|
|
153
|
+
if (isLeadContribution(key, inclusion, chromeGroupByKey)) {
|
|
154
|
+
leadKeys.push(key);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { chromeGroupByKey, hiddenKeys, leadKeys };
|
|
158
|
+
}
|
|
159
|
+
function applyContributionInclusion(key, inclusion, chromeGroupByKey, hiddenKeys) {
|
|
160
|
+
if (inclusion == null)
|
|
161
|
+
return;
|
|
162
|
+
if (inclusion === 'None') {
|
|
163
|
+
hiddenKeys.push(key);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (inclusion === 'More') {
|
|
167
|
+
chromeGroupByKey.set(key, 'more');
|
|
168
|
+
}
|
|
169
|
+
// Primary is its own first-class rail item. Do not fold into Details.
|
|
170
|
+
}
|
|
171
|
+
function isLeadContribution(key, inclusion, chromeGroupByKey) {
|
|
172
|
+
if (inclusion !== 'Primary')
|
|
173
|
+
return false;
|
|
174
|
+
const pinned = chromeGroupByKey.get(key);
|
|
175
|
+
return pinned !== 'details' && pinned !== 'more';
|
|
176
|
+
}
|
|
177
|
+
export function BuildDefaultChromeSpec(panels, relatedRoles, formConfig, contributionSectionKeys = [], contributionChromeGroupByKey = new Map()) {
|
|
178
|
+
const moreKeys = [];
|
|
179
|
+
const fieldKeys = [];
|
|
180
|
+
const relatedGroups = [];
|
|
181
|
+
const contributions = new Set(contributionSectionKeys);
|
|
182
|
+
for (const panel of panels) {
|
|
183
|
+
if (!panel.SectionKey)
|
|
184
|
+
continue;
|
|
185
|
+
if (IsAlwaysMoreSection(panel.SectionKey, panel.SectionName)) {
|
|
186
|
+
moreKeys.push(panel.SectionKey);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const pinned = contributionChromeGroupByKey.get(panel.SectionKey);
|
|
190
|
+
if (pinned === 'details') {
|
|
191
|
+
fieldKeys.push(panel.SectionKey);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (pinned === 'more') {
|
|
195
|
+
moreKeys.push(panel.SectionKey);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (panel.Variant === 'related-entity' || contributions.has(panel.SectionKey)) {
|
|
199
|
+
if (relatedRoles.get(panel.SectionKey) === 'Detail') {
|
|
200
|
+
moreKeys.push(panel.SectionKey);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
relatedGroups.push(relatedGroupFromPanel(panel));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
fieldKeys.push(panel.SectionKey);
|
|
207
|
+
}
|
|
208
|
+
const groups = [];
|
|
209
|
+
if (fieldKeys.length > 0) {
|
|
210
|
+
const firstField = panels.find((p) => p.SectionKey === fieldKeys[0]);
|
|
211
|
+
groups.push({
|
|
212
|
+
Key: DETAILS_SECTION_KEY,
|
|
213
|
+
Title: 'Details',
|
|
214
|
+
Icon: firstField?.Icon?.trim() || 'fa-solid fa-id-card',
|
|
215
|
+
SectionKeys: fieldKeys,
|
|
216
|
+
IsMore: false,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
groups.push(...MergeChromeGroupsByTitle(relatedGroups));
|
|
220
|
+
appendMoreGroup(groups, moreKeys);
|
|
221
|
+
const layout = ResolveFormLayout(formConfig, groups.length);
|
|
222
|
+
return {
|
|
223
|
+
Layout: layout,
|
|
224
|
+
Groups: groups,
|
|
225
|
+
RelatedRoles: relatedRoles,
|
|
226
|
+
MoreSectionKeys: moreKeys,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function relatedRailIcon(rel) {
|
|
230
|
+
if (rel.DisplayIconType === 'Custom' && rel.DisplayIcon?.trim()) {
|
|
231
|
+
return rel.DisplayIcon.trim();
|
|
232
|
+
}
|
|
233
|
+
if (rel.DisplayIcon?.trim())
|
|
234
|
+
return rel.DisplayIcon.trim();
|
|
235
|
+
return 'fa-solid fa-table';
|
|
236
|
+
}
|
|
237
|
+
function relatedGroupFromPanel(panel) {
|
|
238
|
+
return {
|
|
239
|
+
Key: panel.SectionKey,
|
|
240
|
+
Title: HumanizeEntityTitle(panel.SectionName || panel.SectionKey),
|
|
241
|
+
Icon: panel.Icon?.trim() || 'fa-solid fa-table',
|
|
242
|
+
SectionKeys: [panel.SectionKey],
|
|
243
|
+
IsMore: false,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** First SectionKey index in a user order; used to sort rail groups. */
|
|
247
|
+
export function ChromeGroupOrderIndex(group, sectionOrder) {
|
|
248
|
+
let min = Number.MAX_SAFE_INTEGER;
|
|
249
|
+
for (const key of group.SectionKeys) {
|
|
250
|
+
const index = sectionOrder.indexOf(key);
|
|
251
|
+
if (index >= 0 && index < min)
|
|
252
|
+
min = index;
|
|
253
|
+
}
|
|
254
|
+
return min;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Layer a (possibly incomplete) user ranking on the current default rail.
|
|
258
|
+
* Keys the user ranked keep that relative order. Keys they never ranked
|
|
259
|
+
* (Overview, a new related grid) stay in their default slots instead of
|
|
260
|
+
* falling to the end — `form.sections` does not list slot-mounted leads.
|
|
261
|
+
*/
|
|
262
|
+
export function OverlayChromeSectionOrder(defaultOrder, userOrder) {
|
|
263
|
+
if (userOrder.length === 0)
|
|
264
|
+
return [...defaultOrder];
|
|
265
|
+
const defaultSet = new Set(defaultOrder);
|
|
266
|
+
const userSet = new Set(userOrder);
|
|
267
|
+
const result = userOrder.filter((key) => defaultSet.has(key));
|
|
268
|
+
for (let i = 0; i < defaultOrder.length; i++) {
|
|
269
|
+
const key = defaultOrder[i];
|
|
270
|
+
if (userSet.has(key))
|
|
271
|
+
continue;
|
|
272
|
+
let insertAt = 0;
|
|
273
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
274
|
+
const idx = result.indexOf(defaultOrder[j]);
|
|
275
|
+
if (idx >= 0) {
|
|
276
|
+
insertAt = idx + 1;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
result.splice(insertAt, 0, key);
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
/** Reorder More children from a persisted section-key order. */
|
|
285
|
+
export function OrderMoreSectionKeys(moreKeys, sectionOrder) {
|
|
286
|
+
return OverlayChromeSectionOrder(moreKeys, sectionOrder);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Keep the first-class rail order from the previous spec when chrome
|
|
290
|
+
* re-resolves (slot mount, grid load, contribution key appearing).
|
|
291
|
+
* Clicking a rail item must not reshuffle the list.
|
|
292
|
+
* New related groups append. New lead contributions (Overview) insert
|
|
293
|
+
* into the lead band before Details. More stays last.
|
|
294
|
+
* User drag still wins via {@link OrderChromeGroups}.
|
|
295
|
+
*/
|
|
296
|
+
export function StabilizeFirstClassGroupOrder(previous, next) {
|
|
297
|
+
if (!previous || previous.Groups.length === 0)
|
|
298
|
+
return next;
|
|
299
|
+
const details = next.Groups.filter((g) => g.Key === DETAILS_SECTION_KEY);
|
|
300
|
+
const more = next.Groups.filter((g) => g.IsMore);
|
|
301
|
+
const leads = next.Groups.filter((g) => !!g.IsLead);
|
|
302
|
+
const related = next.Groups.filter((g) => !g.IsMore && !g.IsLead && g.Key !== DETAILS_SECTION_KEY);
|
|
303
|
+
if (leads.length === 0 && related.length === 0)
|
|
304
|
+
return next;
|
|
305
|
+
const used = new Set();
|
|
306
|
+
const orderedLeads = [];
|
|
307
|
+
const orderedRelated = [];
|
|
308
|
+
const take = (bucket, group) => {
|
|
309
|
+
if (!group || used.has(group.Key))
|
|
310
|
+
return;
|
|
311
|
+
used.add(group.Key);
|
|
312
|
+
bucket.push(group);
|
|
313
|
+
};
|
|
314
|
+
const matchIn = (pool, prev) => {
|
|
315
|
+
const prevTitle = prev.Title.trim().toLowerCase();
|
|
316
|
+
const prevKeys = new Set(prev.SectionKeys);
|
|
317
|
+
return pool.find((g) => g.Key === prev.Key)
|
|
318
|
+
?? pool.find((g) => g.SectionKeys.some((key) => prevKeys.has(key)))
|
|
319
|
+
?? pool.find((g) => g.Title.trim().toLowerCase() === prevTitle);
|
|
320
|
+
};
|
|
321
|
+
for (const prev of previous.Groups) {
|
|
322
|
+
if (prev.IsMore || prev.Key === DETAILS_SECTION_KEY)
|
|
323
|
+
continue;
|
|
324
|
+
take(orderedLeads, matchIn(leads, prev));
|
|
325
|
+
take(orderedRelated, matchIn(related, prev));
|
|
326
|
+
}
|
|
327
|
+
for (const group of leads) {
|
|
328
|
+
take(orderedLeads, group);
|
|
329
|
+
}
|
|
330
|
+
for (const group of related) {
|
|
331
|
+
take(orderedRelated, group);
|
|
332
|
+
}
|
|
333
|
+
next.Groups = [...orderedLeads, ...details, ...orderedRelated, ...more];
|
|
334
|
+
return next;
|
|
335
|
+
}
|
|
336
|
+
/** Reorder first-class rail groups from a persisted section-key order. More stays last. */
|
|
337
|
+
export function OrderChromeGroups(groups, sectionOrder) {
|
|
338
|
+
if (sectionOrder.length === 0)
|
|
339
|
+
return [...groups];
|
|
340
|
+
const more = groups.filter((g) => g.IsMore);
|
|
341
|
+
const rest = groups.filter((g) => !g.IsMore);
|
|
342
|
+
const defaultKeys = rest.flatMap((g) => g.SectionKeys);
|
|
343
|
+
const overlaid = OverlayChromeSectionOrder(defaultKeys, sectionOrder);
|
|
344
|
+
rest.sort((a, b) => {
|
|
345
|
+
const ai = ChromeGroupOrderIndex(a, overlaid);
|
|
346
|
+
const bi = ChromeGroupOrderIndex(b, overlaid);
|
|
347
|
+
return ai - bi;
|
|
348
|
+
});
|
|
349
|
+
return [...rest, ...more];
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Move every SectionKey of `dragged` as a block in front of `target`
|
|
353
|
+
* inside a section-order list. Used by left-nav rail drag/drop.
|
|
354
|
+
*/
|
|
355
|
+
export function MoveChromeGroupInSectionOrder(sectionOrder, dragged, target) {
|
|
356
|
+
const draggedKeys = new Set(dragged.SectionKeys);
|
|
357
|
+
const without = sectionOrder.filter((key) => !draggedKeys.has(key));
|
|
358
|
+
const insertAt = without.findIndex((key) => target.SectionKeys.includes(key));
|
|
359
|
+
const next = [...without];
|
|
360
|
+
const at = insertAt >= 0 ? insertAt : next.length;
|
|
361
|
+
next.splice(at, 0, ...dragged.SectionKeys);
|
|
362
|
+
return next;
|
|
363
|
+
}
|
|
364
|
+
/** Collapse rail items that share a humanized title (From/To Relationships). */
|
|
365
|
+
export function MergeChromeGroupsByTitle(groups) {
|
|
366
|
+
const merged = [];
|
|
367
|
+
const byTitle = new Map();
|
|
368
|
+
for (const group of groups) {
|
|
369
|
+
const titleKey = group.Title.trim().toLowerCase();
|
|
370
|
+
const existing = byTitle.get(titleKey);
|
|
371
|
+
if (!existing) {
|
|
372
|
+
const copy = { ...group, SectionKeys: [...group.SectionKeys] };
|
|
373
|
+
byTitle.set(titleKey, copy);
|
|
374
|
+
merged.push(copy);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
for (const key of group.SectionKeys) {
|
|
378
|
+
if (!existing.SectionKeys.includes(key)) {
|
|
379
|
+
existing.SectionKeys.push(key);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return merged;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Apply a persisted user membership (move in/out of More) onto a spec
|
|
387
|
+
* in place. Always-more sections (System Metadata) cannot leave More.
|
|
388
|
+
*/
|
|
389
|
+
export function ApplyUserChromeMembership(spec, override, panels = []) {
|
|
390
|
+
if (!override)
|
|
391
|
+
return spec;
|
|
392
|
+
const userMore = override.moreSectionKeys ?? [];
|
|
393
|
+
const userFirst = override.firstClassSectionKeys ?? [];
|
|
394
|
+
if (userMore.length === 0 && userFirst.length === 0)
|
|
395
|
+
return spec;
|
|
396
|
+
const more = new Set(spec.MoreSectionKeys);
|
|
397
|
+
for (const key of userMore) {
|
|
398
|
+
if (key)
|
|
399
|
+
more.add(key);
|
|
400
|
+
}
|
|
401
|
+
const panelByKey = new Map(panels.map((p) => [p.SectionKey, p]));
|
|
402
|
+
for (const key of userFirst) {
|
|
403
|
+
if (!key)
|
|
404
|
+
continue;
|
|
405
|
+
const panel = panelByKey.get(key);
|
|
406
|
+
if (!IsAlwaysMoreSection(key, panel?.SectionName)) {
|
|
407
|
+
more.delete(key);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for (const panel of panels) {
|
|
411
|
+
if (IsAlwaysMoreSection(panel.SectionKey, panel.SectionName)) {
|
|
412
|
+
more.add(panel.SectionKey);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
for (const key of spec.MoreSectionKeys) {
|
|
416
|
+
if (IsAlwaysMoreSection(key))
|
|
417
|
+
more.add(key);
|
|
418
|
+
}
|
|
419
|
+
return RebuildChromeSpecMembership(spec, [...more], panels);
|
|
420
|
+
}
|
|
421
|
+
/** Rebuild Groups / MoreSectionKeys after membership changes. Layout is unchanged. */
|
|
422
|
+
export function RebuildChromeSpecMembership(spec, moreKeys, panels = []) {
|
|
423
|
+
const moreSet = new Set(moreKeys);
|
|
424
|
+
const panelByKey = new Map(panels.map((p) => [p.SectionKey, p]));
|
|
425
|
+
const groups = [];
|
|
426
|
+
for (const group of spec.Groups) {
|
|
427
|
+
if (group.IsMore)
|
|
428
|
+
continue;
|
|
429
|
+
const stay = group.SectionKeys.filter((key) => !moreSet.has(key));
|
|
430
|
+
if (stay.length === 0)
|
|
431
|
+
continue;
|
|
432
|
+
groups.push({ ...group, SectionKeys: stay });
|
|
433
|
+
}
|
|
434
|
+
const firstClass = new Set(groups.flatMap((g) => g.SectionKeys));
|
|
435
|
+
const allKnown = new Set([
|
|
436
|
+
...spec.Groups.flatMap((g) => g.SectionKeys),
|
|
437
|
+
...spec.MoreSectionKeys,
|
|
438
|
+
...moreKeys,
|
|
439
|
+
]);
|
|
440
|
+
for (const key of allKnown) {
|
|
441
|
+
if (!key || moreSet.has(key) || firstClass.has(key))
|
|
442
|
+
continue;
|
|
443
|
+
const panel = panelByKey.get(key);
|
|
444
|
+
groups.push({
|
|
445
|
+
Key: key,
|
|
446
|
+
Title: HumanizeEntityTitle(panel?.SectionName || key),
|
|
447
|
+
Icon: panel?.Icon?.trim() || 'fa-solid fa-table',
|
|
448
|
+
SectionKeys: [key],
|
|
449
|
+
IsMore: false,
|
|
450
|
+
});
|
|
451
|
+
firstClass.add(key);
|
|
452
|
+
}
|
|
453
|
+
appendMoreGroup(groups, [...moreSet]);
|
|
454
|
+
spec.Groups = groups;
|
|
455
|
+
spec.MoreSectionKeys = [...moreSet];
|
|
456
|
+
return spec;
|
|
457
|
+
}
|
|
458
|
+
/** Prefer EntityRelationship.DisplayName on single-key related groups. */
|
|
459
|
+
function applyRelatedDisplayNames(spec, entity) {
|
|
460
|
+
const displayInForm = entity.RelatedEntities.filter((rel) => rel.DisplayInForm);
|
|
461
|
+
for (const rel of displayInForm) {
|
|
462
|
+
const name = rel.DisplayName?.trim();
|
|
463
|
+
if (!name)
|
|
464
|
+
continue;
|
|
465
|
+
const sectionKey = RelatedEntitySectionKey(rel, displayInForm);
|
|
466
|
+
const group = spec.Groups.find((g) => !g.IsMore && g.SectionKeys.length === 1 && g.SectionKeys[0] === sectionKey);
|
|
467
|
+
if (group)
|
|
468
|
+
group.Title = name;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function appendMoreGroup(groups, moreKeys) {
|
|
472
|
+
if (moreKeys.length === 0)
|
|
473
|
+
return;
|
|
474
|
+
const existing = groups.find((g) => g.IsMore);
|
|
475
|
+
if (existing) {
|
|
476
|
+
existing.SectionKeys = moreKeys;
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
groups.push({
|
|
480
|
+
Key: MORE_SECTION_KEY,
|
|
481
|
+
Title: 'More',
|
|
482
|
+
Icon: 'fa-solid fa-ellipsis',
|
|
483
|
+
SectionKeys: moreKeys,
|
|
484
|
+
IsMore: true,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function mapRelatedRoles(entity, assignments) {
|
|
488
|
+
const displayInForm = entity.RelatedEntities.filter((rel) => rel.DisplayInForm);
|
|
489
|
+
const byId = new Map(assignments.map((a) => [a.RelationshipID.toLowerCase(), a]));
|
|
490
|
+
const roles = new Map();
|
|
491
|
+
for (const rel of displayInForm) {
|
|
492
|
+
const assignment = byId.get((rel.ID ?? '').toLowerCase());
|
|
493
|
+
if (!assignment)
|
|
494
|
+
continue;
|
|
495
|
+
const sectionKey = RelatedEntitySectionKey(rel, displayInForm);
|
|
496
|
+
roles.set(sectionKey, assignment.Role);
|
|
497
|
+
}
|
|
498
|
+
return roles;
|
|
499
|
+
}
|
|
500
|
+
function toCandidate(rel, relatedSchemaByEntityId, inboundCountByRelatedEntityId = new Map()) {
|
|
501
|
+
const relatedId = rel.RelatedEntityID ?? '';
|
|
502
|
+
const schema = relatedSchemaByEntityId.get(relatedId.toLowerCase())
|
|
503
|
+
?? relatedSchemaByEntityId.get(relatedId)
|
|
504
|
+
?? '';
|
|
505
|
+
const inbound = inboundCountByRelatedEntityId.get(relatedId.toLowerCase())
|
|
506
|
+
?? inboundCountByRelatedEntityId.get(relatedId);
|
|
507
|
+
return {
|
|
508
|
+
ID: rel.ID,
|
|
509
|
+
RelatedEntity: rel.RelatedEntity,
|
|
510
|
+
RelatedEntityID: rel.RelatedEntityID,
|
|
511
|
+
RelatedEntityJoinField: rel.RelatedEntityJoinField,
|
|
512
|
+
RelatedEntitySchemaName: schema,
|
|
513
|
+
DisplayInForm: rel.DisplayInForm,
|
|
514
|
+
DisplayLocation: rel.DisplayLocation,
|
|
515
|
+
DisplayComponentID: rel.DisplayComponentID,
|
|
516
|
+
RelatedRecordCollection: rel.RelatedRecordCollection,
|
|
517
|
+
JoinView: rel.JoinView,
|
|
518
|
+
Type: rel.Type,
|
|
519
|
+
Sequence: rel.Sequence,
|
|
520
|
+
Configuration: rel.Configuration,
|
|
521
|
+
InboundRelationshipCount: inbound,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/** First-class rail: lead contributions, then Details, then related by Primary/score. */
|
|
525
|
+
function sortFirstClassRelatedGroups(spec, entity, assignments, contributionSectionKeys = [], leadKeys = [], sortKeyByKey = new Map()) {
|
|
526
|
+
const displayInForm = entity.RelatedEntities.filter((rel) => rel.DisplayInForm);
|
|
527
|
+
const byId = new Map(assignments.map((a) => [a.RelationshipID.toLowerCase(), a]));
|
|
528
|
+
const scoreByKey = new Map();
|
|
529
|
+
const explicitPrimary = new Set();
|
|
530
|
+
const contrib = new Set(contributionSectionKeys);
|
|
531
|
+
const leadSet = new Set(leadKeys);
|
|
532
|
+
for (const rel of displayInForm) {
|
|
533
|
+
const assignment = byId.get((rel.ID ?? '').toLowerCase());
|
|
534
|
+
if (!assignment)
|
|
535
|
+
continue;
|
|
536
|
+
const key = RelatedEntitySectionKey(rel, displayInForm);
|
|
537
|
+
const current = scoreByKey.get(key);
|
|
538
|
+
scoreByKey.set(key, current == null ? assignment.Score : Math.max(current, assignment.Score));
|
|
539
|
+
if (assignment.Reason === 'explicit-primary')
|
|
540
|
+
explicitPrimary.add(key);
|
|
541
|
+
}
|
|
542
|
+
for (const key of contrib) {
|
|
543
|
+
scoreByKey.set(key, (scoreByKey.get(key) ?? 0) + 150);
|
|
544
|
+
}
|
|
545
|
+
const details = spec.Groups.filter((g) => g.Key === DETAILS_SECTION_KEY);
|
|
546
|
+
const more = spec.Groups.filter((g) => g.IsMore);
|
|
547
|
+
const firstClass = spec.Groups.filter((g) => !g.IsMore && g.Key !== DETAILS_SECTION_KEY);
|
|
548
|
+
const leads = [];
|
|
549
|
+
const related = [];
|
|
550
|
+
for (const group of firstClass) {
|
|
551
|
+
if (group.SectionKeys.some((key) => leadSet.has(key))) {
|
|
552
|
+
group.IsLead = true;
|
|
553
|
+
leads.push(group);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
related.push(group);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const combinedSort = mergeRelatedSortKeys(entity, displayInForm, sortKeyByKey);
|
|
560
|
+
leads.sort((a, b) => groupSortKey(b, combinedSort) - groupSortKey(a, combinedSort));
|
|
561
|
+
related.sort((a, b) => {
|
|
562
|
+
const aSort = groupSortKey(a, combinedSort);
|
|
563
|
+
const bSort = groupSortKey(b, combinedSort);
|
|
564
|
+
if (aSort !== bSort)
|
|
565
|
+
return bSort - aSort;
|
|
566
|
+
const aExplicit = a.SectionKeys.some((key) => explicitPrimary.has(key));
|
|
567
|
+
const bExplicit = b.SectionKeys.some((key) => explicitPrimary.has(key));
|
|
568
|
+
if (aExplicit !== bExplicit)
|
|
569
|
+
return aExplicit ? -1 : 1;
|
|
570
|
+
return groupScore(b, scoreByKey) - groupScore(a, scoreByKey);
|
|
571
|
+
});
|
|
572
|
+
spec.Groups = [...leads, ...details, ...related, ...more];
|
|
573
|
+
}
|
|
574
|
+
function mergeRelatedSortKeys(entity, displayInForm, contributionSortKeyByKey) {
|
|
575
|
+
const merged = new Map(contributionSortKeyByKey);
|
|
576
|
+
for (const rel of displayInForm) {
|
|
577
|
+
const sort = ReadRelationshipSortKey(rel.Configuration);
|
|
578
|
+
if (sort == null)
|
|
579
|
+
continue;
|
|
580
|
+
const key = RelatedEntitySectionKey(rel, displayInForm);
|
|
581
|
+
const current = merged.get(key);
|
|
582
|
+
merged.set(key, current == null ? sort : Math.max(current, sort));
|
|
583
|
+
}
|
|
584
|
+
return merged;
|
|
585
|
+
}
|
|
586
|
+
function groupSortKey(group, sortKeyByKey) {
|
|
587
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
588
|
+
for (const key of group.SectionKeys) {
|
|
589
|
+
const value = sortKeyByKey.get(key);
|
|
590
|
+
if (value != null && value > max)
|
|
591
|
+
max = value;
|
|
592
|
+
}
|
|
593
|
+
return max === Number.NEGATIVE_INFINITY ? 0 : max;
|
|
594
|
+
}
|
|
595
|
+
function noneInclusionSectionKeys(entity, assignments) {
|
|
596
|
+
const displayInForm = entity.RelatedEntities.filter((rel) => rel.DisplayInForm);
|
|
597
|
+
const noneIds = new Set(assignments.filter((a) => a.Inclusion === 'None').map((a) => a.RelationshipID.toLowerCase()));
|
|
598
|
+
return displayInForm
|
|
599
|
+
.filter((rel) => noneIds.has((rel.ID ?? '').toLowerCase()))
|
|
600
|
+
.map((rel) => RelatedEntitySectionKey(rel, displayInForm));
|
|
601
|
+
}
|
|
602
|
+
function displayInFormFalseSectionKeys(entity) {
|
|
603
|
+
const all = entity.RelatedEntities ?? [];
|
|
604
|
+
return all
|
|
605
|
+
.filter((rel) => !rel.DisplayInForm)
|
|
606
|
+
.map((rel) => RelatedEntitySectionKey(rel, all));
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Collapse first-class groups that are the same related entity on different
|
|
610
|
+
* join fields (Bill-To Orders + Ship-To Orders → one Orders rail item).
|
|
611
|
+
*/
|
|
612
|
+
function mergeRelatedGroupsByEntity(spec, entity) {
|
|
613
|
+
const displayInForm = entity.RelatedEntities.filter((rel) => rel.DisplayInForm);
|
|
614
|
+
const entityIdByKey = new Map();
|
|
615
|
+
const titleByEntityId = new Map();
|
|
616
|
+
for (const rel of displayInForm) {
|
|
617
|
+
const key = RelatedEntitySectionKey(rel, displayInForm);
|
|
618
|
+
const id = (rel.RelatedEntityID ?? '').toLowerCase();
|
|
619
|
+
if (!id)
|
|
620
|
+
continue;
|
|
621
|
+
entityIdByKey.set(key, id);
|
|
622
|
+
const named = rel.DisplayName?.trim();
|
|
623
|
+
if (named)
|
|
624
|
+
titleByEntityId.set(id, named);
|
|
625
|
+
else if (!titleByEntityId.has(id)) {
|
|
626
|
+
titleByEntityId.set(id, HumanizeEntityTitle(rel.RelatedEntity));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const details = spec.Groups.filter((g) => g.Key === DETAILS_SECTION_KEY);
|
|
630
|
+
const more = spec.Groups.filter((g) => g.IsMore);
|
|
631
|
+
const related = spec.Groups.filter((g) => !g.IsMore && g.Key !== DETAILS_SECTION_KEY);
|
|
632
|
+
const merged = [];
|
|
633
|
+
const byEntity = new Map();
|
|
634
|
+
for (const group of related) {
|
|
635
|
+
const entityId = group.SectionKeys
|
|
636
|
+
.map((key) => entityIdByKey.get(key))
|
|
637
|
+
.find((id) => !!id);
|
|
638
|
+
if (!entityId) {
|
|
639
|
+
merged.push(group);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const existing = byEntity.get(entityId);
|
|
643
|
+
if (!existing) {
|
|
644
|
+
const copy = {
|
|
645
|
+
...group,
|
|
646
|
+
Title: titleByEntityId.get(entityId) || group.Title,
|
|
647
|
+
SectionKeys: [...group.SectionKeys],
|
|
648
|
+
};
|
|
649
|
+
byEntity.set(entityId, copy);
|
|
650
|
+
merged.push(copy);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
for (const key of group.SectionKeys) {
|
|
654
|
+
if (!existing.SectionKeys.includes(key))
|
|
655
|
+
existing.SectionKeys.push(key);
|
|
656
|
+
}
|
|
657
|
+
const preferred = titleByEntityId.get(entityId);
|
|
658
|
+
if (preferred)
|
|
659
|
+
existing.Title = preferred;
|
|
660
|
+
}
|
|
661
|
+
spec.Groups = [...details, ...merged, ...more];
|
|
662
|
+
}
|
|
663
|
+
function groupScore(group, scoreByKey) {
|
|
664
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
665
|
+
for (const key of group.SectionKeys) {
|
|
666
|
+
const score = scoreByKey.get(key);
|
|
667
|
+
if (score != null && score > max)
|
|
668
|
+
max = score;
|
|
669
|
+
}
|
|
670
|
+
return max === Number.NEGATIVE_INFINITY ? 0 : max;
|
|
671
|
+
}
|
|
672
|
+
function addMissingRelatedGroups(spec, entity, relatedRoles, hiddenSectionKeys = new Set()) {
|
|
673
|
+
const displayInForm = entity.RelatedEntities.filter((rel) => rel.DisplayInForm);
|
|
674
|
+
const known = new Set([
|
|
675
|
+
...spec.Groups.flatMap((g) => g.SectionKeys),
|
|
676
|
+
...spec.MoreSectionKeys,
|
|
677
|
+
...hiddenSectionKeys,
|
|
678
|
+
]);
|
|
679
|
+
for (const rel of displayInForm) {
|
|
680
|
+
const sectionKey = RelatedEntitySectionKey(rel, displayInForm);
|
|
681
|
+
if (known.has(sectionKey) || hiddenSectionKeys.has(sectionKey))
|
|
682
|
+
continue;
|
|
683
|
+
const role = relatedRoles.get(sectionKey);
|
|
684
|
+
if (role === 'Detail') {
|
|
685
|
+
spec.MoreSectionKeys.push(sectionKey);
|
|
686
|
+
known.add(sectionKey);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (role === 'Primary') {
|
|
690
|
+
const title = HumanizeEntityTitle(rel.DisplayName?.trim() || rel.RelatedEntity);
|
|
691
|
+
const existing = spec.Groups.find((g) => !g.IsMore && g.Title.trim().toLowerCase() === title.toLowerCase());
|
|
692
|
+
if (existing) {
|
|
693
|
+
existing.SectionKeys.push(sectionKey);
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
spec.Groups.push({
|
|
697
|
+
Key: sectionKey,
|
|
698
|
+
Title: title,
|
|
699
|
+
Icon: relatedRailIcon(rel),
|
|
700
|
+
SectionKeys: [sectionKey],
|
|
701
|
+
IsMore: false,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
known.add(sectionKey);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
appendMoreGroup(spec.Groups, spec.MoreSectionKeys);
|
|
708
|
+
spec.Layout = ResolveFormLayout(entity.ConfigurationObject?.UI?.Form ?? null, spec.Groups.length);
|
|
709
|
+
}
|
|
710
|
+
function countFirstClass(spec) {
|
|
711
|
+
return spec.Groups.length;
|
|
712
|
+
}
|
|
713
|
+
//# sourceMappingURL=resolve-form-chrome.js.map
|