@memberjunction/ng-explorer-core 5.21.0 → 5.22.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 +143 -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/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/resource-wrappers/chat-collections-resource.component.d.ts +4 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js +46 -1
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +4 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +21 -0
- package/dist/lib/resource-wrappers/chat-conversations-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/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/tabs/tab-container.component.d.ts +23 -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 +205 -43
- package/dist/lib/shell/components/tabs/tab-container.component.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 +211 -41
- package/dist/lib/shell/shell.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/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 +35 -34
- 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,6 +109,10 @@ 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
|
|
@@ -412,14 +439,8 @@ export class TabContainerComponent {
|
|
|
412
439
|
this.singleResourceComponentRef = cached.componentRef;
|
|
413
440
|
return;
|
|
414
441
|
}
|
|
415
|
-
// Get the component registration (with lazy loading fallback)
|
|
416
|
-
|
|
417
|
-
if (!resourceReg) {
|
|
418
|
-
const loaded = await this.lazyRegistry.Load(driverClass);
|
|
419
|
-
if (loaded) {
|
|
420
|
-
resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
442
|
+
// Get the component registration (with lazy loading fallback via ClassFactory)
|
|
443
|
+
const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
|
|
423
444
|
if (!resourceReg) {
|
|
424
445
|
LogError(`Unable to find resource registration for driver class: ${driverClass}`);
|
|
425
446
|
return;
|
|
@@ -439,6 +460,15 @@ export class TabContainerComponent {
|
|
|
439
460
|
instance.LoadCompleteEvent = () => {
|
|
440
461
|
this.emitFirstLoadCompleteOnce();
|
|
441
462
|
};
|
|
463
|
+
// Wire up display name change for single-resource mode
|
|
464
|
+
// Use a closure that resolves the tab ID at call time, not at wire-up time,
|
|
465
|
+
// because the tab may not be active yet when the component is first created.
|
|
466
|
+
instance.DisplayNameChangedEvent = (newName) => {
|
|
467
|
+
const tabId = this.workspaceManager.GetActiveTabId();
|
|
468
|
+
if (tabId) {
|
|
469
|
+
this.workspaceManager.UpdateTabTitle(tabId, newName);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
442
472
|
// Get the native element and append to container
|
|
443
473
|
const nativeElement = componentRef.hostView.rootNodes[0];
|
|
444
474
|
container.appendChild(nativeElement);
|
|
@@ -517,6 +547,13 @@ export class TabContainerComponent {
|
|
|
517
547
|
* Uses component cache to reuse components for same resources
|
|
518
548
|
*/
|
|
519
549
|
async loadTabContent(tabId, container) {
|
|
550
|
+
// Per-tab guard: prevent concurrent loads of the same tab content.
|
|
551
|
+
// This can happen when a tab's content changes while active — both the workspace
|
|
552
|
+
// config subscription reload path and onTabShown can race to call this method.
|
|
553
|
+
if (this.tabsCurrentlyLoading.has(tabId)) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
this.tabsCurrentlyLoading.add(tabId);
|
|
520
557
|
try {
|
|
521
558
|
const tab = this.workspaceManager.GetTab(tabId);
|
|
522
559
|
if (!tab) {
|
|
@@ -555,14 +592,8 @@ export class TabContainerComponent {
|
|
|
555
592
|
}
|
|
556
593
|
return;
|
|
557
594
|
}
|
|
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
|
-
}
|
|
595
|
+
// Get the component registration using the driver class (with lazy loading fallback via ClassFactory)
|
|
596
|
+
const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
|
|
566
597
|
if (!resourceReg) {
|
|
567
598
|
LogError(`Unable to find resource registration for driver class: ${driverClass}`);
|
|
568
599
|
return;
|
|
@@ -588,6 +619,11 @@ export class TabContainerComponent {
|
|
|
588
619
|
// TODO: Implement UpdateTabTitle in WorkspaceStateManager
|
|
589
620
|
}
|
|
590
621
|
};
|
|
622
|
+
// Wire up display name change notifications
|
|
623
|
+
instance.DisplayNameChangedEvent = (newName) => {
|
|
624
|
+
this.layoutManager.UpdateTabStyle(tabId, { title: newName });
|
|
625
|
+
this.workspaceManager.UpdateTabTitle(tabId, newName);
|
|
626
|
+
};
|
|
591
627
|
// Create a container div for the component
|
|
592
628
|
const componentElement = document.createElement('div');
|
|
593
629
|
componentElement.className = 'tab-content-wrapper';
|
|
@@ -605,6 +641,9 @@ export class TabContainerComponent {
|
|
|
605
641
|
catch (e) {
|
|
606
642
|
LogError(e);
|
|
607
643
|
}
|
|
644
|
+
finally {
|
|
645
|
+
this.tabsCurrentlyLoading.delete(tabId);
|
|
646
|
+
}
|
|
608
647
|
}
|
|
609
648
|
/**
|
|
610
649
|
* Update tab display name in background without loading full component
|
|
@@ -624,13 +663,7 @@ export class TabContainerComponent {
|
|
|
624
663
|
}
|
|
625
664
|
// Get the resource registration to access GetResourceDisplayName without loading full component
|
|
626
665
|
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
|
-
}
|
|
666
|
+
const resourceReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);
|
|
634
667
|
if (!resourceReg) {
|
|
635
668
|
return;
|
|
636
669
|
}
|
|
@@ -973,6 +1006,135 @@ export class TabContainerComponent {
|
|
|
973
1006
|
}
|
|
974
1007
|
this.hideContextMenu();
|
|
975
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Check if context menu tab is pinned to Home dashboard
|
|
1011
|
+
*/
|
|
1012
|
+
get isContextTabPinnedToHome() {
|
|
1013
|
+
if (!this.contextMenuTabId)
|
|
1014
|
+
return false;
|
|
1015
|
+
const tab = this.workspaceManager.GetTab(this.contextMenuTabId);
|
|
1016
|
+
if (!tab)
|
|
1017
|
+
return false;
|
|
1018
|
+
const resourceType = this.resolveResourceType(tab);
|
|
1019
|
+
return this.pinService.IsPinned(resourceType, tab.configuration);
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Pin current context menu tab to Home dashboard
|
|
1023
|
+
*/
|
|
1024
|
+
async onContextPinToHome() {
|
|
1025
|
+
if (this.isContextTabPinnedToHome) {
|
|
1026
|
+
this.hideContextMenu();
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (!this.contextMenuTabId) {
|
|
1030
|
+
this.hideContextMenu();
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const tab = this.workspaceManager.GetTab(this.contextMenuTabId);
|
|
1034
|
+
if (!tab) {
|
|
1035
|
+
this.hideContextMenu();
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const resourceType = this.resolveResourceType(tab);
|
|
1039
|
+
const activeApp = this.appManager.GetActiveApp();
|
|
1040
|
+
// Resolve nav item icon for Custom pins
|
|
1041
|
+
let pinIcon;
|
|
1042
|
+
if (resourceType === 'Custom' && activeApp) {
|
|
1043
|
+
const navItemName = tab.configuration?.['navItemName'];
|
|
1044
|
+
if (navItemName) {
|
|
1045
|
+
const navItems = await activeApp.GetNavItems();
|
|
1046
|
+
const navItem = navItems.find(ni => ni.Label === navItemName);
|
|
1047
|
+
pinIcon = navItem?.Icon || undefined;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const added = this.pinService.AddPin({
|
|
1051
|
+
DisplayName: tab.title || 'Untitled',
|
|
1052
|
+
ResourceType: resourceType,
|
|
1053
|
+
ApplicationID: tab.applicationId || activeApp?.ID,
|
|
1054
|
+
ApplicationName: activeApp?.Name,
|
|
1055
|
+
Icon: pinIcon,
|
|
1056
|
+
Color: activeApp?.GetColor() || undefined,
|
|
1057
|
+
Configuration: tab.configuration,
|
|
1058
|
+
});
|
|
1059
|
+
if (added) {
|
|
1060
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Pinned "${tab.title}" to Home`, 'success', 2000);
|
|
1061
|
+
this.captureContextTabThumbnail(tab);
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
MJNotificationService.Instance.CreateSimpleNotification(`"${tab.title}" is already pinned to Home`, 'info', 3000);
|
|
1065
|
+
}
|
|
1066
|
+
this.hideContextMenu();
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Resolve a WorkspaceTab's resource type string for pin matching
|
|
1070
|
+
*/
|
|
1071
|
+
resolveResourceType(tab) {
|
|
1072
|
+
const config = tab.configuration;
|
|
1073
|
+
const rt = config.resourceType || '';
|
|
1074
|
+
if (rt === 'Dashboards' || config['dashboardId'])
|
|
1075
|
+
return 'Dashboards';
|
|
1076
|
+
if (rt === 'User Views' || rt === 'MJ: User Views' || config['viewId'])
|
|
1077
|
+
return 'User Views';
|
|
1078
|
+
if (rt === 'Queries' || config['queryId'])
|
|
1079
|
+
return 'Queries';
|
|
1080
|
+
if (rt === 'Reports' || config['reportId'])
|
|
1081
|
+
return 'Reports';
|
|
1082
|
+
if (rt === 'Records' || (config['entity'] && config['recordId']))
|
|
1083
|
+
return 'Records';
|
|
1084
|
+
if (rt === 'Custom' || config['navItemName'])
|
|
1085
|
+
return 'Custom';
|
|
1086
|
+
return rt || 'Custom';
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Capture thumbnail for a just-pinned tab (async, non-blocking)
|
|
1090
|
+
*/
|
|
1091
|
+
async captureContextTabThumbnail(tab) {
|
|
1092
|
+
try {
|
|
1093
|
+
// Find the active content element — differs by mode
|
|
1094
|
+
let contentEl = null;
|
|
1095
|
+
if (this.useSingleResourceMode) {
|
|
1096
|
+
contentEl = this.directContentContainer?.nativeElement ?? null;
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
// In Golden Layout mode, find the active tab's content pane
|
|
1100
|
+
contentEl = this.glContainer?.nativeElement?.querySelector('.lm_item_container .lm_content');
|
|
1101
|
+
}
|
|
1102
|
+
if (!contentEl)
|
|
1103
|
+
return;
|
|
1104
|
+
const thumbnail = await this.pinService.CaptureThumbnail(contentEl);
|
|
1105
|
+
if (thumbnail) {
|
|
1106
|
+
const resourceType = this.resolveResourceType(tab);
|
|
1107
|
+
const pin = this.pinService.FindPin(resourceType, tab.configuration);
|
|
1108
|
+
if (pin) {
|
|
1109
|
+
this.pinService.UpdatePin(pin.Id, { Thumbnail: thumbnail });
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
catch {
|
|
1114
|
+
// Thumbnail capture is best-effort
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Public method for external callers (e.g. shell) to capture a thumbnail
|
|
1119
|
+
* of the currently visible content, regardless of mode.
|
|
1120
|
+
*/
|
|
1121
|
+
async CaptureActiveThumbnail() {
|
|
1122
|
+
try {
|
|
1123
|
+
let contentEl = null;
|
|
1124
|
+
if (this.useSingleResourceMode) {
|
|
1125
|
+
contentEl = this.directContentContainer?.nativeElement ?? null;
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
contentEl = this.glContainer?.nativeElement?.querySelector('.lm_item_container .lm_content');
|
|
1129
|
+
}
|
|
1130
|
+
if (!contentEl)
|
|
1131
|
+
return undefined;
|
|
1132
|
+
return await this.pinService.CaptureThumbnail(contentEl);
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
return undefined;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
976
1138
|
/**
|
|
977
1139
|
* While the naming implies this is only invoked once, components we DO NOT CONTROL might have race
|
|
978
1140
|
* conditions that result in unpredictable behavior. To avoid those causing loading screen overaly to show
|
|
@@ -990,21 +1152,21 @@ export class TabContainerComponent {
|
|
|
990
1152
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.directContentContainer = _t.first);
|
|
991
1153
|
} }, hostBindings: function TabContainerComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
992
1154
|
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) {
|
|
1155
|
+
} }, 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
1156
|
i0.ɵɵelementStart(0, "div", 2);
|
|
995
1157
|
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,
|
|
1158
|
+
i0.ɵɵconditionalCreate(3, TabContainerComponent_Conditional_3_Template, 23, 10, "div", 5);
|
|
997
1159
|
i0.ɵɵelementEnd();
|
|
998
1160
|
} if (rf & 2) {
|
|
999
1161
|
i0.ɵɵadvance();
|
|
1000
1162
|
i0.ɵɵconditional(ctx.useSingleResourceMode ? 1 : 2);
|
|
1001
1163
|
i0.ɵɵadvance(2);
|
|
1002
1164
|
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 });
|
|
1165
|
+
} }, 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
1166
|
}
|
|
1005
1167
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TabContainerComponent, [{
|
|
1006
1168
|
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"] }]
|
|
1169
|
+
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
1170
|
}], () => [{ type: i1.GoldenLayoutManager }, { type: i1.WorkspaceStateManager }, { type: i1.ApplicationManager }, { type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }, { type: i0.ChangeDetectorRef }], { glContainer: [{
|
|
1009
1171
|
type: ViewChild,
|
|
1010
1172
|
args: ['glContainer', { static: false }]
|