@memberjunction/ng-explorer-core 6.2.0-edge.4 → 6.2.0-edge.6
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 +7 -2
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +20 -10
- package/dist/app-routing.module.js.map +1 -1
- package/dist/generated/lazy-feature-config.d.ts +1 -1
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +6 -3
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/oauth/oauth-callback.component.d.ts +2 -0
- package/dist/lib/oauth/oauth-callback.component.d.ts.map +1 -1
- package/dist/lib/oauth/oauth-callback.component.js +10 -10
- package/dist/lib/oauth/oauth-callback.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +17 -3
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +211 -13
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +12 -3
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +18 -3
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/record-form-mode.d.ts +44 -0
- package/dist/lib/resource-wrappers/record-form-mode.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/record-form-mode.js +47 -0
- package/dist/lib/resource-wrappers/record-form-mode.js.map +1 -0
- package/dist/lib/resource-wrappers/record-resource.component.d.ts +56 -1
- package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.js +124 -9
- package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.d.ts +3 -3
- package/dist/lib/resource-wrappers/search-results-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js +5 -10
- package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/standard-form-create.d.ts +4 -0
- package/dist/lib/resource-wrappers/standard-form-create.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/standard-form-create.js +6 -0
- package/dist/lib/resource-wrappers/standard-form-create.js.map +1 -0
- package/dist/lib/resource-wrappers/view-resource.component.d.ts +18 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +105 -39
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/server-connectivity/server-connectivity-banner.component.d.ts +16 -3
- package/dist/lib/server-connectivity/server-connectivity-banner.component.d.ts.map +1 -1
- package/dist/lib/server-connectivity/server-connectivity-banner.component.js +17 -3
- package/dist/lib/server-connectivity/server-connectivity-banner.component.js.map +1 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts.map +1 -1
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +12 -8
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +6 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.js +18 -3
- package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +36 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +188 -47
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/deep-link-workspace.d.ts +25 -0
- package/dist/lib/shell/deep-link-workspace.d.ts.map +1 -0
- package/dist/lib/shell/deep-link-workspace.js +49 -0
- package/dist/lib/shell/deep-link-workspace.js.map +1 -0
- package/dist/lib/shell/shell-chrome-policy.d.ts +6 -0
- package/dist/lib/shell/shell-chrome-policy.d.ts.map +1 -1
- package/dist/lib/shell/shell-chrome-policy.js +1 -0
- package/dist/lib/shell/shell-chrome-policy.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +35 -0
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +348 -78
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +39 -2
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +99 -12
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.d.ts +10 -2
- package/dist/lib/user-notifications/user-notifications.component.d.ts.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.js +77 -22
- package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
- package/package.json +57 -57
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Component, ViewChild, runInInjectionContext, createComponent, ViewEncapsulation, HostListener, Output, EventEmitter, inject } from '@angular/core';
|
|
2
|
-
import { GoldenLayoutManager, FlattenLayoutToSingleStack } from '@memberjunction/ng-base-application';
|
|
2
|
+
import { GoldenLayoutManager, FlattenLayoutToSingleStack, TabService } from '@memberjunction/ng-base-application';
|
|
3
3
|
import { MJGlobal } from '@memberjunction/global';
|
|
4
|
-
import { BaseResourceComponent, HomeAppPinService, NavigationService, IsRecordTabsStyle, IsRecordsTabConfiguration, IsRecordsRegionTab, IsRecordDockedToWorkspace, RECORD_DOCKED_TO_WORKSPACE_KEY, GetRecordSourceContext, SafeDetectChanges, ExplorerBreakpointService, ResolveRecordTypeIcon } from '@memberjunction/ng-shared';
|
|
4
|
+
import { BaseResourceComponent, HomeAppPinService, NavigationService, IsRecordTabsStyle, IsRecordsTabConfiguration, IsRecordsRegionTab, IsRecordDockedToWorkspace, RECORD_DOCKED_TO_WORKSPACE_KEY, GetRecordSourceContext, SafeDetectChanges, ExplorerBreakpointService, ResolveRecordTypeIcon, ResolveRecordTabDisplayTitle, GetDeletedRecordFromEvent, RecordTabShowsRecord, RemoveDeletedRecordFromOrigin, OriginToTabConfiguration, OriginHasReturnTarget, NormalizeRecordIdForMatch } from '@memberjunction/ng-shared';
|
|
5
5
|
import { ResourceData, ResourcePermissionEngine } from '@memberjunction/core-entities';
|
|
6
6
|
import { RecordOriginCrumbComponent } from '../record-open/record-origin-crumb.component';
|
|
7
7
|
import { RecordSwitcherService } from '../record-open/record-switcher.service';
|
|
@@ -216,6 +216,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
216
216
|
layoutInitError = this.LayoutInitError;
|
|
217
217
|
pinService = inject(HomeAppPinService);
|
|
218
218
|
navigationService = inject(NavigationService);
|
|
219
|
+
tabService = inject(TabService);
|
|
219
220
|
recordSwitcher = inject(RecordSwitcherService);
|
|
220
221
|
subscriptions = [];
|
|
221
222
|
layoutInitRetryCount = 0;
|
|
@@ -418,7 +419,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
418
419
|
const activeTab = config.tabs.find(t => t.id === config.activeTabId);
|
|
419
420
|
if (activeTab && this.isRecordTab(activeTab)) {
|
|
420
421
|
const app = this.appManager.GetAppById(activeTab.applicationId);
|
|
421
|
-
this.RecordBarTitle = activeTab
|
|
422
|
+
this.RecordBarTitle = this.resolveTabTitle(activeTab);
|
|
422
423
|
this.RecordBarIcon = ResolveRecordTypeIcon(activeTab.configuration, this.ProviderToUse);
|
|
423
424
|
this.RecordBarColor = app?.GetColor() || DEFAULT_APP_COLOR;
|
|
424
425
|
}
|
|
@@ -445,13 +446,16 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
445
446
|
: null;
|
|
446
447
|
return {
|
|
447
448
|
TabId: tab.id,
|
|
448
|
-
Title: tab
|
|
449
|
+
Title: this.resolveTabTitle(tab),
|
|
449
450
|
Icon: ResolveRecordTypeIcon(tab.configuration, this.ProviderToUse),
|
|
450
451
|
Color: app?.GetColor() || DEFAULT_APP_COLOR,
|
|
451
452
|
OriginLabel: originLabel,
|
|
452
453
|
IsActive: tab.id === activeTabId
|
|
453
454
|
};
|
|
454
455
|
}
|
|
456
|
+
resolveTabTitle(tab) {
|
|
457
|
+
return ResolveRecordTabDisplayTitle(tab, this.ProviderToUse) || tab.title;
|
|
458
|
+
}
|
|
455
459
|
/** True while the RECORDS region is the visible surface (active tab is a record) */
|
|
456
460
|
ShowRecordsRegion = false;
|
|
457
461
|
/**
|
|
@@ -473,6 +477,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
473
477
|
flushRegionCd() {
|
|
474
478
|
SafeDetectChanges(this.cdr);
|
|
475
479
|
}
|
|
480
|
+
recordsLayoutInitRetryCount = 0;
|
|
476
481
|
/**
|
|
477
482
|
* Keep the RECORDS region in lockstep with the workspace configuration:
|
|
478
483
|
* resolve region visibility (active tab is a record), lazily initialize the
|
|
@@ -493,6 +498,13 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
493
498
|
const recordTabs = config.tabs.filter(t => this.isRecordTab(t));
|
|
494
499
|
const activeTab = config.tabs.find(t => t.id === config.activeTabId);
|
|
495
500
|
const showing = !!activeTab && this.isRecordTab(activeTab);
|
|
501
|
+
if (showing !== this.ShowRecordsRegion) {
|
|
502
|
+
this.ShowRecordsRegion = showing;
|
|
503
|
+
// Flush NOW — the visibility class must land BEFORE records GL init/tab
|
|
504
|
+
// creation so synchronous TabShown events see ShowRecordsRegion=true and
|
|
505
|
+
// load the active record immediately instead of parking it.
|
|
506
|
+
this.flushRegionCd();
|
|
507
|
+
}
|
|
496
508
|
// EAGER init + sync on EVERY emission. The region container always keeps
|
|
497
509
|
// its full layout box (visibility-hidden, never display:none — see the
|
|
498
510
|
// component CSS), so the records GL can initialize and reconcile even
|
|
@@ -506,14 +518,8 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
506
518
|
if (this.recordsLayoutInitialized) {
|
|
507
519
|
this.syncRecordsTabs(recordTabs);
|
|
508
520
|
}
|
|
509
|
-
if (
|
|
510
|
-
this.
|
|
511
|
-
// Flush NOW — the visibility class must land in this pass, not
|
|
512
|
-
// whenever the next unrelated emission happens to run CD.
|
|
513
|
-
this.flushRegionCd();
|
|
514
|
-
if (showing) {
|
|
515
|
-
this.flushPendingRecordShows();
|
|
516
|
-
}
|
|
521
|
+
if (this.ShowRecordsRegion) {
|
|
522
|
+
this.flushPendingRecordShows();
|
|
517
523
|
}
|
|
518
524
|
if (showing && activeTab && this.recordsLayoutInitialized) {
|
|
519
525
|
this.focusRecordsTab(activeTab.id);
|
|
@@ -546,7 +552,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
546
552
|
const app = this.appManager.GetAppById(tab.applicationId);
|
|
547
553
|
this.recordsLayoutManager.UpdateTabStyle(tab.id, {
|
|
548
554
|
isPinned: tab.isPinned,
|
|
549
|
-
title: tab
|
|
555
|
+
title: this.resolveTabTitle(tab),
|
|
550
556
|
appColor: app?.GetColor() || DEFAULT_APP_COLOR,
|
|
551
557
|
typeIcon: this.resolveTabTypeIcon(tab)
|
|
552
558
|
});
|
|
@@ -736,17 +742,24 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
736
742
|
return;
|
|
737
743
|
}
|
|
738
744
|
const container = this.RecordsGlContainer?.nativeElement;
|
|
739
|
-
if (!container) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
745
|
+
if (!container || container.getBoundingClientRect().height === 0) {
|
|
746
|
+
// NEVER initialize GL inside a missing or zero-size (hidden) container —
|
|
747
|
+
// it bakes a 0x0 internal size into every stack it later creates and the
|
|
748
|
+
// region renders collapsed forever. If the records region IS the active
|
|
749
|
+
// surface (e.g. startup deep link to a record before layout settles),
|
|
750
|
+
// schedule a bounded retry so initialization is not stranded.
|
|
751
|
+
if (this.ShowRecordsRegion && this.recordsLayoutInitRetryCount < this.MAX_LAYOUT_INIT_RETRIES) {
|
|
752
|
+
this.recordsLayoutInitRetryCount++;
|
|
753
|
+
setTimeout(() => {
|
|
754
|
+
const latest = this.workspaceManager.GetConfiguration();
|
|
755
|
+
if (latest && !this.recordsLayoutInitialized) {
|
|
756
|
+
this.syncRecordsRegion(latest);
|
|
757
|
+
}
|
|
758
|
+
}, 50);
|
|
759
|
+
}
|
|
748
760
|
return;
|
|
749
761
|
}
|
|
762
|
+
this.recordsLayoutInitRetryCount = 0;
|
|
750
763
|
// Mobile: headerless GL — the record bar replaces the strip
|
|
751
764
|
this.recordsLayoutManager.Initialize(container, { HideHeaders: this.mobileRecordsActive });
|
|
752
765
|
this.recordsLayoutInitialized = true;
|
|
@@ -765,6 +778,14 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
765
778
|
// loading such a layout would render a ghost pane for a closed record.
|
|
766
779
|
if (layoutToLoad?.root && recordTabs.length > 0 && this.layoutCoversExactTabSet(layoutToLoad.root, recordTabs)) {
|
|
767
780
|
if (this.recordsLayoutManager.LoadLayout(layoutToLoad)) {
|
|
781
|
+
recordTabs.forEach(tab => this.updateTabDisplayName(tab));
|
|
782
|
+
setTimeout(() => {
|
|
783
|
+
const latest = this.workspaceManager.GetConfiguration();
|
|
784
|
+
if (latest?.activeTabId && this.ShowRecordsRegion) {
|
|
785
|
+
this.focusRecordsTab(latest.activeTabId);
|
|
786
|
+
}
|
|
787
|
+
this.recordsLayoutManager.updateSize();
|
|
788
|
+
}, 50);
|
|
768
789
|
return;
|
|
769
790
|
}
|
|
770
791
|
}
|
|
@@ -824,7 +845,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
824
845
|
tabId: tab.id,
|
|
825
846
|
appId: tab.applicationId,
|
|
826
847
|
appColor: app?.GetColor() || DEFAULT_APP_COLOR,
|
|
827
|
-
title: tab
|
|
848
|
+
title: this.resolveTabTitle(tab),
|
|
828
849
|
route: tab.configuration['route'] || '',
|
|
829
850
|
isPinned: tab.isPinned,
|
|
830
851
|
isLoaded: false,
|
|
@@ -874,6 +895,23 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
874
895
|
this.ShowContextMenu(event.x, event.y, event.tabId, event.anchorEl);
|
|
875
896
|
}));
|
|
876
897
|
this.wireRecordsLayoutEvents();
|
|
898
|
+
// A record deleted in this session: close every tab showing it and move the
|
|
899
|
+
// user off it (see HandleRecordDeleted). Remote deletes are left to the
|
|
900
|
+
// record tab itself, which shows a banner rather than vanishing under the user.
|
|
901
|
+
this.subscriptions.push(MJGlobal.Instance.GetEventListener().subscribe(event => {
|
|
902
|
+
// Read the key NOW: right after raising 'delete' the entity calls
|
|
903
|
+
// NewRecord() and its key is gone.
|
|
904
|
+
const deleted = GetDeletedRecordFromEvent(event);
|
|
905
|
+
if (!deleted || deleted.Remote) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
// Deferred one macrotask: the event fires inside BaseEntity.Delete(), and
|
|
909
|
+
// the deleting form still has to finish (success toast, RecordDeleted
|
|
910
|
+
// handlers) before its tab is torn down.
|
|
911
|
+
setTimeout(() => {
|
|
912
|
+
this.HandleRecordDeleted(deleted).catch(err => LogError(err));
|
|
913
|
+
}, 0);
|
|
914
|
+
}));
|
|
877
915
|
// Breakpoint crossings rebuild the records GL in the other chrome mode
|
|
878
916
|
// (headerless+flattened vs full strip+splits). Skip-if-equal: the
|
|
879
917
|
// BehaviorSubject replays the current value on subscribe.
|
|
@@ -1036,6 +1074,11 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1036
1074
|
}));
|
|
1037
1075
|
}
|
|
1038
1076
|
ngAfterViewInit() {
|
|
1077
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
1078
|
+
if (config) {
|
|
1079
|
+
this.syncRecordsRegion(config);
|
|
1080
|
+
this.updateRecordSurfaceState(config);
|
|
1081
|
+
}
|
|
1039
1082
|
// Initialize Golden Layout only if we're not in single-resource mode
|
|
1040
1083
|
if (!this.UseSingleResourceMode) {
|
|
1041
1084
|
this.initializeGoldenLayout();
|
|
@@ -1425,7 +1468,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1425
1468
|
const driverClass = resourceData.Configuration?.resourceTypeDriverClass || resourceData.ResourceType;
|
|
1426
1469
|
// Entity name discriminates between "new record" tabs of different entities (all
|
|
1427
1470
|
// have empty ResourceRecordID otherwise). For non-Records resources this is undefined.
|
|
1428
|
-
const cacheDiscriminator = resourceData.Configuration
|
|
1471
|
+
const cacheDiscriminator = this.resolveCacheDiscriminator(resourceData.Configuration);
|
|
1429
1472
|
// **OPTIMIZATION: Check cache first to reuse existing loaded component**
|
|
1430
1473
|
const cached = this.cacheManager.getCachedComponent(driverClass, resourceData.ResourceRecordID || '', activeTab.applicationId, cacheDiscriminator);
|
|
1431
1474
|
if (cached) {
|
|
@@ -1727,13 +1770,26 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1727
1770
|
const config = this.workspaceManager.GetConfiguration();
|
|
1728
1771
|
const tab = config?.tabs.find(t => t.id === tabId);
|
|
1729
1772
|
const isLastTab = config?.tabs.length === 1 && config.tabs[0].id === tabId;
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1773
|
+
this.closeTabDestroyingComponent(tabId);
|
|
1774
|
+
if (isLastTab && tab) {
|
|
1775
|
+
// CloseTab kept the tab around but unpinned it. Replace it with the app's
|
|
1776
|
+
// default tab so the user lands on a meaningful surface (typically the
|
|
1777
|
+
// home dashboard) instead of staying on the discarded form.
|
|
1778
|
+
await this.openAppDefaultTab(tab.applicationId);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Close a tab and DESTROY (not just detach) its cached component.
|
|
1783
|
+
*
|
|
1784
|
+
* The default tab-close path detaches and keeps components alive for reuse —
|
|
1785
|
+
* but a discarded or deleted record's form is exactly what we DON'T want to
|
|
1786
|
+
* keep: it holds a stale BaseEntity in view mode. The next "Create New Record"
|
|
1787
|
+
* click for the same entity would otherwise hit the cache discriminator
|
|
1788
|
+
* lookup, find this stale component, and reattach it — surfacing a blank form
|
|
1789
|
+
* in view mode instead of a fresh edit-mode form. Destroy here forces a cache
|
|
1790
|
+
* miss on the next open.
|
|
1791
|
+
*/
|
|
1792
|
+
closeTabDestroyingComponent(tabId) {
|
|
1737
1793
|
if (this.singleResourceCacheIdentity?.tabId === tabId) {
|
|
1738
1794
|
// Single-resource mode: the component is tracked via singleResource* fields,
|
|
1739
1795
|
// and its cache entry is currently marked attached. Use the identity directly
|
|
@@ -1744,7 +1800,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1744
1800
|
this.singleResourceComponentRef = null;
|
|
1745
1801
|
this.singleResourceCacheIdentity = null;
|
|
1746
1802
|
// Clear the host container's DOM so the user isn't briefly looking at the
|
|
1747
|
-
// destroyed component's wrapper between CloseTab and the
|
|
1803
|
+
// destroyed component's wrapper between CloseTab and the next tab's load.
|
|
1748
1804
|
const directContainer = this.DirectContentContainer?.nativeElement;
|
|
1749
1805
|
if (directContainer) {
|
|
1750
1806
|
while (directContainer.firstChild)
|
|
@@ -1758,18 +1814,78 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1758
1814
|
this.componentRefs.delete(tabId);
|
|
1759
1815
|
}
|
|
1760
1816
|
this.workspaceManager.CloseTab(tabId);
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1817
|
+
}
|
|
1818
|
+
/** Open an app's default tab (its home / landing surface), if it has one. */
|
|
1819
|
+
async openAppDefaultTab(appId) {
|
|
1820
|
+
const app = this.appManager.GetAppById(appId);
|
|
1821
|
+
if (app) {
|
|
1822
|
+
const defaultTabRequest = await app.CreateDefaultTab();
|
|
1823
|
+
if (defaultTabRequest) {
|
|
1824
|
+
this.workspaceManager.OpenTab(defaultTabRequest, app.GetColor());
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* A record was deleted in this session — from its own form, a custom form, a
|
|
1830
|
+
* grid row, or any other `BaseEntity.Delete()`. Nothing may keep showing it:
|
|
1831
|
+
*
|
|
1832
|
+
* 1. Other record tabs whose origin chain names it get the record spliced out,
|
|
1833
|
+
* so their crumb returns to where the user was before it.
|
|
1834
|
+
* 2. Every record tab showing it closes, its cached form destroyed.
|
|
1835
|
+
* 3. If one of those was the ACTIVE tab, the user is taken back to where it
|
|
1836
|
+
* was opened from; with no origin (e.g. loaded straight from a URL), to the
|
|
1837
|
+
* Home dashboard, or the tab's own app landing page when there is no Home
|
|
1838
|
+
* app. Its URL's history entry is replaced so browser Back can't reopen it.
|
|
1839
|
+
*
|
|
1840
|
+
* Inactive tabs showing it just close: the user wasn't looking at them.
|
|
1841
|
+
*/
|
|
1842
|
+
async HandleRecordDeleted(record) {
|
|
1843
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
1844
|
+
if (!config) {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
const showing = config.tabs.filter(t => RecordTabShowsRecord(t, record));
|
|
1848
|
+
const showingIds = new Set(showing.map(t => t.id));
|
|
1849
|
+
for (const tab of config.tabs) {
|
|
1850
|
+
if (showingIds.has(tab.id) || !IsRecordsTabConfiguration(tab.configuration)) {
|
|
1851
|
+
continue;
|
|
1771
1852
|
}
|
|
1853
|
+
const origin = GetRecordSourceContext(tab.configuration) ?? undefined;
|
|
1854
|
+
const pruned = RemoveDeletedRecordFromOrigin(origin, record);
|
|
1855
|
+
if (pruned !== origin) {
|
|
1856
|
+
this.workspaceManager.UpdateTabConfiguration(tab.id, OriginToTabConfiguration(pruned));
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
if (showing.length === 0) {
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
const activeTab = showing.find(t => t.id === config.activeTabId);
|
|
1863
|
+
const returnTo = activeTab
|
|
1864
|
+
? RemoveDeletedRecordFromOrigin(GetRecordSourceContext(activeTab.configuration) ?? undefined, record)
|
|
1865
|
+
: undefined;
|
|
1866
|
+
if (activeTab) {
|
|
1867
|
+
// Mark by what the tab's URL carries (its own entity string and id), not
|
|
1868
|
+
// the event's canonical name: the URL is built from the tab.
|
|
1869
|
+
const urlEntity = activeTab.configuration?.['Entity'];
|
|
1870
|
+
this.tabService.MarkRecordUrlForReplacement(typeof urlEntity === 'string' ? urlEntity : record.EntityName, NormalizeRecordIdForMatch(activeTab.resourceRecordId || record.RecordId));
|
|
1871
|
+
}
|
|
1872
|
+
for (const tab of showing) {
|
|
1873
|
+
this.closeTabDestroyingComponent(tab.id);
|
|
1874
|
+
}
|
|
1875
|
+
if (activeTab) {
|
|
1876
|
+
await this.leaveDeletedRecord(activeTab, returnTo);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
/** Where the user goes after the record they were looking at is deleted. */
|
|
1880
|
+
async leaveDeletedRecord(deletedTab, origin) {
|
|
1881
|
+
if (OriginHasReturnTarget(origin)) {
|
|
1882
|
+
await this.navigationService.ReturnToRecordSource(origin);
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
if (await this.navigationService.OpenHomeDashboard()) {
|
|
1886
|
+
return;
|
|
1772
1887
|
}
|
|
1888
|
+
await this.openAppDefaultTab(deletedTab.applicationId);
|
|
1773
1889
|
}
|
|
1774
1890
|
/**
|
|
1775
1891
|
* Generate a signature for tab content to detect when content changes
|
|
@@ -1780,13 +1896,15 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1780
1896
|
// IMPORTANT: Check both resourceRecordId AND configuration.recordId
|
|
1781
1897
|
// because for nav items, the recordId is stored in configuration, not resourceRecordId
|
|
1782
1898
|
const effectiveRecordId = tab.resourceRecordId || tab.configuration?.recordId || '';
|
|
1899
|
+
const effectiveAbv = tab.configuration?.['AlternateBaseView'] || tab.configuration?.['alternateBaseView'] || '';
|
|
1783
1900
|
const parts = [
|
|
1784
1901
|
tab.applicationId,
|
|
1785
1902
|
tab.configuration?.resourceType || '',
|
|
1786
1903
|
tab.configuration?.driverClass || '',
|
|
1787
1904
|
tab.configuration?.Entity || '', // Include Entity name for Records resource type
|
|
1788
1905
|
effectiveRecordId,
|
|
1789
|
-
tab.configuration?.route || ''
|
|
1906
|
+
tab.configuration?.route || '',
|
|
1907
|
+
effectiveAbv
|
|
1790
1908
|
];
|
|
1791
1909
|
return parts.join('|');
|
|
1792
1910
|
}
|
|
@@ -1800,7 +1918,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1800
1918
|
tabId: tab.id,
|
|
1801
1919
|
appId: tab.applicationId,
|
|
1802
1920
|
appColor,
|
|
1803
|
-
title: tab
|
|
1921
|
+
title: this.resolveTabTitle(tab),
|
|
1804
1922
|
route: tab.configuration['route'] || '',
|
|
1805
1923
|
isPinned: tab.isPinned,
|
|
1806
1924
|
isLoaded: false,
|
|
@@ -1905,7 +2023,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
1905
2023
|
const driverClass = resourceData.Configuration?.resourceTypeDriverClass || resourceData.ResourceType;
|
|
1906
2024
|
// Discriminate distinct "new record" tabs of different entities (all have empty
|
|
1907
2025
|
// ResourceRecordID otherwise — would silently collide in the cache).
|
|
1908
|
-
const cacheDiscriminator = resourceData.Configuration
|
|
2026
|
+
const cacheDiscriminator = this.resolveCacheDiscriminator(resourceData.Configuration);
|
|
1909
2027
|
// Check if we have a cached component for this resource
|
|
1910
2028
|
const cached = this.cacheManager.getCachedComponent(driverClass, resourceData.ResourceRecordID || '', tab.applicationId, cacheDiscriminator);
|
|
1911
2029
|
if (cached) {
|
|
@@ -2400,6 +2518,8 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
2400
2518
|
// reload from being handed back the component it just detached.
|
|
2401
2519
|
const existingEntity = existingResourceData?.Configuration?.Entity;
|
|
2402
2520
|
const newEntity = tab.configuration['Entity'];
|
|
2521
|
+
const existingAbv = (existingResourceData?.Configuration?.['AlternateBaseView'] ?? existingResourceData?.Configuration?.['alternateBaseView']) || '';
|
|
2522
|
+
const newAbv = (tab.configuration['AlternateBaseView'] ?? tab.configuration['alternateBaseView']) || '';
|
|
2403
2523
|
// The record id comparison accepts the URL-segment form ResourceRecordSaved writes into
|
|
2404
2524
|
// the component's Data ('ID|<uuid>' for a tab that keeps '<uuid>'). An exact comparison
|
|
2405
2525
|
// reloads the same cached component on every configuration emission, and each reload
|
|
@@ -2408,6 +2528,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
2408
2528
|
existingResourceData?.Configuration?.applicationId !== tab.applicationId ||
|
|
2409
2529
|
!TabContainerComponent.IsSameRecordId(existingRecordId, newRecordId) ||
|
|
2410
2530
|
existingEntity !== newEntity ||
|
|
2531
|
+
existingAbv !== newAbv ||
|
|
2411
2532
|
(tab.configuration['resourceType'] === 'Custom' && existingDriverClass !== newDriverClass);
|
|
2412
2533
|
if (needsReload) {
|
|
2413
2534
|
// Clean up old component
|
|
@@ -2435,7 +2556,7 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
2435
2556
|
const app = this.appManager.GetAppById(tab.applicationId);
|
|
2436
2557
|
const styleUpdate = {
|
|
2437
2558
|
isPinned: tab.isPinned,
|
|
2438
|
-
title: tab
|
|
2559
|
+
title: this.resolveTabTitle(tab),
|
|
2439
2560
|
appColor: app?.GetColor() || DEFAULT_APP_COLOR
|
|
2440
2561
|
};
|
|
2441
2562
|
if (IsRecordsTabConfiguration(tab.configuration)) {
|
|
@@ -2919,6 +3040,26 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
2919
3040
|
// Unblock the shell's first-load gate so the loading overlay clears.
|
|
2920
3041
|
this.emitFirstLoadCompleteOnce();
|
|
2921
3042
|
}
|
|
3043
|
+
resolveCacheDiscriminator(configuration) {
|
|
3044
|
+
if (typeof ComponentCacheManager.GetCacheDiscriminator === 'function') {
|
|
3045
|
+
return ComponentCacheManager.GetCacheDiscriminator(configuration);
|
|
3046
|
+
}
|
|
3047
|
+
if (!configuration) {
|
|
3048
|
+
return undefined;
|
|
3049
|
+
}
|
|
3050
|
+
const entity = typeof configuration['Entity'] === 'string' && configuration['Entity'].trim().length > 0
|
|
3051
|
+
? configuration['Entity'].trim()
|
|
3052
|
+
: '';
|
|
3053
|
+
const rawAbv = configuration['AlternateBaseView'];
|
|
3054
|
+
const abv = typeof rawAbv === 'string' && rawAbv.trim().length > 0 ? rawAbv.trim() : '';
|
|
3055
|
+
if (!entity && !abv) {
|
|
3056
|
+
return undefined;
|
|
3057
|
+
}
|
|
3058
|
+
if (entity && abv) {
|
|
3059
|
+
return `${entity}|abv:${abv}`;
|
|
3060
|
+
}
|
|
3061
|
+
return entity || `abv:${abv}`;
|
|
3062
|
+
}
|
|
2922
3063
|
static ɵfac = function TabContainerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TabContainerComponent)(i0.ɵɵdirectiveInject(i1.GoldenLayoutManager), i0.ɵɵdirectiveInject(i1.WorkspaceStateManager), i0.ɵɵdirectiveInject(i1.ApplicationManager), i0.ɵɵdirectiveInject(i0.ApplicationRef), i0.ɵɵdirectiveInject(i0.EnvironmentInjector), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
2923
3064
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TabContainerComponent, selectors: [["mj-tab-container"]], viewQuery: function TabContainerComponent_Query(rf, ctx) { if (rf & 1) {
|
|
2924
3065
|
i0.ɵɵviewQuery(_c0, 5)(_c1, 5)(_c2, 5);
|
|
@@ -2971,5 +3112,5 @@ export class TabContainerComponent extends BaseAngularComponent {
|
|
|
2971
3112
|
type: HostListener,
|
|
2972
3113
|
args: ['window:resize']
|
|
2973
3114
|
}] }); })();
|
|
2974
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TabContainerComponent, { className: "TabContainerComponent", filePath: "src/lib/shell/components/tabs/tab-container.component.ts", lineNumber:
|
|
3115
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TabContainerComponent, { className: "TabContainerComponent", filePath: "src/lib/shell/components/tabs/tab-container.component.ts", lineNumber: 68 }); })();
|
|
2975
3116
|
//# sourceMappingURL=tab-container.component.js.map
|