@memberjunction/ng-explorer-core 5.21.0 → 5.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/lazy-feature-config.d.ts +19 -0
- package/dist/generated/lazy-feature-config.d.ts.map +1 -0
- package/dist/generated/lazy-feature-config.js +144 -0
- package/dist/generated/lazy-feature-config.js.map +1 -0
- package/dist/lib/command-palette/command-palette.component.d.ts +10 -1
- package/dist/lib/command-palette/command-palette.component.d.ts.map +1 -1
- package/dist/lib/command-palette/command-palette.component.js +68 -16
- package/dist/lib/command-palette/command-palette.component.js.map +1 -1
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +49 -49
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js.map +1 -1
- package/dist/lib/generic/form-toolbar.js +10 -10
- package/dist/lib/generic/form-toolbar.js.map +1 -1
- package/dist/lib/generic/resource-container-component.d.ts +0 -1
- package/dist/lib/generic/resource-container-component.d.ts.map +1 -1
- package/dist/lib/generic/resource-container-component.js +3 -12
- package/dist/lib/generic/resource-container-component.js.map +1 -1
- package/dist/lib/oauth/oauth-callback.component.js +6 -6
- package/dist/lib/oauth/oauth-callback.component.js.map +1 -1
- package/dist/lib/oauth/oauth.module.d.ts +2 -3
- package/dist/lib/oauth/oauth.module.d.ts.map +1 -1
- package/dist/lib/oauth/oauth.module.js +0 -4
- package/dist/lib/oauth/oauth.module.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts +8 -23
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js +68 -117
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +9 -21
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +66 -137
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +3 -19
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +16 -98
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +0 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +4 -12
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts +13 -11
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +80 -89
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/services/lazy-module-registry.d.ts +24 -9
- package/dist/lib/services/lazy-module-registry.d.ts.map +1 -1
- package/dist/lib/services/lazy-module-registry.js +32 -13
- package/dist/lib/services/lazy-module-registry.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +18 -3
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +38 -16
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.js +57 -35
- package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +56 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +298 -53
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/services/settings-dialog.service.d.ts +8 -8
- package/dist/lib/shell/services/settings-dialog.service.d.ts.map +1 -1
- package/dist/lib/shell/services/settings-dialog.service.js +20 -26
- package/dist/lib/shell/services/settings-dialog.service.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +26 -2
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +225 -54
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/shell/shell.module.d.ts +4 -5
- package/dist/lib/shell/shell.module.d.ts.map +1 -1
- package/dist/lib/shell/shell.module.js +4 -8
- package/dist/lib/shell/shell.module.js.map +1 -1
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js +72 -71
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js.map +1 -1
- package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js +11 -11
- package/dist/lib/single-dashboard/Components/delete-item/delete-item.component.js.map +1 -1
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts +36 -12
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js +78 -50
- package/dist/lib/single-dashboard/Components/edit-dashboard/edit-dashboard.component.js.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts +12 -5
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js +44 -55
- package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts +10 -2
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.js +313 -243
- package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
- package/dist/lib/user-menu/base-user-menu.d.ts +4 -0
- package/dist/lib/user-menu/base-user-menu.d.ts.map +1 -1
- package/dist/lib/user-menu/base-user-menu.js +26 -0
- package/dist/lib/user-menu/base-user-menu.js.map +1 -1
- package/dist/lib/user-menu/user-menu.types.d.ts +20 -0
- package/dist/lib/user-menu/user-menu.types.d.ts.map +1 -1
- package/dist/lib/user-menu/user-menu.types.js.map +1 -1
- package/dist/module.d.ts +23 -34
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +33 -74
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +1 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +38 -47
- package/dist/lib/services/lazy-feature-config.d.ts +0 -16
- package/dist/lib/services/lazy-feature-config.d.ts.map +0 -1
- package/dist/lib/services/lazy-feature-config.js +0 -113
- package/dist/lib/services/lazy-feature-config.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Component, ViewChild, createComponent, ViewEncapsulation, HostListener, Output, EventEmitter, inject } from '@angular/core';
|
|
2
2
|
import { MJGlobal } from '@memberjunction/global';
|
|
3
|
-
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
3
|
+
import { BaseResourceComponent, HomeAppPinService } from '@memberjunction/ng-shared';
|
|
4
4
|
import { ResourceData, ResourcePermissionEngine } from '@memberjunction/core-entities';
|
|
5
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
5
6
|
import { LogError, Metadata } from '@memberjunction/core';
|
|
6
7
|
import { ComponentCacheManager } from './component-cache-manager';
|
|
7
|
-
import { LazyModuleRegistry } from '../../../services/lazy-module-registry';
|
|
8
8
|
import * as i0 from "@angular/core";
|
|
9
9
|
import * as i1 from "@memberjunction/ng-base-application";
|
|
10
10
|
const _c0 = ["glContainer"];
|
|
@@ -15,6 +15,19 @@ function TabContainerComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
|
15
15
|
function TabContainerComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
16
16
|
i0.ɵɵelement(0, "div", 4, 1);
|
|
17
17
|
} }
|
|
18
|
+
function TabContainerComponent_Conditional_3_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
19
|
+
i0.ɵɵelementStart(0, "span");
|
|
20
|
+
i0.ɵɵtext(1, "Pinned to Home");
|
|
21
|
+
i0.ɵɵelementEnd();
|
|
22
|
+
i0.ɵɵelementStart(2, "span", 14);
|
|
23
|
+
i0.ɵɵelement(3, "i", 15);
|
|
24
|
+
i0.ɵɵelementEnd();
|
|
25
|
+
} }
|
|
26
|
+
function TabContainerComponent_Conditional_3_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
27
|
+
i0.ɵɵelementStart(0, "span");
|
|
28
|
+
i0.ɵɵtext(1, "Pin to Home");
|
|
29
|
+
i0.ɵɵelementEnd();
|
|
30
|
+
} }
|
|
18
31
|
function TabContainerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
19
32
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
20
33
|
i0.ɵɵelementStart(0, "div", 6)(1, "div", 7);
|
|
@@ -25,28 +38,38 @@ function TabContainerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
|
25
38
|
i0.ɵɵelementEnd()();
|
|
26
39
|
i0.ɵɵelement(5, "div", 9);
|
|
27
40
|
i0.ɵɵelementStart(6, "div", 7);
|
|
28
|
-
i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_6_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.
|
|
41
|
+
i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_6_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextPinToHome()); });
|
|
29
42
|
i0.ɵɵelement(7, "i", 10);
|
|
30
|
-
i0.ɵɵ
|
|
31
|
-
i0.ɵɵ
|
|
43
|
+
i0.ɵɵconditionalCreate(8, TabContainerComponent_Conditional_3_Conditional_8_Template, 4, 0)(9, TabContainerComponent_Conditional_3_Conditional_9_Template, 2, 0, "span");
|
|
44
|
+
i0.ɵɵelementEnd();
|
|
45
|
+
i0.ɵɵelement(10, "div", 9);
|
|
46
|
+
i0.ɵɵelementStart(11, "div", 7);
|
|
47
|
+
i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_11_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextClose()); });
|
|
48
|
+
i0.ɵɵelement(12, "i", 11);
|
|
49
|
+
i0.ɵɵelementStart(13, "span");
|
|
50
|
+
i0.ɵɵtext(14, "Close Tab");
|
|
32
51
|
i0.ɵɵelementEnd()();
|
|
33
|
-
i0.ɵɵelementStart(
|
|
34
|
-
i0.ɵɵlistener("click", function
|
|
35
|
-
i0.ɵɵelement(
|
|
36
|
-
i0.ɵɵelementStart(
|
|
37
|
-
i0.ɵɵtext(
|
|
52
|
+
i0.ɵɵelementStart(15, "div", 7);
|
|
53
|
+
i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_15_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseOthers()); });
|
|
54
|
+
i0.ɵɵelement(16, "i", 12);
|
|
55
|
+
i0.ɵɵelementStart(17, "span");
|
|
56
|
+
i0.ɵɵtext(18, "Close Others");
|
|
38
57
|
i0.ɵɵelementEnd()();
|
|
39
|
-
i0.ɵɵelementStart(
|
|
40
|
-
i0.ɵɵlistener("click", function
|
|
41
|
-
i0.ɵɵelement(
|
|
42
|
-
i0.ɵɵelementStart(
|
|
43
|
-
i0.ɵɵtext(
|
|
58
|
+
i0.ɵɵelementStart(19, "div", 7);
|
|
59
|
+
i0.ɵɵlistener("click", function TabContainerComponent_Conditional_3_Template_div_click_19_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseToRight()); });
|
|
60
|
+
i0.ɵɵelement(20, "i", 13);
|
|
61
|
+
i0.ɵɵelementStart(21, "span");
|
|
62
|
+
i0.ɵɵtext(22, "Close to Right");
|
|
44
63
|
i0.ɵɵelementEnd()()();
|
|
45
64
|
} if (rf & 2) {
|
|
46
65
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
47
66
|
i0.ɵɵstyleProp("left", ctx_r1.contextMenuX, "px")("top", ctx_r1.contextMenuY, "px");
|
|
48
67
|
i0.ɵɵadvance(4);
|
|
49
68
|
i0.ɵɵtextInterpolate(ctx_r1.isContextTabPinned ? "Unpin Tab" : "Pin Tab");
|
|
69
|
+
i0.ɵɵadvance(2);
|
|
70
|
+
i0.ɵɵclassProp("new-item", !ctx_r1.isContextTabPinnedToHome)("disabled", ctx_r1.isContextTabPinnedToHome);
|
|
71
|
+
i0.ɵɵadvance(2);
|
|
72
|
+
i0.ɵɵconditional(ctx_r1.isContextTabPinnedToHome ? 8 : 9);
|
|
50
73
|
} }
|
|
51
74
|
/**
|
|
52
75
|
* Container for Golden Layout tabs with app-colored styling.
|
|
@@ -78,7 +101,7 @@ export class TabContainerComponent {
|
|
|
78
101
|
* The shell can use this to show an error dialog and redirect.
|
|
79
102
|
*/
|
|
80
103
|
layoutInitError = new EventEmitter();
|
|
81
|
-
|
|
104
|
+
pinService = inject(HomeAppPinService);
|
|
82
105
|
subscriptions = [];
|
|
83
106
|
layoutInitRetryCount = 0;
|
|
84
107
|
MAX_LAYOUT_INIT_RETRIES = 5;
|
|
@@ -86,12 +109,18 @@ export class TabContainerComponent {
|
|
|
86
109
|
layoutRestorationComplete = false; // True only AFTER layout is fully restored/created
|
|
87
110
|
// Track component references for cleanup (legacy - keep for backward compat during transition)
|
|
88
111
|
componentRefs = new Map();
|
|
112
|
+
// Guard against concurrent loadTabContent calls for the same tab.
|
|
113
|
+
// When a tab's content changes while active, both the reload path (workspace config subscription)
|
|
114
|
+
// and onTabShown can race to call loadTabContent, resulting in duplicate component rendering.
|
|
115
|
+
tabsCurrentlyLoading = new Set();
|
|
89
116
|
// NEW: Smart component cache for preserving state across tab switches
|
|
90
117
|
cacheManager;
|
|
91
118
|
// Single-resource mode: render component directly without Golden Layout
|
|
92
119
|
// This avoids the 20px height issue when GL header is hidden
|
|
93
120
|
useSingleResourceMode = false;
|
|
94
121
|
singleResourceComponentRef = null;
|
|
122
|
+
/** Cache identity of the current single-resource component for detachment */
|
|
123
|
+
singleResourceCacheIdentity = null;
|
|
95
124
|
previousTabBarVisible = null;
|
|
96
125
|
currentSingleResourceSignature = null; // Track loaded content signature to avoid unnecessary reloads
|
|
97
126
|
isCreatingInitialTabs = false; // Flag to prevent syncTabsWithConfiguration during initial tab creation
|
|
@@ -139,8 +168,17 @@ export class TabContainerComponent {
|
|
|
139
168
|
if (activeTab) {
|
|
140
169
|
const signature = this.getTabContentSignature(activeTab);
|
|
141
170
|
if (signature !== this.currentSingleResourceSignature) {
|
|
171
|
+
// DO NOT call saveCurrentComponentQueryParams() here — by the time this
|
|
172
|
+
// subscription fires, OpenTab has already replaced the tab config with the
|
|
173
|
+
// new nav item's config, so queryParams are gone. The cache entry already
|
|
174
|
+
// has the correct queryParams from the most recent unchanged-signature save.
|
|
142
175
|
this.loadSingleResourceContent();
|
|
143
176
|
}
|
|
177
|
+
else {
|
|
178
|
+
// Signature unchanged — sync queryParams to cache entry so it stays current.
|
|
179
|
+
// This catches incremental queryParam updates (e.g., user selects a conversation).
|
|
180
|
+
this.saveCurrentComponentQueryParams();
|
|
181
|
+
}
|
|
144
182
|
}
|
|
145
183
|
}
|
|
146
184
|
else if (this.layoutRestorationComplete && !this.isCreatingInitialTabs) {
|
|
@@ -304,7 +342,9 @@ export class TabContainerComponent {
|
|
|
304
342
|
const shouldUseSingleResourceMode = !tabBarVisible;
|
|
305
343
|
if (shouldUseSingleResourceMode !== this.useSingleResourceMode) {
|
|
306
344
|
this.useSingleResourceMode = shouldUseSingleResourceMode;
|
|
307
|
-
|
|
345
|
+
// Defer detectChanges to next microtask to avoid ExpressionChangedAfterItHasBeenCheckedError
|
|
346
|
+
// when this handler fires during an already-running change detection cycle.
|
|
347
|
+
Promise.resolve().then(() => this.cdr.detectChanges());
|
|
308
348
|
if (this.useSingleResourceMode) {
|
|
309
349
|
// Transitioning to single-resource mode
|
|
310
350
|
// **CRITICAL FIX**: Wait for the template to render directContentContainer
|
|
@@ -403,23 +443,30 @@ export class TabContainerComponent {
|
|
|
403
443
|
if (cached) {
|
|
404
444
|
// Clean up previous single-resource component (if different)
|
|
405
445
|
this.cleanupSingleResourceComponent();
|
|
406
|
-
//
|
|
407
|
-
|
|
446
|
+
// Mark cached component as attached to this tab (it was detached / available for reuse).
|
|
447
|
+
// IMPORTANT: We use markAsAttached here, NOT markAsDetached — the component is being
|
|
448
|
+
// reattached to the DOM and should NOT be eligible for LRU eviction.
|
|
449
|
+
this.cacheManager.markAsAttached(driverClass, resourceData.ResourceRecordID || '', activeTab.applicationId, activeTab.id);
|
|
408
450
|
// Reattach the cached wrapper element to single-resource container
|
|
409
451
|
cached.wrapperElement.style.height = "100%"; // Ensure full height
|
|
410
452
|
container.appendChild(cached.wrapperElement);
|
|
411
|
-
// Store reference for cleanup
|
|
453
|
+
// Store reference and identity for cleanup/detachment
|
|
412
454
|
this.singleResourceComponentRef = cached.componentRef;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
455
|
+
this.singleResourceCacheIdentity = { driverClass, recordId: resourceData.ResourceRecordID || '', appId: activeTab.applicationId, tabId: activeTab.id };
|
|
456
|
+
// Restore saved queryParams to the tab config so the URL reflects
|
|
457
|
+
// the component's preserved state (e.g., selected conversation, collection drill-down).
|
|
458
|
+
if (cached.savedQueryParams) {
|
|
459
|
+
this.workspaceManager.UpdateTabConfiguration(activeTab.id, {
|
|
460
|
+
queryParams: cached.savedQueryParams
|
|
461
|
+
});
|
|
462
|
+
// Do NOT clear savedQueryParams here — the else branch (unchanged-signature saves)
|
|
463
|
+
// will keep it current while the component is active. Clearing it would cause the
|
|
464
|
+
// queryParams to be lost on the next detach/reattach cycle.
|
|
421
465
|
}
|
|
466
|
+
return;
|
|
422
467
|
}
|
|
468
|
+
// Get the component registration (with lazy loading fallback via ClassFactory)
|
|
469
|
+
const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
|
|
423
470
|
if (!resourceReg) {
|
|
424
471
|
LogError(`Unable to find resource registration for driver class: ${driverClass}`);
|
|
425
472
|
return;
|
|
@@ -439,6 +486,18 @@ export class TabContainerComponent {
|
|
|
439
486
|
instance.LoadCompleteEvent = () => {
|
|
440
487
|
this.emitFirstLoadCompleteOnce();
|
|
441
488
|
};
|
|
489
|
+
// Wire up display name change for single-resource mode.
|
|
490
|
+
// Guard: only update the title if THIS component is the currently displayed one.
|
|
491
|
+
// Without this guard, cached components (detached but alive) can fire this callback
|
|
492
|
+
// and overwrite the active tab's title with a stale name.
|
|
493
|
+
instance.DisplayNameChangedEvent = (newName) => {
|
|
494
|
+
if (this.singleResourceComponentRef?.instance === instance) {
|
|
495
|
+
const tabId = this.workspaceManager.GetActiveTabId();
|
|
496
|
+
if (tabId) {
|
|
497
|
+
this.workspaceManager.UpdateTabTitle(tabId, newName);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
442
501
|
// Get the native element and append to container
|
|
443
502
|
const nativeElement = componentRef.hostView.rootNodes[0];
|
|
444
503
|
container.appendChild(nativeElement);
|
|
@@ -446,22 +505,76 @@ export class TabContainerComponent {
|
|
|
446
505
|
if (container.children?.length > 0) {
|
|
447
506
|
container.children[0].style.height = "100%";
|
|
448
507
|
}
|
|
449
|
-
//
|
|
508
|
+
// Cache the component for reuse when switching between nav items within the same app.
|
|
509
|
+
// Without this, every nav switch creates a brand new component from scratch.
|
|
510
|
+
const wrapperElement = nativeElement;
|
|
511
|
+
this.cacheManager.cacheComponent(componentRef, wrapperElement, resourceData, activeTab.id);
|
|
512
|
+
// Store reference and identity for cleanup/detachment
|
|
450
513
|
this.singleResourceComponentRef = componentRef;
|
|
514
|
+
this.singleResourceCacheIdentity = { driverClass, recordId: resourceData.ResourceRecordID || '', appId: activeTab.applicationId, tabId: activeTab.id };
|
|
451
515
|
}
|
|
452
516
|
/**
|
|
453
517
|
* Clean up single-resource mode component
|
|
454
518
|
*/
|
|
519
|
+
/**
|
|
520
|
+
* Detaches the current single-resource component from the DOM and marks it as
|
|
521
|
+
* available for reuse in the component cache.
|
|
522
|
+
*
|
|
523
|
+
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
524
|
+
* ║ ⚠️ DO NOT DESTROY THE COMPONENT HERE — INTENTIONAL DESIGN CHOICE ⚠️ ║
|
|
525
|
+
* ║ ║
|
|
526
|
+
* ║ The component is DETACHED from the DOM, NOT destroyed. It stays alive ║
|
|
527
|
+
* ║ in the ComponentCacheManager with its full Angular state preserved ║
|
|
528
|
+
* ║ (properties, subscriptions, loaded data, scroll position, etc). ║
|
|
529
|
+
* ║ ║
|
|
530
|
+
* ║ When the user returns to this tab, the cached component is reattached ║
|
|
531
|
+
* ║ instantly — no data reload, no API calls, no flash of empty content. ║
|
|
532
|
+
* ║ ║
|
|
533
|
+
* ║ Destroying components here "for memory optimization" is a net ║
|
|
534
|
+
* ║ NEGATIVE: the reload on return is far more expensive (DB queries, ║
|
|
535
|
+
* ║ API calls, re-rendering) than keeping the component in memory. ║
|
|
536
|
+
* ║ The LRU eviction in ComponentCacheManager handles memory limits — ║
|
|
537
|
+
* ║ when MaxDetachedComponents is exceeded, the LEAST recently used ║
|
|
538
|
+
* ║ components are evicted automatically. ║
|
|
539
|
+
* ║ ║
|
|
540
|
+
* ║ If you think memory is a problem, adjust MaxDetachedComponents ║
|
|
541
|
+
* ║ instead of destroying components here. ║
|
|
542
|
+
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
543
|
+
*/
|
|
544
|
+
/**
|
|
545
|
+
* Save the currently displayed component's queryParams to its cache entry.
|
|
546
|
+
* Called on every config change so the cache entry always has the latest queryParams,
|
|
547
|
+
* even after the tab config is overwritten by a new nav item.
|
|
548
|
+
*/
|
|
549
|
+
saveCurrentComponentQueryParams() {
|
|
550
|
+
if (!this.singleResourceCacheIdentity)
|
|
551
|
+
return;
|
|
552
|
+
const { tabId } = this.singleResourceCacheIdentity;
|
|
553
|
+
const tab = this.workspaceManager.GetTab(tabId);
|
|
554
|
+
const qp = tab?.configuration?.['queryParams'];
|
|
555
|
+
const cached = this.cacheManager.getComponentByTabId(tabId);
|
|
556
|
+
if (cached) {
|
|
557
|
+
cached.savedQueryParams = (qp && Object.keys(qp).length > 0) ? { ...qp } : undefined;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
455
560
|
cleanupSingleResourceComponent() {
|
|
456
561
|
if (this.singleResourceComponentRef) {
|
|
457
|
-
|
|
458
|
-
|
|
562
|
+
if (this.singleResourceCacheIdentity) {
|
|
563
|
+
const { driverClass, recordId, appId } = this.singleResourceCacheIdentity;
|
|
564
|
+
// Mark as DETACHED by resource identity — the ONE consistent key used everywhere.
|
|
565
|
+
this.cacheManager.markAsDetached(driverClass, recordId, appId);
|
|
566
|
+
}
|
|
459
567
|
this.singleResourceComponentRef = null;
|
|
568
|
+
this.singleResourceCacheIdentity = null;
|
|
460
569
|
}
|
|
461
|
-
//
|
|
570
|
+
// Remove children from the container. This detaches the wrapper DOM element
|
|
571
|
+
// without destroying the Angular component — it lives on in the cache.
|
|
572
|
+
// Using removeChild (not innerHTML='') to avoid aggressive DOM cleanup.
|
|
462
573
|
const container = this.directContentContainer?.nativeElement;
|
|
463
574
|
if (container) {
|
|
464
|
-
container.
|
|
575
|
+
while (container.firstChild) {
|
|
576
|
+
container.removeChild(container.firstChild);
|
|
577
|
+
}
|
|
465
578
|
}
|
|
466
579
|
}
|
|
467
580
|
/**
|
|
@@ -517,6 +630,13 @@ export class TabContainerComponent {
|
|
|
517
630
|
* Uses component cache to reuse components for same resources
|
|
518
631
|
*/
|
|
519
632
|
async loadTabContent(tabId, container) {
|
|
633
|
+
// Per-tab guard: prevent concurrent loads of the same tab content.
|
|
634
|
+
// This can happen when a tab's content changes while active — both the workspace
|
|
635
|
+
// config subscription reload path and onTabShown can race to call this method.
|
|
636
|
+
if (this.tabsCurrentlyLoading.has(tabId)) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
this.tabsCurrentlyLoading.add(tabId);
|
|
520
640
|
try {
|
|
521
641
|
const tab = this.workspaceManager.GetTab(tabId);
|
|
522
642
|
if (!tab) {
|
|
@@ -555,14 +675,8 @@ export class TabContainerComponent {
|
|
|
555
675
|
}
|
|
556
676
|
return;
|
|
557
677
|
}
|
|
558
|
-
// Get the component registration using the driver class (with lazy loading fallback)
|
|
559
|
-
|
|
560
|
-
if (!resourceReg) {
|
|
561
|
-
const loaded = await this.lazyRegistry.Load(driverClass);
|
|
562
|
-
if (loaded) {
|
|
563
|
-
resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
678
|
+
// Get the component registration using the driver class (with lazy loading fallback via ClassFactory)
|
|
679
|
+
const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
|
|
566
680
|
if (!resourceReg) {
|
|
567
681
|
LogError(`Unable to find resource registration for driver class: ${driverClass}`);
|
|
568
682
|
return;
|
|
@@ -588,6 +702,11 @@ export class TabContainerComponent {
|
|
|
588
702
|
// TODO: Implement UpdateTabTitle in WorkspaceStateManager
|
|
589
703
|
}
|
|
590
704
|
};
|
|
705
|
+
// Wire up display name change notifications
|
|
706
|
+
instance.DisplayNameChangedEvent = (newName) => {
|
|
707
|
+
this.layoutManager.UpdateTabStyle(tabId, { title: newName });
|
|
708
|
+
this.workspaceManager.UpdateTabTitle(tabId, newName);
|
|
709
|
+
};
|
|
591
710
|
// Create a container div for the component
|
|
592
711
|
const componentElement = document.createElement('div');
|
|
593
712
|
componentElement.className = 'tab-content-wrapper';
|
|
@@ -605,6 +724,9 @@ export class TabContainerComponent {
|
|
|
605
724
|
catch (e) {
|
|
606
725
|
LogError(e);
|
|
607
726
|
}
|
|
727
|
+
finally {
|
|
728
|
+
this.tabsCurrentlyLoading.delete(tabId);
|
|
729
|
+
}
|
|
608
730
|
}
|
|
609
731
|
/**
|
|
610
732
|
* Update tab display name in background without loading full component
|
|
@@ -624,13 +746,7 @@ export class TabContainerComponent {
|
|
|
624
746
|
}
|
|
625
747
|
// Get the resource registration to access GetResourceDisplayName without loading full component
|
|
626
748
|
const driverClass = resourceData.Configuration?.resourceTypeDriverClass || resourceData.ResourceType;
|
|
627
|
-
|
|
628
|
-
if (!resourceReg) {
|
|
629
|
-
const loaded = await this.lazyRegistry.Load(driverClass);
|
|
630
|
-
if (loaded) {
|
|
631
|
-
resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
749
|
+
const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
|
|
634
750
|
if (!resourceReg) {
|
|
635
751
|
return;
|
|
636
752
|
}
|
|
@@ -809,7 +925,7 @@ export class TabContainerComponent {
|
|
|
809
925
|
*/
|
|
810
926
|
cleanupTabComponent(tabId) {
|
|
811
927
|
// First, try to detach from cache (preserves component for reuse)
|
|
812
|
-
const cachedInfo = this.cacheManager.
|
|
928
|
+
const cachedInfo = this.cacheManager.findAndDetachByTabId(tabId);
|
|
813
929
|
if (cachedInfo) {
|
|
814
930
|
// Remove from legacy componentRefs but keep in cache
|
|
815
931
|
this.componentRefs.delete(tabId);
|
|
@@ -973,6 +1089,135 @@ export class TabContainerComponent {
|
|
|
973
1089
|
}
|
|
974
1090
|
this.hideContextMenu();
|
|
975
1091
|
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Check if context menu tab is pinned to Home dashboard
|
|
1094
|
+
*/
|
|
1095
|
+
get isContextTabPinnedToHome() {
|
|
1096
|
+
if (!this.contextMenuTabId)
|
|
1097
|
+
return false;
|
|
1098
|
+
const tab = this.workspaceManager.GetTab(this.contextMenuTabId);
|
|
1099
|
+
if (!tab)
|
|
1100
|
+
return false;
|
|
1101
|
+
const resourceType = this.resolveResourceType(tab);
|
|
1102
|
+
return this.pinService.IsPinned(resourceType, tab.configuration);
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Pin current context menu tab to Home dashboard
|
|
1106
|
+
*/
|
|
1107
|
+
async onContextPinToHome() {
|
|
1108
|
+
if (this.isContextTabPinnedToHome) {
|
|
1109
|
+
this.hideContextMenu();
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (!this.contextMenuTabId) {
|
|
1113
|
+
this.hideContextMenu();
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const tab = this.workspaceManager.GetTab(this.contextMenuTabId);
|
|
1117
|
+
if (!tab) {
|
|
1118
|
+
this.hideContextMenu();
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const resourceType = this.resolveResourceType(tab);
|
|
1122
|
+
const activeApp = this.appManager.GetActiveApp();
|
|
1123
|
+
// Resolve nav item icon for Custom pins
|
|
1124
|
+
let pinIcon;
|
|
1125
|
+
if (resourceType === 'Custom' && activeApp) {
|
|
1126
|
+
const navItemName = tab.configuration?.['navItemName'];
|
|
1127
|
+
if (navItemName) {
|
|
1128
|
+
const navItems = await activeApp.GetNavItems();
|
|
1129
|
+
const navItem = navItems.find(ni => ni.Label === navItemName);
|
|
1130
|
+
pinIcon = navItem?.Icon || undefined;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const added = this.pinService.AddPin({
|
|
1134
|
+
DisplayName: tab.title || 'Untitled',
|
|
1135
|
+
ResourceType: resourceType,
|
|
1136
|
+
ApplicationID: tab.applicationId || activeApp?.ID,
|
|
1137
|
+
ApplicationName: activeApp?.Name,
|
|
1138
|
+
Icon: pinIcon,
|
|
1139
|
+
Color: activeApp?.GetColor() || undefined,
|
|
1140
|
+
Configuration: tab.configuration,
|
|
1141
|
+
});
|
|
1142
|
+
if (added) {
|
|
1143
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Pinned "${tab.title}" to Home`, 'success', 2000);
|
|
1144
|
+
this.captureContextTabThumbnail(tab);
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
MJNotificationService.Instance.CreateSimpleNotification(`"${tab.title}" is already pinned to Home`, 'info', 3000);
|
|
1148
|
+
}
|
|
1149
|
+
this.hideContextMenu();
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Resolve a WorkspaceTab's resource type string for pin matching
|
|
1153
|
+
*/
|
|
1154
|
+
resolveResourceType(tab) {
|
|
1155
|
+
const config = tab.configuration;
|
|
1156
|
+
const rt = config.resourceType || '';
|
|
1157
|
+
if (rt === 'Dashboards' || config['dashboardId'])
|
|
1158
|
+
return 'Dashboards';
|
|
1159
|
+
if (rt === 'User Views' || rt === 'MJ: User Views' || config['viewId'])
|
|
1160
|
+
return 'User Views';
|
|
1161
|
+
if (rt === 'Queries' || config['queryId'])
|
|
1162
|
+
return 'Queries';
|
|
1163
|
+
if (rt === 'Reports' || config['reportId'])
|
|
1164
|
+
return 'Reports';
|
|
1165
|
+
if (rt === 'Records' || (config['entity'] && config['recordId']))
|
|
1166
|
+
return 'Records';
|
|
1167
|
+
if (rt === 'Custom' || config['navItemName'])
|
|
1168
|
+
return 'Custom';
|
|
1169
|
+
return rt || 'Custom';
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Capture thumbnail for a just-pinned tab (async, non-blocking)
|
|
1173
|
+
*/
|
|
1174
|
+
async captureContextTabThumbnail(tab) {
|
|
1175
|
+
try {
|
|
1176
|
+
// Find the active content element — differs by mode
|
|
1177
|
+
let contentEl = null;
|
|
1178
|
+
if (this.useSingleResourceMode) {
|
|
1179
|
+
contentEl = this.directContentContainer?.nativeElement ?? null;
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
// In Golden Layout mode, find the active tab's content pane
|
|
1183
|
+
contentEl = this.glContainer?.nativeElement?.querySelector('.lm_item_container .lm_content');
|
|
1184
|
+
}
|
|
1185
|
+
if (!contentEl)
|
|
1186
|
+
return;
|
|
1187
|
+
const thumbnail = await this.pinService.CaptureThumbnail(contentEl);
|
|
1188
|
+
if (thumbnail) {
|
|
1189
|
+
const resourceType = this.resolveResourceType(tab);
|
|
1190
|
+
const pin = this.pinService.FindPin(resourceType, tab.configuration);
|
|
1191
|
+
if (pin) {
|
|
1192
|
+
this.pinService.UpdatePin(pin.Id, { Thumbnail: thumbnail });
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
catch {
|
|
1197
|
+
// Thumbnail capture is best-effort
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Public method for external callers (e.g. shell) to capture a thumbnail
|
|
1202
|
+
* of the currently visible content, regardless of mode.
|
|
1203
|
+
*/
|
|
1204
|
+
async CaptureActiveThumbnail() {
|
|
1205
|
+
try {
|
|
1206
|
+
let contentEl = null;
|
|
1207
|
+
if (this.useSingleResourceMode) {
|
|
1208
|
+
contentEl = this.directContentContainer?.nativeElement ?? null;
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
contentEl = this.glContainer?.nativeElement?.querySelector('.lm_item_container .lm_content');
|
|
1212
|
+
}
|
|
1213
|
+
if (!contentEl)
|
|
1214
|
+
return undefined;
|
|
1215
|
+
return await this.pinService.CaptureThumbnail(contentEl);
|
|
1216
|
+
}
|
|
1217
|
+
catch {
|
|
1218
|
+
return undefined;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
976
1221
|
/**
|
|
977
1222
|
* While the naming implies this is only invoked once, components we DO NOT CONTROL might have race
|
|
978
1223
|
* conditions that result in unpredictable behavior. To avoid those causing loading screen overaly to show
|
|
@@ -990,21 +1235,21 @@ export class TabContainerComponent {
|
|
|
990
1235
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.directContentContainer = _t.first);
|
|
991
1236
|
} }, hostBindings: function TabContainerComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
992
1237
|
i0.ɵɵlistener("resize", function TabContainerComponent_resize_HostBindingHandler() { return ctx.onWindowResize(); }, i0.ɵɵresolveWindow);
|
|
993
|
-
} }, outputs: { firstResourceLoadComplete: "firstResourceLoadComplete", layoutInitError: "layoutInitError" }, standalone: false, decls: 4, vars: 2, consts: [["directContentContainer", ""], ["glContainer", ""], [1, "tab-container"], [1, "direct-content-container"], [1, "gl-container"], [1, "context-menu", 3, "left", "top"], [1, "context-menu"], [1, "context-menu-item", 3, "click"], [1, "fa-solid", "fa-thumbtack"], [1, "context-menu-divider"], [1, "fa-solid", "fa-xmark"], [1, "fa-solid", "fa-layer-group"], [1, "fa-solid", "fa-angles-right"]], template: function TabContainerComponent_Template(rf, ctx) { if (rf & 1) {
|
|
1238
|
+
} }, outputs: { firstResourceLoadComplete: "firstResourceLoadComplete", layoutInitError: "layoutInitError" }, standalone: false, decls: 4, vars: 2, consts: [["directContentContainer", ""], ["glContainer", ""], [1, "tab-container"], [1, "direct-content-container"], [1, "gl-container"], [1, "context-menu", 3, "left", "top"], [1, "context-menu"], [1, "context-menu-item", 3, "click"], [1, "fa-solid", "fa-thumbtack"], [1, "context-menu-divider"], [1, "fa-solid", "fa-house-chimney"], [1, "fa-solid", "fa-xmark"], [1, "fa-solid", "fa-layer-group"], [1, "fa-solid", "fa-angles-right"], [1, "check"], [1, "fa-solid", "fa-check"]], template: function TabContainerComponent_Template(rf, ctx) { if (rf & 1) {
|
|
994
1239
|
i0.ɵɵelementStart(0, "div", 2);
|
|
995
1240
|
i0.ɵɵconditionalCreate(1, TabContainerComponent_Conditional_1_Template, 2, 0, "div", 3)(2, TabContainerComponent_Conditional_2_Template, 2, 0, "div", 4);
|
|
996
|
-
i0.ɵɵconditionalCreate(3, TabContainerComponent_Conditional_3_Template,
|
|
1241
|
+
i0.ɵɵconditionalCreate(3, TabContainerComponent_Conditional_3_Template, 23, 10, "div", 5);
|
|
997
1242
|
i0.ɵɵelementEnd();
|
|
998
1243
|
} if (rf & 2) {
|
|
999
1244
|
i0.ɵɵadvance();
|
|
1000
1245
|
i0.ɵɵconditional(ctx.useSingleResourceMode ? 1 : 2);
|
|
1001
1246
|
i0.ɵɵadvance(2);
|
|
1002
1247
|
i0.ɵɵconditional(ctx.contextMenuVisible ? 3 : -1);
|
|
1003
|
-
} }, styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"], encapsulation: 2 });
|
|
1248
|
+
} }, styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Context menu item variants */\n.context-menu-item.new-item i { color: var(--mj-brand-primary); }\n.context-menu-item.new-item { font-weight: 500; }\n.context-menu-item.disabled { color: var(--mj-text-disabled); cursor: default; }\n.context-menu-item.disabled:hover { background: transparent; }\n.context-menu-item.disabled i { color: var(--mj-text-disabled); }\n.context-menu-item .check { margin-left: auto; color: var(--mj-status-success); font-size: 12px; }\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"], encapsulation: 2 });
|
|
1004
1249
|
}
|
|
1005
1250
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TabContainerComponent, [{
|
|
1006
1251
|
type: Component,
|
|
1007
|
-
args: [{ standalone: false, selector: 'mj-tab-container', encapsulation: ViewEncapsulation.None, template: "<div class=\"tab-container\">\n\n <!-- Single-Resource Mode: Direct component rendering without Golden Layout -->\n <!-- This avoids the 20px height issue when GL header is hidden -->\n @if (useSingleResourceMode) {\n <div #directContentContainer class=\"direct-content-container\"></div>\n } @else {\n <!-- Multi-Tab Mode: Golden Layout Container -->\n <div #glContainer class=\"gl-container\"></div>\n }\n\n <!-- Context Menu (only relevant in multi-tab mode) -->\n @if (contextMenuVisible) {\n <div\n class=\"context-menu\"\n [style.left.px]=\"contextMenuX\"\n [style.top.px]=\"contextMenuY\">\n <div class=\"context-menu-item\" (click)=\"onContextPin()\">\n <i class=\"fa-solid fa-thumbtack\"></i>\n <span>{{ isContextTabPinned ? 'Unpin Tab' : 'Pin Tab' }}</span>\n </div>\n <div class=\"context-menu-divider\"></div>\n <div class=\"context-menu-item\" (click)=\"onContextClose()\">\n <i class=\"fa-solid fa-xmark\"></i>\n <span>Close Tab</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseOthers()\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>Close Others</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseToRight()\">\n <i class=\"fa-solid fa-angles-right\"></i>\n <span>Close to Right</span>\n </div>\n </div>\n }\n</div>\n", styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"] }]
|
|
1252
|
+
args: [{ standalone: false, selector: 'mj-tab-container', encapsulation: ViewEncapsulation.None, template: "<div class=\"tab-container\">\n\n <!-- Single-Resource Mode: Direct component rendering without Golden Layout -->\n <!-- This avoids the 20px height issue when GL header is hidden -->\n @if (useSingleResourceMode) {\n <div #directContentContainer class=\"direct-content-container\"></div>\n } @else {\n <!-- Multi-Tab Mode: Golden Layout Container -->\n <div #glContainer class=\"gl-container\"></div>\n }\n\n <!-- Context Menu (only relevant in multi-tab mode) -->\n @if (contextMenuVisible) {\n <div\n class=\"context-menu\"\n [style.left.px]=\"contextMenuX\"\n [style.top.px]=\"contextMenuY\">\n <div class=\"context-menu-item\" (click)=\"onContextPin()\">\n <i class=\"fa-solid fa-thumbtack\"></i>\n <span>{{ isContextTabPinned ? 'Unpin Tab' : 'Pin Tab' }}</span>\n </div>\n <div class=\"context-menu-divider\"></div>\n <div class=\"context-menu-item\" [class.new-item]=\"!isContextTabPinnedToHome\" [class.disabled]=\"isContextTabPinnedToHome\" (click)=\"onContextPinToHome()\">\n <i class=\"fa-solid fa-house-chimney\"></i>\n @if (isContextTabPinnedToHome) {\n <span>Pinned to Home</span>\n <span class=\"check\"><i class=\"fa-solid fa-check\"></i></span>\n } @else {\n <span>Pin to Home</span>\n }\n </div>\n <div class=\"context-menu-divider\"></div>\n <div class=\"context-menu-item\" (click)=\"onContextClose()\">\n <i class=\"fa-solid fa-xmark\"></i>\n <span>Close Tab</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseOthers()\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>Close Others</span>\n </div>\n <div class=\"context-menu-item\" (click)=\"onContextCloseToRight()\">\n <i class=\"fa-solid fa-angles-right\"></i>\n <span>Close to Right</span>\n </div>\n </div>\n }\n</div>\n", styles: [":host {\n display: flex;\n flex: 1;\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.tab-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n width: 100%;\n overflow: hidden;\n}\n\n.gl-container {\n flex: 1;\n width: 100%;\n height: 100%;\n position: relative;\n background: var(--mj-bg-page);\n}\n\n/* Direct content container for single-resource mode */\n/* Renders components directly without Golden Layout overhead */\n.direct-content-container {\n flex: 1;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-page);\n overflow: hidden;\n}\n\n.tab-content-container {\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n min-width: 150px;\n z-index: 10001;\n overflow: hidden;\n}\n\n.context-menu .context-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: var(--mj-text-secondary);\n}\n\n.context-menu .context-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Context menu item variants */\n.context-menu-item.new-item i { color: var(--mj-brand-primary); }\n.context-menu-item.new-item { font-weight: 500; }\n.context-menu-item.disabled { color: var(--mj-text-disabled); cursor: default; }\n.context-menu-item.disabled:hover { background: transparent; }\n.context-menu-item.disabled i { color: var(--mj-text-disabled); }\n.context-menu-item .check { margin-left: auto; color: var(--mj-status-success); font-size: 12px; }\n\n/* Override Golden Layout styles */\n/* Global overrides */\n\n/* Ensure GL root container fills available space */\nmj-tab-container .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-tab-container .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-tab-container .lm_item,\nmj-tab-container .lm_content,\nmj-tab-container .lm_stack,\nmj-tab-container .lm_row,\nmj-tab-container .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-tab-container .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\n/* This is separate from .lm_item - it holds the actual tab content */\nmj-tab-container .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-tab-container .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the anonymous ComponentItem div inside lm_items (has no class) */\n/* Created in golden-layout/src/ts/items/component-item.ts:51 without a class */\n/* NOTE: Do NOT set display here - GL uses display:none to hide inactive tabs */\nmj-tab-container .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-tab-container .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout - GL uses float:left for horizontal panes */\nmj-tab-container .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds (GL sets pixel widths inline) */\nmj-tab-container .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_content {\n background: var(--mj-bg-page) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-tab-container .lm_item_container {\n background: var(--mj-bg-page) !important;\n}\n\n/* Tab content wrapper - allow scrolling */\n.tab-content-wrapper {\n overflow: auto !important;\n}\n\n/* Make tabs larger and easier to click */\nmj-tab-container .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-sunken) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-tab-container .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls */\nmj-tab-container .lm_controls {\n display: none !important;\n}\n\nmj-tab-container .lm_header .lm_tab {\n padding: 0 16px !important;\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\n/* App color accent - left edge indicator */\nmj-tab-container .lm_header .lm_tab::before {\n content: '' !important;\n position: absolute !important;\n left: 0 !important;\n top: 4px !important;\n bottom: 4px !important;\n width: 3px !important;\n border-radius: 0 2px 2px 0 !important;\n background-color: var(--app-color, transparent) !important;\n opacity: 0.6 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-hover) !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-tab-container .lm_header .lm_tab:hover::before {\n opacity: 0.8 !important;\n}\n\nmj-tab-container .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\n/* Enhanced app color accent for active tab */\nmj-tab-container .lm_header .lm_tab.lm_active::before {\n opacity: 1 !important;\n width: 3px !important;\n top: 2px !important;\n bottom: 2px !important;\n box-shadow: 0 0 6px var(--app-color, transparent) !important;\n}\n\nmj-tab-container .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\nmj-tab-container .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n flex-shrink: 0 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n font-size: 10px !important;\n color: var(--mj-text-secondary) !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Show close button on hover (except pinned) */\nmj-tab-container .lm_header .lm_tab:hover:not(.pinned) .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Pinned tabs never show close button */\nmj-tab-container .lm_header .lm_tab.pinned .lm_close_tab {\n display: none !important;\n}\n\n/* Hide close button on active tab by default */\nmj-tab-container .lm_active .lm_close_tab {\n opacity: 0 !important;\n}\n\n/* Show close button when hovering active tab */\nmj-tab-container .lm_active:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\n/* Adjust padding for close button */\nmj-tab-container .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n"] }]
|
|
1008
1253
|
}], () => [{ type: i1.GoldenLayoutManager }, { type: i1.WorkspaceStateManager }, { type: i1.ApplicationManager }, { type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }, { type: i0.ChangeDetectorRef }], { glContainer: [{
|
|
1009
1254
|
type: ViewChild,
|
|
1010
1255
|
args: ['glContainer', { static: false }]
|