@memberjunction/ng-explorer-core 2.121.0 → 2.122.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/app-routing.module.d.ts +6 -5
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +266 -132
- package/dist/app-routing.module.js.map +1 -1
- package/dist/lib/app-view/application-view.component.js +10 -10
- package/dist/lib/app-view/application-view.component.js.map +1 -1
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +11 -12
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js.map +1 -1
- package/dist/lib/data-browser-component/data-browser.component.js +8 -8
- package/dist/lib/data-browser-component/data-browser.component.js.map +1 -1
- package/dist/lib/generic-browse-list/generic-browse-list.component.js +10 -7
- package/dist/lib/generic-browse-list/generic-browse-list.component.js.map +1 -1
- package/dist/lib/generic-browser-list/generic-browser-list.component.js +14 -11
- package/dist/lib/generic-browser-list/generic-browser-list.component.js.map +1 -1
- package/dist/lib/list-view/list-view.component.js +24 -21
- package/dist/lib/list-view/list-view.component.js.map +1 -1
- package/dist/lib/navigation/navigation.component.d.ts +0 -1
- package/dist/lib/navigation/navigation.component.d.ts.map +1 -1
- package/dist/lib/navigation/navigation.component.js +11 -19
- package/dist/lib/navigation/navigation.component.js.map +1 -1
- package/dist/lib/resource-browser/resource-browser.component.js +14 -11
- package/dist/lib/resource-browser/resource-browser.component.js.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +20 -0
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/artifact-resource.component.js +92 -0
- package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts +119 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js +500 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +87 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +345 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +67 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +244 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +46 -3
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +298 -21
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/list-detail-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/list-detail-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/query-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/query-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.js +22 -5
- package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/report-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/report-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/resource-wrappers-loader.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/resource-wrappers-loader.js +15 -0
- package/dist/lib/resource-wrappers/resource-wrappers-loader.js.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +45 -0
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -0
- package/dist/lib/shell/components/header/app-nav.component.js +127 -0
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -0
- package/dist/lib/shell/components/header/app-switcher.component.d.ts +53 -0
- package/dist/lib/shell/components/header/app-switcher.component.d.ts.map +1 -0
- package/dist/lib/shell/components/header/app-switcher.component.js +190 -0
- package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +83 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.js +175 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +138 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -0
- package/dist/lib/shell/components/tabs/tab-container.component.js +920 -0
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -0
- package/dist/lib/shell/services/settings-dialog.service.d.ts +28 -0
- package/dist/lib/shell/services/settings-dialog.service.d.ts.map +1 -0
- package/dist/lib/shell/services/settings-dialog.service.js +67 -0
- package/dist/lib/shell/services/settings-dialog.service.js.map +1 -0
- package/dist/lib/shell/shell.component.d.ts +166 -0
- package/dist/lib/shell/shell.component.d.ts.map +1 -0
- package/dist/lib/shell/shell.component.js +1173 -0
- package/dist/lib/shell/shell.component.js.map +1 -0
- package/dist/lib/shell/shell.module.d.ts +14 -0
- package/dist/lib/shell/shell.module.d.ts.map +1 -0
- package/dist/lib/shell/shell.module.js +42 -0
- package/dist/lib/shell/shell.module.js.map +1 -0
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js +17 -13
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts +2 -1
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js +6 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
- package/dist/lib/single-entity/single-entity.component.js +21 -17
- package/dist/lib/single-entity/single-entity.component.js.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.js +36 -30
- package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +1 -0
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +16 -9
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/dist/lib/single-report/single-report.component.d.ts +1 -7
- package/dist/lib/single-report/single-report.component.d.ts.map +1 -1
- package/dist/lib/single-report/single-report.component.js +16 -37
- package/dist/lib/single-report/single-report.component.js.map +1 -1
- package/dist/lib/single-view/single-view.component.d.ts +1 -2
- package/dist/lib/single-view/single-view.component.d.ts.map +1 -1
- package/dist/lib/single-view/single-view.component.js +35 -43
- package/dist/lib/single-view/single-view.component.js.map +1 -1
- package/dist/lib/style-guide-test/style-guide-test.component.js +20 -13
- package/dist/lib/style-guide-test/style-guide-test.component.js.map +1 -1
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.d.ts +1 -1
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.d.ts.map +1 -1
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.js +8 -11
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.js.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.d.ts.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.js +0 -5
- package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
- package/dist/module.d.ts +50 -46
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +23 -11
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +4 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +5 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +36 -35
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
import { Component, ViewChild, createComponent, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
3
|
+
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
4
|
+
import { ResourceData } from '@memberjunction/core-entities';
|
|
5
|
+
import { LogError, Metadata } from '@memberjunction/core';
|
|
6
|
+
import { ComponentCacheManager } from './component-cache-manager';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
import * as i1 from "@memberjunction/ng-base-application";
|
|
9
|
+
import * as i2 from "@angular/common";
|
|
10
|
+
const _c0 = ["glContainer"];
|
|
11
|
+
const _c1 = ["directContentContainer"];
|
|
12
|
+
function TabContainerComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
13
|
+
i0.ɵɵelement(0, "div", 3, 0);
|
|
14
|
+
} }
|
|
15
|
+
function TabContainerComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
16
|
+
i0.ɵɵelement(0, "div", 4, 1);
|
|
17
|
+
} }
|
|
18
|
+
function TabContainerComponent_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
19
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
20
|
+
i0.ɵɵelementStart(0, "div", 6)(1, "div", 7);
|
|
21
|
+
i0.ɵɵlistener("click", function TabContainerComponent_div_3_Template_div_click_1_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextPin()); });
|
|
22
|
+
i0.ɵɵelement(2, "i", 8);
|
|
23
|
+
i0.ɵɵelementStart(3, "span");
|
|
24
|
+
i0.ɵɵtext(4);
|
|
25
|
+
i0.ɵɵelementEnd()();
|
|
26
|
+
i0.ɵɵelement(5, "div", 9);
|
|
27
|
+
i0.ɵɵelementStart(6, "div", 7);
|
|
28
|
+
i0.ɵɵlistener("click", function TabContainerComponent_div_3_Template_div_click_6_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextClose()); });
|
|
29
|
+
i0.ɵɵelement(7, "i", 10);
|
|
30
|
+
i0.ɵɵelementStart(8, "span");
|
|
31
|
+
i0.ɵɵtext(9, "Close Tab");
|
|
32
|
+
i0.ɵɵelementEnd()();
|
|
33
|
+
i0.ɵɵelementStart(10, "div", 7);
|
|
34
|
+
i0.ɵɵlistener("click", function TabContainerComponent_div_3_Template_div_click_10_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseOthers()); });
|
|
35
|
+
i0.ɵɵelement(11, "i", 11);
|
|
36
|
+
i0.ɵɵelementStart(12, "span");
|
|
37
|
+
i0.ɵɵtext(13, "Close Others");
|
|
38
|
+
i0.ɵɵelementEnd()();
|
|
39
|
+
i0.ɵɵelementStart(14, "div", 7);
|
|
40
|
+
i0.ɵɵlistener("click", function TabContainerComponent_div_3_Template_div_click_14_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onContextCloseToRight()); });
|
|
41
|
+
i0.ɵɵelement(15, "i", 12);
|
|
42
|
+
i0.ɵɵelementStart(16, "span");
|
|
43
|
+
i0.ɵɵtext(17, "Close to Right");
|
|
44
|
+
i0.ɵɵelementEnd()()();
|
|
45
|
+
} if (rf & 2) {
|
|
46
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
47
|
+
i0.ɵɵstyleProp("left", ctx_r1.contextMenuX, "px")("top", ctx_r1.contextMenuY, "px");
|
|
48
|
+
i0.ɵɵadvance(4);
|
|
49
|
+
i0.ɵɵtextInterpolate(ctx_r1.isContextTabPinned ? "Unpin Tab" : "Pin Tab");
|
|
50
|
+
} }
|
|
51
|
+
/**
|
|
52
|
+
* Container for Golden Layout tabs with app-colored styling.
|
|
53
|
+
*
|
|
54
|
+
* Handles:
|
|
55
|
+
* - Golden Layout initialization
|
|
56
|
+
* - Tab creation and styling
|
|
57
|
+
* - Lazy loading of tab content
|
|
58
|
+
* - Context menu for pin/close
|
|
59
|
+
* - Layout persistence
|
|
60
|
+
*/
|
|
61
|
+
export class TabContainerComponent {
|
|
62
|
+
layoutManager;
|
|
63
|
+
workspaceManager;
|
|
64
|
+
appManager;
|
|
65
|
+
appRef;
|
|
66
|
+
environmentInjector;
|
|
67
|
+
cdr;
|
|
68
|
+
glContainer;
|
|
69
|
+
directContentContainer;
|
|
70
|
+
subscriptions = [];
|
|
71
|
+
layoutInitialized = false;
|
|
72
|
+
// Track component references for cleanup (legacy - keep for backward compat during transition)
|
|
73
|
+
componentRefs = new Map();
|
|
74
|
+
// NEW: Smart component cache for preserving state across tab switches
|
|
75
|
+
cacheManager;
|
|
76
|
+
// Single-resource mode: render component directly without Golden Layout
|
|
77
|
+
// This avoids the 20px height issue when GL header is hidden
|
|
78
|
+
useSingleResourceMode = false;
|
|
79
|
+
singleResourceComponentRef = null;
|
|
80
|
+
previousTabBarVisible = null;
|
|
81
|
+
currentSingleResourceSignature = null; // Track loaded content signature to avoid unnecessary reloads
|
|
82
|
+
isCreatingInitialTabs = false; // Flag to prevent syncTabsWithConfiguration during initial tab creation
|
|
83
|
+
// Context menu state
|
|
84
|
+
contextMenuVisible = false;
|
|
85
|
+
contextMenuX = 0;
|
|
86
|
+
contextMenuY = 0;
|
|
87
|
+
contextMenuTabId = null;
|
|
88
|
+
constructor(layoutManager, workspaceManager, appManager, appRef, environmentInjector, cdr) {
|
|
89
|
+
this.layoutManager = layoutManager;
|
|
90
|
+
this.workspaceManager = workspaceManager;
|
|
91
|
+
this.appManager = appManager;
|
|
92
|
+
this.appRef = appRef;
|
|
93
|
+
this.environmentInjector = environmentInjector;
|
|
94
|
+
this.cdr = cdr;
|
|
95
|
+
// Initialize component cache manager
|
|
96
|
+
this.cacheManager = new ComponentCacheManager(this.appRef);
|
|
97
|
+
}
|
|
98
|
+
ngOnInit() {
|
|
99
|
+
// Subscribe to tab events
|
|
100
|
+
this.subscriptions.push(this.layoutManager.TabShown.subscribe(event => {
|
|
101
|
+
this.onTabShown(event);
|
|
102
|
+
}), this.layoutManager.TabClosed.subscribe(tabId => {
|
|
103
|
+
this.cleanupTabComponent(tabId);
|
|
104
|
+
this.workspaceManager.CloseTab(tabId);
|
|
105
|
+
}), this.layoutManager.LayoutChanged.subscribe(event => {
|
|
106
|
+
const layout = this.layoutManager.SaveLayout();
|
|
107
|
+
this.workspaceManager.UpdateLayout(layout);
|
|
108
|
+
}), this.layoutManager.ActiveTab.subscribe(tabId => {
|
|
109
|
+
if (tabId) {
|
|
110
|
+
this.workspaceManager.SetActiveTab(tabId);
|
|
111
|
+
}
|
|
112
|
+
}), this.layoutManager.TabDoubleClicked.subscribe(tabId => {
|
|
113
|
+
this.workspaceManager.TogglePin(tabId);
|
|
114
|
+
}), this.layoutManager.TabRightClicked.subscribe(event => {
|
|
115
|
+
this.showContextMenu(event.x, event.y, event.tabId);
|
|
116
|
+
}));
|
|
117
|
+
// Subscribe to configuration changes to sync tabs
|
|
118
|
+
this.subscriptions.push(this.workspaceManager.Configuration.subscribe(config => {
|
|
119
|
+
if (config) {
|
|
120
|
+
if (this.useSingleResourceMode) {
|
|
121
|
+
// In single-resource mode, reload content if the tab content changed
|
|
122
|
+
// The same tab ID can have different content (tab gets reused)
|
|
123
|
+
const activeTab = config.tabs.find(t => t.id === config.activeTabId) || config.tabs[0];
|
|
124
|
+
if (activeTab) {
|
|
125
|
+
const signature = this.getTabContentSignature(activeTab);
|
|
126
|
+
if (signature !== this.currentSingleResourceSignature) {
|
|
127
|
+
this.loadSingleResourceContent();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (this.layoutInitialized && !this.isCreatingInitialTabs) {
|
|
132
|
+
// In multi-tab mode, sync with Golden Layout
|
|
133
|
+
// Skip during initial tab creation to avoid race condition (tabs would be created twice)
|
|
134
|
+
this.syncTabsWithConfiguration(config.tabs);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
// Subscribe to tab bar visibility changes for single-resource mode
|
|
139
|
+
this.subscriptions.push(this.workspaceManager.TabBarVisible.subscribe(tabBarVisible => {
|
|
140
|
+
this.handleTabBarVisibilityChange(tabBarVisible);
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
ngAfterViewInit() {
|
|
144
|
+
// Initialize Golden Layout only if we're not in single-resource mode
|
|
145
|
+
if (!this.useSingleResourceMode) {
|
|
146
|
+
this.initializeGoldenLayout();
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// In single-resource mode, load content directly
|
|
150
|
+
this.loadSingleResourceContent();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Initialize Golden Layout and load tabs
|
|
155
|
+
* @param forceCreateTabs - If true, always creates tabs fresh from config.tabs instead of restoring saved layout
|
|
156
|
+
*/
|
|
157
|
+
initializeGoldenLayout(forceCreateTabs = false) {
|
|
158
|
+
if (!this.glContainer?.nativeElement) {
|
|
159
|
+
console.warn('Golden Layout container not available, waiting...');
|
|
160
|
+
setTimeout(() => this.initializeGoldenLayout(forceCreateTabs), 50);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (this.layoutInitialized) {
|
|
164
|
+
return; // Already initialized
|
|
165
|
+
}
|
|
166
|
+
// Initialize Golden Layout
|
|
167
|
+
this.layoutManager.Initialize(this.glContainer.nativeElement);
|
|
168
|
+
// Mark layout as initialized
|
|
169
|
+
this.layoutInitialized = true;
|
|
170
|
+
// Load tabs from configuration
|
|
171
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
172
|
+
if (!config || config.tabs.length === 0) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Check if we have a saved layout structure with actual content
|
|
176
|
+
const hasSavedLayout = config.layout?.root?.content && config.layout.root.content.length > 0;
|
|
177
|
+
if (hasSavedLayout && !forceCreateTabs) {
|
|
178
|
+
// RESTORE SAVED LAYOUT - preserves drag/drop arrangements (stacks, columns, rows)
|
|
179
|
+
// This is the single source of truth for visual arrangement
|
|
180
|
+
console.log('[TabContainer.initializeGoldenLayout] Restoring saved layout structure');
|
|
181
|
+
this.layoutManager.LoadLayout(config.layout);
|
|
182
|
+
// Focus active tab and ensure proper sizing
|
|
183
|
+
setTimeout(() => {
|
|
184
|
+
if (config.activeTabId) {
|
|
185
|
+
this.layoutManager.FocusTab(config.activeTabId);
|
|
186
|
+
}
|
|
187
|
+
}, 50);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// CREATE FRESH - no saved layout or forceCreateTabs=true
|
|
191
|
+
// Use config.tabs sorted by sequence to build a simple single-stack layout
|
|
192
|
+
console.log(`[TabContainer.initializeGoldenLayout] Creating ${config.tabs.length} tabs from config (sorted by sequence)`);
|
|
193
|
+
const sortedTabs = [...config.tabs].sort((a, b) => a.sequence - b.sequence);
|
|
194
|
+
this.isCreatingInitialTabs = true;
|
|
195
|
+
try {
|
|
196
|
+
sortedTabs.forEach(tab => {
|
|
197
|
+
this.createTab(tab);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.isCreatingInitialTabs = false;
|
|
202
|
+
}
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
if (config.activeTabId) {
|
|
205
|
+
this.layoutManager.FocusTab(config.activeTabId);
|
|
206
|
+
}
|
|
207
|
+
}, 50);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
ngOnDestroy() {
|
|
211
|
+
this.subscriptions.forEach(sub => sub.unsubscribe());
|
|
212
|
+
// Cleanup single-resource mode component if exists
|
|
213
|
+
this.cleanupSingleResourceComponent();
|
|
214
|
+
// Clear the component cache (destroys all components)
|
|
215
|
+
this.cacheManager.clearCache();
|
|
216
|
+
// Cleanup any legacy componentRefs
|
|
217
|
+
this.componentRefs.forEach((ref, tabId) => {
|
|
218
|
+
this.appRef.detachView(ref.hostView);
|
|
219
|
+
ref.destroy();
|
|
220
|
+
});
|
|
221
|
+
this.componentRefs.clear();
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Handle changes to tab bar visibility - switches between single-resource and multi-tab modes
|
|
225
|
+
*/
|
|
226
|
+
handleTabBarVisibilityChange(tabBarVisible) {
|
|
227
|
+
// Skip if no change
|
|
228
|
+
if (this.previousTabBarVisible === tabBarVisible) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
this.previousTabBarVisible = tabBarVisible;
|
|
232
|
+
// Determine if we should use single-resource mode
|
|
233
|
+
const shouldUseSingleResourceMode = !tabBarVisible;
|
|
234
|
+
if (shouldUseSingleResourceMode !== this.useSingleResourceMode) {
|
|
235
|
+
console.log(`🔄 Switching to ${shouldUseSingleResourceMode ? 'single-resource' : 'multi-tab'} mode`);
|
|
236
|
+
this.useSingleResourceMode = shouldUseSingleResourceMode;
|
|
237
|
+
this.cdr.detectChanges();
|
|
238
|
+
if (this.useSingleResourceMode) {
|
|
239
|
+
// Transitioning to single-resource mode
|
|
240
|
+
// **CRITICAL FIX**: Wait for the template to render directContentContainer
|
|
241
|
+
// before trying to load content. detectChanges() only marks dirty, doesn't render immediately.
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
// First, destroy Golden Layout if it was initialized (prevents stale state)
|
|
244
|
+
if (this.layoutInitialized) {
|
|
245
|
+
console.log('[TabContainer] Destroying Golden Layout when transitioning to single-resource mode');
|
|
246
|
+
this.layoutManager.Destroy();
|
|
247
|
+
this.layoutInitialized = false;
|
|
248
|
+
}
|
|
249
|
+
// Load the active tab's content directly (now container will exist)
|
|
250
|
+
this.loadSingleResourceContent();
|
|
251
|
+
}, 0);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Transitioning to multi-tab mode
|
|
255
|
+
// Pin the previously displayed tab (it was the "current" content in single-resource mode)
|
|
256
|
+
// This ensures we only have ONE temporary tab at a time
|
|
257
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
258
|
+
if (config && config.tabs.length > 0) {
|
|
259
|
+
// The new tab (just added via OpenTabForced) is now the activeTabId
|
|
260
|
+
// All OTHER unpinned tabs should be pinned since they represent content
|
|
261
|
+
// the user explicitly kept open
|
|
262
|
+
const updatedTabs = config.tabs.map(tab => {
|
|
263
|
+
// Pin all tabs except the newly active one (which is the temporary tab)
|
|
264
|
+
if (tab.id !== config.activeTabId && !tab.isPinned) {
|
|
265
|
+
return { ...tab, isPinned: true };
|
|
266
|
+
}
|
|
267
|
+
return tab;
|
|
268
|
+
});
|
|
269
|
+
// Only update if we actually changed something
|
|
270
|
+
const hasChanges = updatedTabs.some((tab, i) => tab.isPinned !== config.tabs[i].isPinned);
|
|
271
|
+
if (hasChanges) {
|
|
272
|
+
this.workspaceManager.UpdateConfiguration({
|
|
273
|
+
...config,
|
|
274
|
+
tabs: updatedTabs
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Clean up direct component, Golden Layout will handle tabs
|
|
279
|
+
this.cleanupSingleResourceComponent();
|
|
280
|
+
this.currentSingleResourceSignature = null; // Reset tracking
|
|
281
|
+
// Reset layout initialized flag since we're switching from single-resource mode
|
|
282
|
+
// The gl-container is a new DOM element (due to @if), so we need fresh initialization
|
|
283
|
+
this.layoutInitialized = false;
|
|
284
|
+
// Initialize Golden Layout - use setTimeout to allow the template to update first
|
|
285
|
+
// and ensure the gl-container div exists in the DOM
|
|
286
|
+
// IMPORTANT: Use forceCreateTabs=true to create tabs fresh from config.tabs
|
|
287
|
+
// instead of restoring potentially stale saved layout structure
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
this.initializeGoldenLayout(true /* forceCreateTabs */);
|
|
290
|
+
}, 0);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Load content directly for single-resource mode (bypasses Golden Layout)
|
|
296
|
+
*/
|
|
297
|
+
async loadSingleResourceContent() {
|
|
298
|
+
// Wait for next tick to ensure the container is rendered
|
|
299
|
+
await Promise.resolve();
|
|
300
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
301
|
+
if (!config || config.tabs.length === 0) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// Get the active tab (or first tab)
|
|
305
|
+
const activeTab = config.tabs.find(t => t.id === config.activeTabId) || config.tabs[0];
|
|
306
|
+
if (!activeTab) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// Track which content we're loading (signature includes resource type and record ID)
|
|
310
|
+
const newSignature = this.getTabContentSignature(activeTab);
|
|
311
|
+
if (this.currentSingleResourceSignature === newSignature) {
|
|
312
|
+
// Content already loaded, no action needed
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.currentSingleResourceSignature = newSignature;
|
|
316
|
+
// Get the container element
|
|
317
|
+
const container = this.directContentContainer?.nativeElement;
|
|
318
|
+
if (!container) {
|
|
319
|
+
console.warn('Direct content container not available yet, retrying...');
|
|
320
|
+
// Retry after view is updated
|
|
321
|
+
setTimeout(() => this.loadSingleResourceContent(), 50);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// Create ResourceData from tab
|
|
325
|
+
const resourceData = await this.getResourceDataFromTab(activeTab);
|
|
326
|
+
if (!resourceData) {
|
|
327
|
+
LogError(`Unable to create ResourceData for tab: ${activeTab.title}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// Get driver class for component lookup
|
|
331
|
+
const driverClass = resourceData.Configuration?.resourceTypeDriverClass || resourceData.ResourceType;
|
|
332
|
+
// **OPTIMIZATION: Check cache first to reuse existing loaded component**
|
|
333
|
+
const cached = this.cacheManager.getCachedComponent(driverClass, resourceData.ResourceRecordID || '', activeTab.applicationId);
|
|
334
|
+
if (cached) {
|
|
335
|
+
console.log(`♻️ Reusing cached component for single-resource mode: ${driverClass}`);
|
|
336
|
+
// Clean up previous single-resource component (if different)
|
|
337
|
+
this.cleanupSingleResourceComponent();
|
|
338
|
+
// Detach from tab tracking (it was attached to a tab in Golden Layout)
|
|
339
|
+
this.cacheManager.markAsDetached(activeTab.id);
|
|
340
|
+
// Reattach the cached wrapper element to single-resource container
|
|
341
|
+
cached.wrapperElement.style.height = "100%"; // Ensure full height
|
|
342
|
+
container.appendChild(cached.wrapperElement);
|
|
343
|
+
// Store reference for cleanup
|
|
344
|
+
this.singleResourceComponentRef = cached.componentRef;
|
|
345
|
+
console.log('✅ Single-resource component transferred from cache (instant!)');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// **Fallback: Create new component if not in cache**
|
|
349
|
+
console.log(`📦 Creating new component for single-resource mode: ${driverClass}`);
|
|
350
|
+
// Get the component registration
|
|
351
|
+
const resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
|
|
352
|
+
if (!resourceReg) {
|
|
353
|
+
LogError(`Unable to find resource registration for driver class: ${driverClass}`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// Clean up previous component if any
|
|
357
|
+
this.cleanupSingleResourceComponent();
|
|
358
|
+
// Create the component dynamically
|
|
359
|
+
const componentRef = createComponent(resourceReg.SubClass, {
|
|
360
|
+
environmentInjector: this.environmentInjector
|
|
361
|
+
});
|
|
362
|
+
// Attach to Angular's change detection
|
|
363
|
+
this.appRef.attachView(componentRef.hostView);
|
|
364
|
+
// Set the resource data on the component
|
|
365
|
+
const instance = componentRef.instance;
|
|
366
|
+
instance.Data = resourceData;
|
|
367
|
+
// Wire up events
|
|
368
|
+
instance.LoadCompleteEvent = () => {
|
|
369
|
+
console.log('✅ Single-resource component loaded');
|
|
370
|
+
};
|
|
371
|
+
// Get the native element and append to container
|
|
372
|
+
const nativeElement = componentRef.hostView.rootNodes[0];
|
|
373
|
+
container.appendChild(nativeElement);
|
|
374
|
+
// now make sure that the container's direct child is 100% height
|
|
375
|
+
if (container.children?.length > 0) {
|
|
376
|
+
container.children[0].style.height = "100%";
|
|
377
|
+
}
|
|
378
|
+
// Store reference for cleanup
|
|
379
|
+
this.singleResourceComponentRef = componentRef;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Clean up single-resource mode component
|
|
383
|
+
*/
|
|
384
|
+
cleanupSingleResourceComponent() {
|
|
385
|
+
if (this.singleResourceComponentRef) {
|
|
386
|
+
this.appRef.detachView(this.singleResourceComponentRef.hostView);
|
|
387
|
+
this.singleResourceComponentRef.destroy();
|
|
388
|
+
this.singleResourceComponentRef = null;
|
|
389
|
+
}
|
|
390
|
+
// Clear the container
|
|
391
|
+
const container = this.directContentContainer?.nativeElement;
|
|
392
|
+
if (container) {
|
|
393
|
+
container.innerHTML = '';
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Generate a signature for tab content to detect when content changes
|
|
398
|
+
* This is needed because in single-resource mode, the same tab ID can have different content
|
|
399
|
+
*/
|
|
400
|
+
getTabContentSignature(tab) {
|
|
401
|
+
// Include key identifying fields that determine what component/content is shown
|
|
402
|
+
const parts = [
|
|
403
|
+
tab.applicationId,
|
|
404
|
+
tab.configuration?.resourceType || '',
|
|
405
|
+
tab.configuration?.driverClass || '',
|
|
406
|
+
tab.resourceRecordId || '',
|
|
407
|
+
tab.configuration?.route || ''
|
|
408
|
+
];
|
|
409
|
+
return parts.join('|');
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Create a tab in Golden Layout from workspace tab data
|
|
413
|
+
*/
|
|
414
|
+
createTab(tab) {
|
|
415
|
+
const app = this.appManager.GetAppById(tab.applicationId);
|
|
416
|
+
const appColor = app?.GetColor() || '#757575';
|
|
417
|
+
const state = {
|
|
418
|
+
tabId: tab.id,
|
|
419
|
+
appId: tab.applicationId,
|
|
420
|
+
appColor,
|
|
421
|
+
title: tab.title,
|
|
422
|
+
route: tab.configuration['route'] || '',
|
|
423
|
+
isPinned: tab.isPinned,
|
|
424
|
+
isLoaded: false
|
|
425
|
+
};
|
|
426
|
+
this.layoutManager.AddTab(state);
|
|
427
|
+
// Load display name in background without loading full component
|
|
428
|
+
this.updateTabDisplayName(tab);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Handle tab shown event for lazy loading
|
|
432
|
+
*/
|
|
433
|
+
async onTabShown(event) {
|
|
434
|
+
if (event.isFirstShow) {
|
|
435
|
+
// Load content for this tab
|
|
436
|
+
await this.loadTabContent(event.tabId, event.container);
|
|
437
|
+
this.layoutManager.MarkTabLoaded(event.tabId);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Load content into a tab container
|
|
442
|
+
* Uses component cache to reuse components for same resources
|
|
443
|
+
*/
|
|
444
|
+
async loadTabContent(tabId, container) {
|
|
445
|
+
try {
|
|
446
|
+
const tab = this.workspaceManager.GetTab(tabId);
|
|
447
|
+
if (!tab) {
|
|
448
|
+
LogError(`Tab not found: ${tabId}`);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
// Get the container element from Golden Layout
|
|
452
|
+
const glContainer = container;
|
|
453
|
+
if (!glContainer?.element) {
|
|
454
|
+
LogError('Golden Layout container element not found');
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// Extract resource data from tab configuration
|
|
458
|
+
const resourceData = await this.getResourceDataFromTab(tab);
|
|
459
|
+
if (!resourceData) {
|
|
460
|
+
LogError(`Unable to create ResourceData for tab: ${tab.title}`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// Clear any existing content from the container (important for tab reuse)
|
|
464
|
+
glContainer.element.innerHTML = '';
|
|
465
|
+
// Get driver class for cache lookup (resolves to actual component class name)
|
|
466
|
+
const driverClass = resourceData.Configuration?.resourceTypeDriverClass || resourceData.ResourceType;
|
|
467
|
+
// Check if we have a cached component for this resource
|
|
468
|
+
const cached = this.cacheManager.getCachedComponent(driverClass, resourceData.ResourceRecordID || '', tab.applicationId);
|
|
469
|
+
if (cached) {
|
|
470
|
+
console.log(`♻️ Reusing cached component for ${resourceData.ResourceType} (driver: ${driverClass})`);
|
|
471
|
+
// Reattach the cached wrapper element
|
|
472
|
+
glContainer.element.appendChild(cached.wrapperElement);
|
|
473
|
+
// Mark as attached to this tab
|
|
474
|
+
this.cacheManager.markAsAttached(driverClass, resourceData.ResourceRecordID || '', tab.applicationId, tabId);
|
|
475
|
+
// Keep legacy componentRefs map updated
|
|
476
|
+
this.componentRefs.set(tabId, cached.componentRef);
|
|
477
|
+
// If resource is already loaded, update tab title immediately
|
|
478
|
+
const instance = cached.componentRef.instance;
|
|
479
|
+
if (instance.LoadComplete) {
|
|
480
|
+
this.updateTabTitleFromResource(tabId, instance, resourceData);
|
|
481
|
+
}
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
// No cached component found - create new one
|
|
485
|
+
console.log(`🆕 Creating new component for ${resourceData.ResourceType} using driver class: ${driverClass}`);
|
|
486
|
+
// Get the component registration using the driver class
|
|
487
|
+
const resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
|
|
488
|
+
if (!resourceReg) {
|
|
489
|
+
LogError(`Unable to find resource registration for driver class: ${driverClass}`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
// Create the component dynamically
|
|
493
|
+
const componentRef = createComponent(resourceReg.SubClass, {
|
|
494
|
+
environmentInjector: this.environmentInjector
|
|
495
|
+
});
|
|
496
|
+
// Attach to Angular's change detection
|
|
497
|
+
this.appRef.attachView(componentRef.hostView);
|
|
498
|
+
// Set the resource data on the component
|
|
499
|
+
const instance = componentRef.instance;
|
|
500
|
+
instance.Data = resourceData;
|
|
501
|
+
// Wire up events
|
|
502
|
+
instance.LoadCompleteEvent = () => {
|
|
503
|
+
// Tab content loaded - update tab title with resource display name
|
|
504
|
+
this.updateTabTitleFromResource(tabId, instance, resourceData);
|
|
505
|
+
};
|
|
506
|
+
instance.ResourceRecordSavedEvent = (entity) => {
|
|
507
|
+
// Update tab title if needed
|
|
508
|
+
if (entity && entity.Get && entity.Get('Name')) {
|
|
509
|
+
// TODO: Implement UpdateTabTitle in WorkspaceStateManager
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
// Create a container div for the component
|
|
513
|
+
const componentElement = document.createElement('div');
|
|
514
|
+
componentElement.className = 'tab-content-wrapper';
|
|
515
|
+
componentElement.style.cssText = 'width: 100%; height: 100%;';
|
|
516
|
+
// Append the component's native element
|
|
517
|
+
const nativeElement = componentRef.hostView.rootNodes[0];
|
|
518
|
+
componentElement.appendChild(nativeElement);
|
|
519
|
+
// Add to Golden Layout container
|
|
520
|
+
glContainer.element.appendChild(componentElement);
|
|
521
|
+
// Cache the component for future reuse
|
|
522
|
+
this.cacheManager.cacheComponent(componentRef, componentElement, resourceData, tabId);
|
|
523
|
+
// Store reference for cleanup (legacy)
|
|
524
|
+
this.componentRefs.set(tabId, componentRef);
|
|
525
|
+
}
|
|
526
|
+
catch (e) {
|
|
527
|
+
LogError(e);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Update tab display name in background without loading full component
|
|
532
|
+
* This ensures all tabs show proper names immediately, not just when clicked
|
|
533
|
+
*/
|
|
534
|
+
async updateTabDisplayName(tab) {
|
|
535
|
+
try {
|
|
536
|
+
// Only update display names for resource-based tabs
|
|
537
|
+
const resourceType = tab.configuration['resourceType'];
|
|
538
|
+
if (!resourceType) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
// Get ResourceData from tab
|
|
542
|
+
const resourceData = await this.getResourceDataFromTab(tab);
|
|
543
|
+
if (!resourceData) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// Get the resource registration to access GetResourceDisplayName without loading full component
|
|
547
|
+
const driverClass = resourceData.Configuration?.resourceTypeDriverClass || resourceData.ResourceType;
|
|
548
|
+
const resourceReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseResourceComponent, driverClass);
|
|
549
|
+
if (!resourceReg) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
// Create a lightweight instance just to call GetResourceDisplayName
|
|
553
|
+
const tempInstance = new resourceReg.SubClass();
|
|
554
|
+
const displayName = await tempInstance.GetResourceDisplayName(resourceData);
|
|
555
|
+
if (displayName && displayName !== tab.title) {
|
|
556
|
+
console.log('[TabContainer.updateTabDisplayName] Updating tab title:', {
|
|
557
|
+
tabId: tab.id,
|
|
558
|
+
from: tab.title,
|
|
559
|
+
to: displayName
|
|
560
|
+
});
|
|
561
|
+
// Update the tab title in Golden Layout
|
|
562
|
+
this.layoutManager.UpdateTabStyle(tab.id, { title: displayName });
|
|
563
|
+
// Update the tab title in workspace configuration for persistence
|
|
564
|
+
this.workspaceManager.UpdateTabTitle(tab.id, displayName);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
console.error('[TabContainer.updateTabDisplayName] Error updating tab display name:', error);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Update tab title with resource display name after resource loads
|
|
573
|
+
*/
|
|
574
|
+
async updateTabTitleFromResource(tabId, resourceComponent, resourceData) {
|
|
575
|
+
try {
|
|
576
|
+
console.log('[TabContainer.updateTabTitleFromResource] Getting display name for tab:', tabId);
|
|
577
|
+
// Get the display name from the resource component
|
|
578
|
+
const displayName = await resourceComponent.GetResourceDisplayName(resourceData);
|
|
579
|
+
console.log('[TabContainer.updateTabTitleFromResource] Got display name:', displayName);
|
|
580
|
+
if (!displayName) {
|
|
581
|
+
console.log('[TabContainer.updateTabTitleFromResource] No display name returned, keeping current title');
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
// Update the tab title in Golden Layout
|
|
585
|
+
this.layoutManager.UpdateTabStyle(tabId, { title: displayName });
|
|
586
|
+
console.log('[TabContainer.updateTabTitleFromResource] Updated Golden Layout tab title to:', displayName);
|
|
587
|
+
// Update the tab title in workspace configuration for persistence
|
|
588
|
+
this.workspaceManager.UpdateTabTitle(tabId, displayName);
|
|
589
|
+
console.log('[TabContainer.updateTabTitleFromResource] Updated workspace configuration tab title');
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
console.error('[TabContainer.updateTabTitleFromResource] Error updating tab title:', error);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Convert tab configuration to ResourceData
|
|
597
|
+
*/
|
|
598
|
+
async getResourceDataFromTab(tab) {
|
|
599
|
+
const config = tab.configuration;
|
|
600
|
+
// Extract resource type from configuration or route
|
|
601
|
+
let resourceType = config['resourceType'];
|
|
602
|
+
if (!resourceType && config['route']) {
|
|
603
|
+
// Parse route to determine resource type
|
|
604
|
+
resourceType = this.getResourceTypeFromRoute(config['route']);
|
|
605
|
+
}
|
|
606
|
+
if (!resourceType) {
|
|
607
|
+
console.error('[TabContainer.getResourceDataFromTab] No resourceType found in config or route');
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
// Determine the driver class to use for component instantiation
|
|
611
|
+
let driverClass = resourceType; // Default: use resourceType as driver class
|
|
612
|
+
// For Custom resource type, get DriverClass from configuration or ResourceType metadata
|
|
613
|
+
if (resourceType.toLowerCase() === 'custom') {
|
|
614
|
+
// Custom resource type uses NavItem's DriverClass
|
|
615
|
+
driverClass = config['driverClass'];
|
|
616
|
+
if (!driverClass) {
|
|
617
|
+
LogError('Custom resource type requires driverClass in configuration');
|
|
618
|
+
console.error('[TabContainer.getResourceDataFromTab] Missing driverClass for Custom resource type');
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
// For standard resource types, look up DriverClass from metadata
|
|
624
|
+
const resourceTypeEntity = await this.getResourceTypeEntity(resourceType);
|
|
625
|
+
if (resourceTypeEntity?.DriverClass) {
|
|
626
|
+
driverClass = resourceTypeEntity.DriverClass;
|
|
627
|
+
}
|
|
628
|
+
// If no DriverClass in metadata, fall back to resourceType (backward compatibility)
|
|
629
|
+
}
|
|
630
|
+
// Include applicationId and driverClass in configuration
|
|
631
|
+
const resourceConfig = {
|
|
632
|
+
...config,
|
|
633
|
+
applicationId: tab.applicationId,
|
|
634
|
+
resourceTypeDriverClass: driverClass // Store resolved driver class for component lookup
|
|
635
|
+
};
|
|
636
|
+
// Get ResourceRecordID from config or fall back to tab.resourceRecordId
|
|
637
|
+
// Important: Some tabs store the record ID in config['recordId'], others in tab.resourceRecordId
|
|
638
|
+
const resourceRecordId = config['recordId'] || tab.resourceRecordId || '';
|
|
639
|
+
const resourceData = new ResourceData({
|
|
640
|
+
ResourceTypeID: await this.getResourceTypeId(resourceType),
|
|
641
|
+
ResourceRecordID: resourceRecordId,
|
|
642
|
+
Configuration: resourceConfig
|
|
643
|
+
});
|
|
644
|
+
return resourceData;
|
|
645
|
+
}
|
|
646
|
+
static _resourceTypesDataset = null;
|
|
647
|
+
/**
|
|
648
|
+
* Get ResourceType entity by name (includes DriverClass field)
|
|
649
|
+
*/
|
|
650
|
+
async getResourceTypeEntity(resourceType) {
|
|
651
|
+
const md = new Metadata();
|
|
652
|
+
const ds = TabContainerComponent._resourceTypesDataset || await md.GetDatasetByName("ResourceTypes");
|
|
653
|
+
if (!ds || !ds.Success || ds.Results.length === 0) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
if (!TabContainerComponent._resourceTypesDataset) {
|
|
657
|
+
TabContainerComponent._resourceTypesDataset = ds; // cache for next time
|
|
658
|
+
}
|
|
659
|
+
const result = ds.Results.find(r => r.Code.trim().toLowerCase() === 'resourcetypes');
|
|
660
|
+
if (result && result.Results?.length > 0) {
|
|
661
|
+
const rt = result.Results.find(rt => rt.Name.trim().toLowerCase() === resourceType.trim().toLowerCase());
|
|
662
|
+
return rt || null;
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
async getResourceTypeId(resourceType) {
|
|
667
|
+
const rt = await this.getResourceTypeEntity(resourceType);
|
|
668
|
+
if (rt) {
|
|
669
|
+
return rt.ID;
|
|
670
|
+
}
|
|
671
|
+
throw new Error(`ResourceType ID not found for type: ${resourceType}`);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Determine resource type from route
|
|
675
|
+
*/
|
|
676
|
+
getResourceTypeFromRoute(route) {
|
|
677
|
+
// Parse route segments to determine resource type
|
|
678
|
+
const segments = route.split('/').filter(s => s);
|
|
679
|
+
if (segments.length === 0) {
|
|
680
|
+
return 'home';
|
|
681
|
+
}
|
|
682
|
+
// Common route patterns
|
|
683
|
+
if (route.includes('/record/')) {
|
|
684
|
+
return 'record';
|
|
685
|
+
}
|
|
686
|
+
if (route.includes('/view/')) {
|
|
687
|
+
return 'view';
|
|
688
|
+
}
|
|
689
|
+
if (route.includes('/dashboard/')) {
|
|
690
|
+
return 'dashboard';
|
|
691
|
+
}
|
|
692
|
+
if (route.includes('/report/')) {
|
|
693
|
+
return 'report';
|
|
694
|
+
}
|
|
695
|
+
if (route.includes('/search')) {
|
|
696
|
+
return 'search';
|
|
697
|
+
}
|
|
698
|
+
if (route.includes('/query/')) {
|
|
699
|
+
return 'query';
|
|
700
|
+
}
|
|
701
|
+
// Default based on first segment
|
|
702
|
+
return segments[0] || 'home';
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Cleanup a tab's component
|
|
706
|
+
* Detaches from DOM but keeps in cache for potential reuse
|
|
707
|
+
*/
|
|
708
|
+
cleanupTabComponent(tabId) {
|
|
709
|
+
// First, try to detach from cache (preserves component for reuse)
|
|
710
|
+
const cachedInfo = this.cacheManager.markAsDetached(tabId);
|
|
711
|
+
if (cachedInfo) {
|
|
712
|
+
console.log(`📎 Detached component from tab ${tabId}, available for reuse`);
|
|
713
|
+
// Remove from legacy componentRefs but keep in cache
|
|
714
|
+
this.componentRefs.delete(tabId);
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
// Fallback: destroy if not in cache (shouldn't happen in normal flow)
|
|
718
|
+
const componentRef = this.componentRefs.get(tabId);
|
|
719
|
+
if (componentRef) {
|
|
720
|
+
this.appRef.detachView(componentRef.hostView);
|
|
721
|
+
componentRef.destroy();
|
|
722
|
+
this.componentRefs.delete(tabId);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Sync tabs with configuration changes
|
|
728
|
+
*/
|
|
729
|
+
syncTabsWithConfiguration(tabs) {
|
|
730
|
+
// Get existing tab IDs from Golden Layout
|
|
731
|
+
const existingTabIds = this.layoutManager.GetAllTabIds();
|
|
732
|
+
// Get tab IDs from configuration
|
|
733
|
+
const configTabIds = tabs.map(tab => tab.id);
|
|
734
|
+
// Remove tabs that are no longer in configuration
|
|
735
|
+
existingTabIds.forEach(tabId => {
|
|
736
|
+
if (!configTabIds.includes(tabId)) {
|
|
737
|
+
console.log('[TabContainer.syncTabsWithConfiguration] Removing tab not in config:', tabId);
|
|
738
|
+
this.layoutManager.RemoveTab(tabId);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
// Create tabs that don't exist yet
|
|
742
|
+
tabs.forEach(tab => {
|
|
743
|
+
if (!existingTabIds.includes(tab.id)) {
|
|
744
|
+
this.createTab(tab);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
// Check if tab content needs to be reloaded (app or resource type changed)
|
|
748
|
+
const existingComponentRef = this.componentRefs.get(tab.id);
|
|
749
|
+
if (existingComponentRef) {
|
|
750
|
+
const existingResourceData = existingComponentRef.instance.Data;
|
|
751
|
+
// For Custom resource types, also check driverClass to distinguish between different custom resources
|
|
752
|
+
const existingDriverClass = existingResourceData?.Configuration?.driverClass || existingResourceData?.Configuration?.resourceTypeDriverClass;
|
|
753
|
+
const newDriverClass = tab.configuration['driverClass'] || tab.configuration['resourceTypeDriverClass'];
|
|
754
|
+
// Normalize record IDs for comparison (treat null/undefined as empty string)
|
|
755
|
+
// IMPORTANT: Check both tab.resourceRecordId AND tab.configuration['recordId']
|
|
756
|
+
// because for nav items, the recordId is stored in configuration, not resourceRecordId
|
|
757
|
+
const existingRecordId = existingResourceData?.ResourceRecordID || '';
|
|
758
|
+
const newRecordId = tab.resourceRecordId || tab.configuration['recordId'] || '';
|
|
759
|
+
const needsReload = existingResourceData?.ResourceType !== tab.configuration['resourceType'] ||
|
|
760
|
+
existingResourceData?.Configuration?.applicationId !== tab.applicationId ||
|
|
761
|
+
existingRecordId !== newRecordId ||
|
|
762
|
+
(tab.configuration['resourceType'] === 'Custom' && existingDriverClass !== newDriverClass);
|
|
763
|
+
if (needsReload) {
|
|
764
|
+
console.log('[TabContainer.syncTabsWithConfiguration] Tab content changed, reloading:', {
|
|
765
|
+
tabId: tab.id,
|
|
766
|
+
title: tab.title,
|
|
767
|
+
existingRecordId,
|
|
768
|
+
newRecordId,
|
|
769
|
+
configRecordId: tab.configuration['recordId'],
|
|
770
|
+
recordIdChanged: existingRecordId !== newRecordId
|
|
771
|
+
});
|
|
772
|
+
// Clean up old component
|
|
773
|
+
this.cleanupTabComponent(tab.id);
|
|
774
|
+
// Mark tab as not loaded so it will reload when shown
|
|
775
|
+
this.layoutManager.MarkTabNotLoaded(tab.id);
|
|
776
|
+
// Update display name in background
|
|
777
|
+
this.updateTabDisplayName(tab);
|
|
778
|
+
// If this tab is currently active, reload it immediately
|
|
779
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
780
|
+
if (config?.activeTabId === tab.id) {
|
|
781
|
+
const glContainer = this.layoutManager.GetContainer(tab.id);
|
|
782
|
+
if (glContainer) {
|
|
783
|
+
this.loadTabContent(tab.id, glContainer);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
// Update styling for existing tabs
|
|
789
|
+
const app = this.appManager.GetAppById(tab.applicationId);
|
|
790
|
+
this.layoutManager.UpdateTabStyle(tab.id, {
|
|
791
|
+
isPinned: tab.isPinned,
|
|
792
|
+
title: tab.title,
|
|
793
|
+
appColor: app?.GetColor() || '#757575'
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
// Focus the active tab
|
|
798
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
799
|
+
if (config?.activeTabId) {
|
|
800
|
+
this.layoutManager.FocusTab(config.activeTabId);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Show context menu
|
|
805
|
+
*/
|
|
806
|
+
showContextMenu(x, y, tabId) {
|
|
807
|
+
this.contextMenuX = x;
|
|
808
|
+
this.contextMenuY = y;
|
|
809
|
+
this.contextMenuTabId = tabId;
|
|
810
|
+
this.contextMenuVisible = true;
|
|
811
|
+
// Close menu when clicking outside - use setTimeout to avoid immediate trigger
|
|
812
|
+
setTimeout(() => {
|
|
813
|
+
const clickHandler = (event) => {
|
|
814
|
+
const target = event.target;
|
|
815
|
+
if (!target.closest('.context-menu')) {
|
|
816
|
+
this.hideContextMenu();
|
|
817
|
+
document.removeEventListener('click', clickHandler);
|
|
818
|
+
document.removeEventListener('keydown', keyHandler);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
const keyHandler = (event) => {
|
|
822
|
+
if (event.key === 'Escape') {
|
|
823
|
+
this.hideContextMenu();
|
|
824
|
+
document.removeEventListener('click', clickHandler);
|
|
825
|
+
document.removeEventListener('keydown', keyHandler);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
document.addEventListener('click', clickHandler);
|
|
829
|
+
document.addEventListener('keydown', keyHandler);
|
|
830
|
+
}, 0);
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Hide context menu
|
|
834
|
+
*/
|
|
835
|
+
hideContextMenu() {
|
|
836
|
+
this.contextMenuVisible = false;
|
|
837
|
+
this.contextMenuTabId = null;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Check if context menu tab is pinned
|
|
841
|
+
*/
|
|
842
|
+
get isContextTabPinned() {
|
|
843
|
+
if (!this.contextMenuTabId)
|
|
844
|
+
return false;
|
|
845
|
+
const tab = this.workspaceManager.GetTab(this.contextMenuTabId);
|
|
846
|
+
return tab?.isPinned || false;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Toggle pin from context menu
|
|
850
|
+
*/
|
|
851
|
+
onContextPin() {
|
|
852
|
+
if (this.contextMenuTabId) {
|
|
853
|
+
this.workspaceManager.TogglePin(this.contextMenuTabId);
|
|
854
|
+
}
|
|
855
|
+
this.hideContextMenu();
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Close tab from context menu
|
|
859
|
+
*/
|
|
860
|
+
onContextClose() {
|
|
861
|
+
if (this.contextMenuTabId) {
|
|
862
|
+
this.layoutManager.RemoveTab(this.contextMenuTabId);
|
|
863
|
+
}
|
|
864
|
+
this.hideContextMenu();
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Close all other tabs from context menu
|
|
868
|
+
*/
|
|
869
|
+
onContextCloseOthers() {
|
|
870
|
+
console.log('[TabContainer.onContextCloseOthers] Called with tabId:', this.contextMenuTabId);
|
|
871
|
+
if (this.contextMenuTabId) {
|
|
872
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
873
|
+
console.log('[TabContainer.onContextCloseOthers] Current tabs:', config?.tabs.length);
|
|
874
|
+
this.workspaceManager.CloseOtherTabs(this.contextMenuTabId);
|
|
875
|
+
}
|
|
876
|
+
this.hideContextMenu();
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Close tabs to the right from context menu
|
|
880
|
+
*/
|
|
881
|
+
onContextCloseToRight() {
|
|
882
|
+
console.log('[TabContainer.onContextCloseToRight] Called with tabId:', this.contextMenuTabId);
|
|
883
|
+
if (this.contextMenuTabId) {
|
|
884
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
885
|
+
console.log('[TabContainer.onContextCloseToRight] Current tabs:', config?.tabs.length);
|
|
886
|
+
this.workspaceManager.CloseTabsToRight(this.contextMenuTabId);
|
|
887
|
+
}
|
|
888
|
+
this.hideContextMenu();
|
|
889
|
+
}
|
|
890
|
+
static ɵfac = function TabContainerComponent_Factory(t) { return new (t || TabContainerComponent)(i0.ɵɵdirectiveInject(i1.GoldenLayoutManager), i0.ɵɵdirectiveInject(i1.WorkspaceStateManager), i0.ɵɵdirectiveInject(i1.ApplicationManager), i0.ɵɵdirectiveInject(i0.ApplicationRef), i0.ɵɵdirectiveInject(i0.EnvironmentInjector), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
891
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TabContainerComponent, selectors: [["mj-tab-container"]], viewQuery: function TabContainerComponent_Query(rf, ctx) { if (rf & 1) {
|
|
892
|
+
i0.ɵɵviewQuery(_c0, 5);
|
|
893
|
+
i0.ɵɵviewQuery(_c1, 5);
|
|
894
|
+
} if (rf & 2) {
|
|
895
|
+
let _t;
|
|
896
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.glContainer = _t.first);
|
|
897
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.directContentContainer = _t.first);
|
|
898
|
+
} }, decls: 4, vars: 2, consts: [["directContentContainer", ""], ["glContainer", ""], [1, "tab-container"], [1, "direct-content-container"], [1, "gl-container"], ["class", "context-menu", 3, "left", "top", 4, "ngIf"], [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) {
|
|
899
|
+
i0.ɵɵelementStart(0, "div", 2);
|
|
900
|
+
i0.ɵɵtemplate(1, TabContainerComponent_Conditional_1_Template, 2, 0, "div", 3)(2, TabContainerComponent_Conditional_2_Template, 2, 0, "div", 4)(3, TabContainerComponent_div_3_Template, 18, 5, "div", 5);
|
|
901
|
+
i0.ɵɵelementEnd();
|
|
902
|
+
} if (rf & 2) {
|
|
903
|
+
i0.ɵɵadvance();
|
|
904
|
+
i0.ɵɵconditional(ctx.useSingleResourceMode ? 1 : 2);
|
|
905
|
+
i0.ɵɵadvance(2);
|
|
906
|
+
i0.ɵɵproperty("ngIf", ctx.contextMenuVisible);
|
|
907
|
+
} }, dependencies: [i2.NgIf], 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: white;\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: white;\n overflow: hidden;\n}\n\n.tab-content-container {\n background: white;\n color: #333;\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: white;\n border: 1px solid #e0e0e0;\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: #424242;\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: #757575;\n}\n\n.context-menu .context-menu-item:hover {\n background: #f5f5f5;\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n/* Override Golden Layout styles */\n/* Global overrides */\nmj-tab-container .lm_content {\n background: white !important;\n}\n\nmj-tab-container .lm_item_container {\n background: white !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: #f5f5f5 !important;\n border-bottom: 1px solid #ebebeb !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 #ebebeb !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: #e8e8e8 !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: white !important;\n height: 37px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid #ebebeb !important;\n border-bottom-color: white !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: #757575 !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: #c62828 !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 });
|
|
908
|
+
}
|
|
909
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TabContainerComponent, [{
|
|
910
|
+
type: Component,
|
|
911
|
+
args: [{ 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 <div\n class=\"context-menu\"\n *ngIf=\"contextMenuVisible\"\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</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: white;\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: white;\n overflow: hidden;\n}\n\n.tab-content-container {\n background: white;\n color: #333;\n padding: 20px;\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n background: white;\n border: 1px solid #e0e0e0;\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: #424242;\n transition: background 0.15s;\n}\n\n.context-menu .context-menu-item i {\n width: 16px;\n text-align: center;\n color: #757575;\n}\n\n.context-menu .context-menu-item:hover {\n background: #f5f5f5;\n}\n\n.context-menu .context-menu-divider {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n/* Override Golden Layout styles */\n/* Global overrides */\nmj-tab-container .lm_content {\n background: white !important;\n}\n\nmj-tab-container .lm_item_container {\n background: white !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: #f5f5f5 !important;\n border-bottom: 1px solid #ebebeb !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 #ebebeb !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: #e8e8e8 !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: white !important;\n height: 37px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid #ebebeb !important;\n border-bottom-color: white !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: #757575 !important;\n}\n\nmj-tab-container .lm_close_tab:hover {\n opacity: 1 !important;\n color: #c62828 !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"] }]
|
|
912
|
+
}], () => [{ type: i1.GoldenLayoutManager }, { type: i1.WorkspaceStateManager }, { type: i1.ApplicationManager }, { type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }, { type: i0.ChangeDetectorRef }], { glContainer: [{
|
|
913
|
+
type: ViewChild,
|
|
914
|
+
args: ['glContainer', { static: false }]
|
|
915
|
+
}], directContentContainer: [{
|
|
916
|
+
type: ViewChild,
|
|
917
|
+
args: ['directContentContainer', { static: false }]
|
|
918
|
+
}] }); })();
|
|
919
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TabContainerComponent, { className: "TabContainerComponent", filePath: "src/lib/shell/components/tabs/tab-container.component.ts", lineNumber: 46 }); })();
|
|
920
|
+
//# sourceMappingURL=tab-container.component.js.map
|