cloud-ide-layout 1.0.124 → 1.0.125
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/fesm2022/{cloud-ide-layout-cloud-ide-layout-DJxJ2FIt.mjs → cloud-ide-layout-cloud-ide-layout-DdMf1j7_.mjs} +980 -939
- package/fesm2022/cloud-ide-layout-cloud-ide-layout-DdMf1j7_.mjs.map +1 -0
- package/fesm2022/{cloud-ide-layout-dashboard-manager.component-C-kubr7Z.mjs → cloud-ide-layout-dashboard-manager.component-C8rnVZpP.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-dashboard-manager.component-C-kubr7Z.mjs.map → cloud-ide-layout-dashboard-manager.component-C8rnVZpP.mjs.map} +1 -1
- package/fesm2022/{cloud-ide-layout-drawer-theme.component-J2GTGGFO.mjs → cloud-ide-layout-drawer-theme.component-DV0QsDsO.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-drawer-theme.component-J2GTGGFO.mjs.map → cloud-ide-layout-drawer-theme.component-DV0QsDsO.mjs.map} +1 -1
- package/fesm2022/{cloud-ide-layout-floating-entity-selection.component-CGoIPT5y.mjs → cloud-ide-layout-floating-entity-selection.component-DteJtUqk.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-floating-entity-selection.component-CGoIPT5y.mjs.map → cloud-ide-layout-floating-entity-selection.component-DteJtUqk.mjs.map} +1 -1
- package/fesm2022/{cloud-ide-layout-home-wrapper.component-DaonafYv.mjs → cloud-ide-layout-home-wrapper.component-DjRHVU59.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-home-wrapper.component-DaonafYv.mjs.map → cloud-ide-layout-home-wrapper.component-DjRHVU59.mjs.map} +1 -1
- package/fesm2022/{cloud-ide-layout-sidedrawer-notes.component-DMzp8tJc.mjs → cloud-ide-layout-sidedrawer-notes.component-C2tOsEC4.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-sidedrawer-notes.component-DMzp8tJc.mjs.map → cloud-ide-layout-sidedrawer-notes.component-C2tOsEC4.mjs.map} +1 -1
- package/fesm2022/cloud-ide-layout.mjs +1 -1
- package/index.d.ts +12 -2
- package/package.json +1 -1
- package/fesm2022/cloud-ide-layout-cloud-ide-layout-DJxJ2FIt.mjs.map +0 -1
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, signal,
|
|
2
|
+
import { Injectable, inject, signal, computed, effect, DestroyRef, Component, ViewChild, ElementRef, HostListener, ChangeDetectionStrategy, ComponentRef, ViewContainerRef, ViewChildren, viewChild, input, InjectionToken, PLATFORM_ID, output } from '@angular/core';
|
|
3
3
|
import { HttpClient } from '@angular/common/http';
|
|
4
4
|
import { cidePath, hostManagerRoutesUrl, coreRoutesUrl, commonRoutesUrl, designConfigRoutesUrl, generateStringFromObject } from 'cloud-ide-lms-model';
|
|
5
|
-
import { Observable,
|
|
6
|
-
import { map,
|
|
5
|
+
import { Observable, throwError, of, BehaviorSubject, interval, take as take$1, firstValueFrom } from 'rxjs';
|
|
6
|
+
import { map, filter, tap, catchError, shareReplay, take, distinctUntilChanged } from 'rxjs/operators';
|
|
7
7
|
import * as i2 from '@angular/router';
|
|
8
|
-
import { Router,
|
|
8
|
+
import { Router, NavigationEnd, RouteReuseStrategy, RouterModule, ActivatedRoute } from '@angular/router';
|
|
9
9
|
import { Title, DomSanitizer } from '@angular/platform-browser';
|
|
10
|
-
import {
|
|
11
|
-
import { merge } from 'lodash';
|
|
10
|
+
import { CideEleFileManagerService, CideElementsService, CideEleFloatingContainerService, NotificationService, CideIconComponent, CideEleButtonComponent, CideInputComponent, CideSelectComponent, CideThemeService, WebSocketNotificationService, NotificationApiService, CideEleDropdownComponent, CideEleFileImageDirective, CideEleResizerDirective, TooltipDirective, CideSpinnerComponent, CideEleSkeletonLoaderComponent, KeyboardShortcutService, FloatingContainerShortcutsService, CideEleFloatingContainerManagerComponent, CideEleGlobalNotificationsComponent, CideEleBreadcrumbComponent } from 'cloud-ide-element';
|
|
12
11
|
import * as i1$1 from '@angular/common';
|
|
13
12
|
import { CommonModule, NgClass, NgFor, NgIf, isPlatformBrowser } from '@angular/common';
|
|
14
13
|
import { FINANCIAL_YEAR_SERVICE_TOKEN, ACADEMIC_YEAR_SERVICE_TOKEN, AUTH_SERVICE_TOKEN, authGuard, ENTITY_SERVICE_TOKEN } from 'cloud-ide-shared';
|
|
@@ -16,6 +15,7 @@ import * as i1 from '@angular/forms';
|
|
|
16
15
|
import { FormBuilder, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
17
16
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
18
17
|
import { trigger, state, transition, style, animate } from '@angular/animations';
|
|
18
|
+
import { merge } from 'lodash';
|
|
19
19
|
|
|
20
20
|
class CloudIdeLayoutService {
|
|
21
21
|
constructor() { }
|
|
@@ -247,1035 +247,464 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
247
247
|
}]
|
|
248
248
|
}] });
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
// Determines if this route (and its subtree) should be detached to be reused later.
|
|
276
|
-
shouldDetach(route) {
|
|
277
|
-
return !!(route.data && route.data['reuseTab']);
|
|
278
|
-
}
|
|
279
|
-
// Stores the detached route.
|
|
280
|
-
store(route, handle) {
|
|
281
|
-
const pathKey = this.getPathKey(route);
|
|
282
|
-
if (handle && route.data && route.data['reuseTab'] && pathKey) {
|
|
283
|
-
this.storedRoutes[pathKey] = handle;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
// Determines if this route (and its subtree) should be reattached.
|
|
287
|
-
shouldAttach(route) {
|
|
288
|
-
const pathKey = this.getPathKey(route);
|
|
289
|
-
return !!pathKey && !!this.storedRoutes[pathKey];
|
|
290
|
-
}
|
|
291
|
-
// Retrieves the previously stored route.
|
|
292
|
-
retrieve(route) {
|
|
293
|
-
const pathKey = this.getPathKey(route);
|
|
294
|
-
if (!pathKey || !this.storedRoutes[pathKey]) {
|
|
250
|
+
class AppStateService {
|
|
251
|
+
// Inject services
|
|
252
|
+
fileManagerService = inject(CideEleFileManagerService);
|
|
253
|
+
// Private signal for current user
|
|
254
|
+
currentUserSignal = signal(null, ...(ngDevMode ? [{ debugName: "currentUserSignal" }] : []));
|
|
255
|
+
// Private signal for active module
|
|
256
|
+
activeModuleSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeModuleSignal" }] : []));
|
|
257
|
+
// Private signal for active entity
|
|
258
|
+
activeEntitySignal = signal(null, ...(ngDevMode ? [{ debugName: "activeEntitySignal" }] : []));
|
|
259
|
+
// Track if entity change handler is initialized (to prevent triggering on initial load)
|
|
260
|
+
entityChangeHandlerInitialized = false;
|
|
261
|
+
// Callback registration for entity change handlers (to avoid circular dependencies)
|
|
262
|
+
entityChangeCallbacks = [];
|
|
263
|
+
// Public computed signal for current user, for other services to get the user value
|
|
264
|
+
currentUser = computed(() => this.currentUserSignal(), ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
|
|
265
|
+
// Public computed signal for active module
|
|
266
|
+
activeModule = computed(() => this.activeModuleSignal(), ...(ngDevMode ? [{ debugName: "activeModule" }] : []));
|
|
267
|
+
// Public computed signal for active entity
|
|
268
|
+
activeEntity = computed(() => this.activeEntitySignal(), ...(ngDevMode ? [{ debugName: "activeEntity" }] : []));
|
|
269
|
+
// Computed signals for derived state
|
|
270
|
+
isAuthenticated = computed(() => !!this.currentUserSignal(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
|
|
271
|
+
userInfo = computed(() => {
|
|
272
|
+
const user = this.currentUserSignal();
|
|
273
|
+
if (!user)
|
|
295
274
|
return null;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
275
|
+
return {
|
|
276
|
+
id: user._id,
|
|
277
|
+
name: user.user_fullname || `${user.user_firstname} ${user.user_lastname}`,
|
|
278
|
+
email: user.user_emailid,
|
|
279
|
+
role: "" // user.user_id_role
|
|
280
|
+
};
|
|
281
|
+
}, ...(ngDevMode ? [{ debugName: "userInfo" }] : []));
|
|
282
|
+
// Computed signal for active module info
|
|
283
|
+
activeModuleInfo = computed(() => {
|
|
284
|
+
const module = this.activeModuleSignal();
|
|
285
|
+
if (!module)
|
|
286
|
+
return null;
|
|
287
|
+
return {
|
|
288
|
+
id: module._id,
|
|
289
|
+
title: module.syme_title,
|
|
290
|
+
icon: module.syme_icon,
|
|
291
|
+
path: module.syme_path,
|
|
292
|
+
type: module.syme_type
|
|
293
|
+
};
|
|
294
|
+
}, ...(ngDevMode ? [{ debugName: "activeModuleInfo" }] : []));
|
|
295
|
+
constructor() {
|
|
296
|
+
// Initialize file manager base URL on app startup
|
|
297
|
+
this.fileManagerService.setBaseUrl(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager]));
|
|
298
|
+
this.fileManagerService.setObjectIdEndpoint(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, commonRoutesUrl?.module, commonRoutesUrl?.generateObjectId]));
|
|
299
|
+
// Load initial state from localStorage
|
|
300
|
+
this.loadFromLocalStorage();
|
|
301
|
+
// Save to localStorage whenever state changes
|
|
302
|
+
effect(() => {
|
|
303
|
+
const currentUser = this.currentUserSignal();
|
|
304
|
+
const activeModule = this.activeModuleSignal();
|
|
305
|
+
const activeEntity = this.activeEntitySignal();
|
|
306
|
+
this.saveToLocalStorage({
|
|
307
|
+
user: currentUser,
|
|
308
|
+
activeModule: activeModule,
|
|
309
|
+
activeEntity: activeEntity
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
// Automatically update file manager user ID whenever user changes
|
|
313
|
+
effect(() => {
|
|
314
|
+
const currentUser = this.currentUserSignal();
|
|
315
|
+
console.log('🔍 [AppStateService] User changed, updating file manager user ID:', currentUser);
|
|
316
|
+
if (currentUser?._id) {
|
|
317
|
+
this.fileManagerService.setUserId(currentUser._id);
|
|
307
318
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const viewContainerRef = handle;
|
|
338
|
-
console.log(`🗑️ Clearing view container for route: ${pathKey}`);
|
|
339
|
-
viewContainerRef.clear();
|
|
340
|
-
}
|
|
341
|
-
// Method 3: Check if handle is an object with nested ComponentRef
|
|
342
|
-
if (handle && typeof handle === 'object' && !(handle instanceof ComponentRef) && !(handle instanceof ViewContainerRef)) {
|
|
343
|
-
const handleObj = handle;
|
|
344
|
-
// Try to find and destroy ComponentRef from various possible properties
|
|
345
|
-
let componentRef = null;
|
|
346
|
-
// Check common property names
|
|
347
|
-
if (handleObj.componentRef && typeof handleObj.componentRef.destroy === 'function') {
|
|
348
|
-
componentRef = handleObj.componentRef;
|
|
349
|
-
}
|
|
350
|
-
else if (handleObj.component && typeof handleObj.component.destroy === 'function') {
|
|
351
|
-
componentRef = handleObj.component;
|
|
352
|
-
}
|
|
353
|
-
else if (handleObj.context && typeof handleObj.context.destroy === 'function') {
|
|
354
|
-
componentRef = handleObj.context;
|
|
355
|
-
}
|
|
356
|
-
else if (handleObj._componentRef && typeof handleObj._componentRef.destroy === 'function') {
|
|
357
|
-
componentRef = handleObj._componentRef;
|
|
319
|
+
else {
|
|
320
|
+
this.fileManagerService.setUserId('');
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
// GLOBAL ENTITY CHANGE HANDLER: When entity changes, close all tabs and navigate to home
|
|
324
|
+
// This ensures all components are destroyed and data is refreshed for the new entity
|
|
325
|
+
// No need to inject this service in any component - it works globally
|
|
326
|
+
let previousEntityId = null;
|
|
327
|
+
effect(() => {
|
|
328
|
+
const currentEntity = this.activeEntitySignal();
|
|
329
|
+
const currentEntityId = currentEntity?._id || null;
|
|
330
|
+
// Mark handler as initialized after first run (to skip initial load from localStorage)
|
|
331
|
+
if (!this.entityChangeHandlerInitialized) {
|
|
332
|
+
previousEntityId = currentEntityId;
|
|
333
|
+
this.entityChangeHandlerInitialized = true;
|
|
334
|
+
console.log('🏢 [AppStateService] Entity change handler initialized with initial entity:', currentEntityId);
|
|
335
|
+
return; // Skip on initial load
|
|
336
|
+
}
|
|
337
|
+
// Only trigger if entity actually changed (not on initial load)
|
|
338
|
+
if (previousEntityId !== null && currentEntityId !== previousEntityId && currentEntityId !== null) {
|
|
339
|
+
console.log('🏢 [AppStateService] Entity changed - notifying registered handlers', {
|
|
340
|
+
previousEntityId,
|
|
341
|
+
newEntityId: currentEntityId,
|
|
342
|
+
newEntityName: currentEntity?.syen_name
|
|
343
|
+
});
|
|
344
|
+
// Notify all registered entity change callbacks (e.g., close tabs, refresh components)
|
|
345
|
+
this.entityChangeCallbacks.forEach(callback => {
|
|
346
|
+
try {
|
|
347
|
+
callback(currentEntity);
|
|
358
348
|
}
|
|
359
|
-
|
|
360
|
-
console.
|
|
361
|
-
componentRef.destroy();
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error('Error in entity change callback:', error);
|
|
362
351
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
else if (previousEntityId !== null && currentEntityId === null) {
|
|
355
|
+
// Entity was cleared - also notify callbacks
|
|
356
|
+
console.log('🏢 [AppStateService] Entity cleared - notifying registered handlers');
|
|
357
|
+
this.entityChangeCallbacks.forEach(callback => {
|
|
358
|
+
try {
|
|
359
|
+
callback(null);
|
|
367
360
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
handleObj.components.forEach((comp, index) => {
|
|
371
|
-
if (comp && typeof comp.destroy === 'function') {
|
|
372
|
-
console.log(`🗑️ Destroying nested component ${index} for route: ${pathKey}`);
|
|
373
|
-
comp.destroy();
|
|
374
|
-
}
|
|
375
|
-
});
|
|
361
|
+
catch (error) {
|
|
362
|
+
console.error('Error in entity change callback:', error);
|
|
376
363
|
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
console.warn(`⚠️ Error destroying route handle for ${pathKey}:`, error);
|
|
364
|
+
});
|
|
381
365
|
}
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
console.log(`✅ Cleared stored route: ${pathKey} (removed from storedRoutes map)`);
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
console.log(`ℹ️ No stored route found for: ${pathKey}`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
// Clears all stored routes (useful for cleanup)
|
|
391
|
-
clearAllStoredRoutes() {
|
|
392
|
-
Object.keys(this.storedRoutes).forEach(pathKey => {
|
|
393
|
-
this.clearStoredRoute(pathKey);
|
|
366
|
+
// Update previous entity ID for next comparison
|
|
367
|
+
previousEntityId = currentEntityId;
|
|
394
368
|
});
|
|
369
|
+
// Listen for localStorage changes from other tabs/windows
|
|
370
|
+
this.setupStorageListener();
|
|
395
371
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Set the current user
|
|
374
|
+
*/
|
|
375
|
+
setUser(user) {
|
|
376
|
+
this.currentUserSignal.set(user);
|
|
377
|
+
// Note: File manager user ID is automatically updated via effect() in constructor
|
|
399
378
|
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Creates the default state for a new tab.
|
|
404
|
-
*/
|
|
405
|
-
const createDefaultComponentState = () => ({
|
|
406
|
-
sideDrawer: { isOpen: false, activeComponent: null, componentState: {} },
|
|
407
|
-
footer: { someData: '' },
|
|
408
|
-
console: { history: [] },
|
|
409
|
-
});
|
|
410
|
-
class TabStateService {
|
|
411
|
-
// Holds the state for all open tabs, keyed by tab ID.
|
|
412
|
-
_tabsState = new BehaviorSubject(new Map());
|
|
413
|
-
// Holds the ID of the currently active tab.
|
|
414
|
-
_activeTabId = new BehaviorSubject(null);
|
|
415
|
-
/** An observable of all open tabs. */
|
|
416
|
-
tabs$ = this._tabsState.pipe(map(tabsMap => Array.from(tabsMap.values())));
|
|
417
|
-
/** An observable of the currently active tab's ID. */
|
|
418
|
-
activeTabId$ = this._activeTabId.asObservable().pipe(distinctUntilChanged());
|
|
419
|
-
/** An observable of the state of the currently active tab. */
|
|
420
|
-
activeTabState$ = this.activeTabId$.pipe(map(activeId => (activeId ? this._tabsState.getValue().get(activeId) ?? null : null)),
|
|
421
|
-
// Use a deep-ish comparison to prevent emissions when only the object reference changes
|
|
422
|
-
distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)));
|
|
423
|
-
/** An observable of just the component-specific state for the active tab. */
|
|
424
|
-
activeComponentState$ = this.activeTabState$.pipe(map(activeTab => activeTab?.componentState ?? null));
|
|
425
379
|
/**
|
|
426
|
-
*
|
|
427
|
-
* @param title The title for the new tab.
|
|
428
|
-
* @param id An optional unique ID. If not provided, one will be generated.
|
|
429
|
-
* @returns The ID of the newly created tab.
|
|
380
|
+
* Update user data (partial update)
|
|
430
381
|
*/
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
componentState: createDefaultComponentState(),
|
|
437
|
-
};
|
|
438
|
-
const currentTabs = this._tabsState.getValue();
|
|
439
|
-
const newTabs = new Map(currentTabs).set(newTabId, newTab);
|
|
440
|
-
this._tabsState.next(newTabs);
|
|
441
|
-
this.setActiveTab(newTabId);
|
|
442
|
-
return newTabId;
|
|
382
|
+
updateUser(updates) {
|
|
383
|
+
const currentUser = this.currentUserSignal();
|
|
384
|
+
if (currentUser) {
|
|
385
|
+
this.currentUserSignal.set({ ...currentUser, ...updates });
|
|
386
|
+
}
|
|
443
387
|
}
|
|
444
388
|
/**
|
|
445
|
-
*
|
|
446
|
-
* @param tabIdToRemove The ID of the tab to remove.
|
|
389
|
+
* Clear user data
|
|
447
390
|
*/
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (!currentTabs.has(tabIdToRemove)) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
const newTabs = new Map(currentTabs);
|
|
454
|
-
newTabs.delete(tabIdToRemove);
|
|
455
|
-
this._tabsState.next(newTabs);
|
|
391
|
+
clearUser() {
|
|
392
|
+
this.currentUserSignal.set(null);
|
|
456
393
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Get current user value (non-reactive)
|
|
396
|
+
*/
|
|
397
|
+
getUserValue() {
|
|
398
|
+
return this.currentUserSignal();
|
|
461
399
|
}
|
|
462
400
|
/**
|
|
463
|
-
*
|
|
464
|
-
* @param tabId The unique identifier of the tab to activate.
|
|
401
|
+
* Set the active module
|
|
465
402
|
*/
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
this._activeTabId.next(tabId);
|
|
469
|
-
}
|
|
403
|
+
setActiveModule(module) {
|
|
404
|
+
this.activeModuleSignal.set(module);
|
|
470
405
|
}
|
|
471
406
|
/**
|
|
472
|
-
*
|
|
473
|
-
* This performs a deep merge to avoid overwriting nested state.
|
|
474
|
-
* @param partialState The partial state to update for the active tab.
|
|
407
|
+
* Get active module value (non-reactive)
|
|
475
408
|
*/
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if (!activeId) {
|
|
479
|
-
console.warn('Cannot update state, no active tab.');
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
const tabs = this._tabsState.getValue();
|
|
483
|
-
const activeTabState = tabs.get(activeId);
|
|
484
|
-
if (activeTabState) {
|
|
485
|
-
// Use lodash merge for a deep merge
|
|
486
|
-
const updatedState = merge({}, activeTabState, { componentState: partialState });
|
|
487
|
-
const newTabsState = new Map(tabs);
|
|
488
|
-
newTabsState.set(activeId, updatedState);
|
|
489
|
-
this._tabsState.next(newTabsState);
|
|
490
|
-
}
|
|
409
|
+
getActiveModuleValue() {
|
|
410
|
+
return this.activeModuleSignal();
|
|
491
411
|
}
|
|
492
412
|
/**
|
|
493
|
-
*
|
|
494
|
-
* @param tabId The ID of the tab to observe.
|
|
413
|
+
* Set the active entity
|
|
495
414
|
*/
|
|
496
|
-
|
|
497
|
-
|
|
415
|
+
setActiveEntity(entity) {
|
|
416
|
+
this.activeEntitySignal.set(entity);
|
|
498
417
|
}
|
|
499
418
|
/**
|
|
500
|
-
*
|
|
501
|
-
* @param idToRemove The ID of the tab being removed.
|
|
502
|
-
* @returns The ID of the next tab to activate, or null if no tabs are left.
|
|
419
|
+
* Get active entity value (non-reactive)
|
|
503
420
|
*/
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (tabsMap.size <= 1) {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
const tabIds = Array.from(tabsMap.keys());
|
|
510
|
-
const removedIndex = tabIds.indexOf(idToRemove);
|
|
511
|
-
// If it's the last tab in the list, activate the one before it. Otherwise, activate the next one.
|
|
512
|
-
const nextIndex = removedIndex === tabIds.length - 1 ? removedIndex - 1 : removedIndex + 1;
|
|
513
|
-
return tabIds[nextIndex];
|
|
421
|
+
getActiveEntityValue() {
|
|
422
|
+
return this.activeEntitySignal();
|
|
514
423
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}], ctorParameters: () => [] });
|
|
524
|
-
|
|
525
|
-
class CideLytRequestService {
|
|
526
|
-
requestVisible = false;
|
|
527
|
-
// Modern Angular v20+ pattern: Use Signals instead of BehaviorSubject
|
|
528
|
-
tabsSignal = signal([], ...(ngDevMode ? [{ debugName: "tabsSignal" }] : []));
|
|
529
|
-
activeTabIdSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeTabIdSignal" }] : []));
|
|
530
|
-
// Computed signals for derived state and public readonly access
|
|
531
|
-
tabs = computed(() => this.tabsSignal(), ...(ngDevMode ? [{ debugName: "tabs" }] : []));
|
|
532
|
-
activeTabId = computed(() => this.activeTabIdSignal(), ...(ngDevMode ? [{ debugName: "activeTabId" }] : []));
|
|
533
|
-
activeTab = computed(() => this.tabsSignal().find(tab => tab.active), ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
534
|
-
// Use inject() for all dependencies
|
|
535
|
-
router = inject(Router);
|
|
536
|
-
routeReuseStrategy = inject(RouteReuseStrategy);
|
|
537
|
-
tabStateService = inject(TabStateService);
|
|
538
|
-
sidedrawerService = inject(CideLytSidedrawerService);
|
|
539
|
-
sharedService = inject(CideLytSharedService);
|
|
540
|
-
floatingContainerService = inject(CideEleFloatingContainerService);
|
|
541
|
-
constructor() {
|
|
542
|
-
// Initialize router
|
|
543
|
-
this.router = inject(Router);
|
|
544
|
-
this.router.routeReuseStrategy = new CustomRouteReuseStrategy();
|
|
545
|
-
// Register tab management callback with shared service to avoid circular dependency
|
|
546
|
-
this.sharedService.registerTabManagement(this.handleRouteBasedTabManagement.bind(this));
|
|
547
|
-
// Register request visibility callback
|
|
548
|
-
this.sharedService.registerRequestVisibility(this.handleRequestVisibility.bind(this));
|
|
424
|
+
/**
|
|
425
|
+
* Register a callback to be called when entity changes
|
|
426
|
+
* This allows other services to react to entity changes without creating circular dependencies
|
|
427
|
+
* @param callback Function to call when entity changes, receives the new entity (or null if cleared)
|
|
428
|
+
*/
|
|
429
|
+
registerEntityChangeHandler(callback) {
|
|
430
|
+
this.entityChangeCallbacks.push(callback);
|
|
431
|
+
console.log('📝 [AppStateService] Registered entity change handler, total handlers:', this.entityChangeCallbacks.length);
|
|
549
432
|
}
|
|
550
433
|
/**
|
|
551
|
-
*
|
|
434
|
+
* Unregister an entity change callback
|
|
435
|
+
* @param callback The callback function to remove
|
|
552
436
|
*/
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
this.hideRequest();
|
|
437
|
+
unregisterEntityChangeHandler(callback) {
|
|
438
|
+
const index = this.entityChangeCallbacks.indexOf(callback);
|
|
439
|
+
if (index > -1) {
|
|
440
|
+
this.entityChangeCallbacks.splice(index, 1);
|
|
441
|
+
console.log('📝 [AppStateService] Unregistered entity change handler, remaining handlers:', this.entityChangeCallbacks.length);
|
|
559
442
|
}
|
|
560
443
|
}
|
|
561
444
|
/**
|
|
562
|
-
*
|
|
445
|
+
* Manually refresh state from localStorage
|
|
446
|
+
* Call this method when you need to sync with external localStorage changes
|
|
563
447
|
*/
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
//
|
|
567
|
-
console.log('🔗 REQUEST SERVICE: Tab activated, sidebar sync handled by shared service:', tabInfo.title);
|
|
448
|
+
refreshFromLocalStorage() {
|
|
449
|
+
this.loadFromLocalStorage();
|
|
450
|
+
// Note: File manager user ID is automatically updated via effect() when user signal changes
|
|
568
451
|
}
|
|
569
452
|
/**
|
|
570
|
-
*
|
|
571
|
-
* This method checks for existing tabs and creates/activates as needed
|
|
453
|
+
* Check if localStorage is available (browser environment)
|
|
572
454
|
*/
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const tabAlreadyExists = currentTabs.find((t) => {
|
|
577
|
-
const tRoutePath = t.route.startsWith('/') ? t.route : '/' + t.route;
|
|
578
|
-
const tParamsMatch = JSON.stringify(t.params || {}) === JSON.stringify(queryParams || {});
|
|
579
|
-
return tRoutePath === currentRoutePath && tParamsMatch;
|
|
580
|
-
});
|
|
581
|
-
console.log('tabAlreadyExists', tabAlreadyExists);
|
|
582
|
-
if (!tabAlreadyExists) {
|
|
583
|
-
const title = pageData.data?.page?.sypg_title || pageCode.toString() || 'Untitled Tab';
|
|
584
|
-
console.log(`🆕 Adding new tab: ${title}`);
|
|
585
|
-
this.addTab(title, currentRoutePath, queryParams, layout);
|
|
586
|
-
}
|
|
587
|
-
else if (tabAlreadyExists && !tabAlreadyExists.active) {
|
|
588
|
-
console.log(`🔄 Activating existing tab: ${tabAlreadyExists.title}`);
|
|
589
|
-
this.activateTab(tabAlreadyExists.id);
|
|
590
|
-
}
|
|
591
|
-
else if (tabAlreadyExists && tabAlreadyExists.active) {
|
|
592
|
-
console.log(`🔄 Activating existing tab: ${tabAlreadyExists.title}`);
|
|
593
|
-
this.activateTab(tabAlreadyExists.id);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
addTab(title, route, params, layout) {
|
|
597
|
-
console.log('addTab', title, route, params, layout);
|
|
598
|
-
const id = this.generateId();
|
|
599
|
-
const currentTabs = this.tabsSignal();
|
|
600
|
-
console.log('currentTabs', currentTabs, route);
|
|
601
|
-
// Tab reuse disabled - always create new tab
|
|
602
|
-
// Removed existing tab check to prevent tab reuse
|
|
603
|
-
// Create new tab
|
|
604
|
-
const newTab = {
|
|
605
|
-
id,
|
|
606
|
-
title,
|
|
607
|
-
route,
|
|
608
|
-
params,
|
|
609
|
-
active: true,
|
|
610
|
-
layout,
|
|
611
|
-
sytm_page_id_sypg: '',
|
|
612
|
-
themeId: ''
|
|
613
|
-
};
|
|
614
|
-
// Deactivate all other tabs
|
|
615
|
-
currentTabs.forEach((tab) => tab.active = false);
|
|
616
|
-
console.log('currentTabs after deactivate', currentTabs, 'activateTab', id);
|
|
617
|
-
// Add new tab
|
|
618
|
-
this.tabsSignal.set([...currentTabs, newTab]);
|
|
619
|
-
this.activeTabIdSignal.set(id);
|
|
620
|
-
// Sync with TabStateService
|
|
621
|
-
this.tabStateService.addTab(title, id);
|
|
622
|
-
// Request visibility is now controlled by layout configuration in setPageData
|
|
623
|
-
// No need to automatically show request wrapper here
|
|
624
|
-
// Navigate to the new route
|
|
625
|
-
if (params) {
|
|
626
|
-
this.router.navigate([route], { queryParams: params });
|
|
455
|
+
isLocalStorageAvailable() {
|
|
456
|
+
try {
|
|
457
|
+
return typeof window !== 'undefined' && typeof localStorage !== 'undefined';
|
|
627
458
|
}
|
|
628
|
-
|
|
629
|
-
|
|
459
|
+
catch {
|
|
460
|
+
return false;
|
|
630
461
|
}
|
|
631
|
-
return id;
|
|
632
462
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (!
|
|
638
|
-
console.warn('⚠️ Tab not found:', tabId);
|
|
463
|
+
/**
|
|
464
|
+
* Save state to localStorage
|
|
465
|
+
*/
|
|
466
|
+
saveToLocalStorage(data) {
|
|
467
|
+
if (!this.isLocalStorageAvailable()) {
|
|
639
468
|
return;
|
|
640
469
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
.
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
470
|
+
try {
|
|
471
|
+
if (data.user !== undefined) {
|
|
472
|
+
if (data.user) {
|
|
473
|
+
localStorage.setItem('cide_auth_user', JSON.stringify(data.user));
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
localStorage.removeItem('cide_auth_user');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (data.activeModule !== undefined) {
|
|
480
|
+
if (data.activeModule) {
|
|
481
|
+
localStorage.setItem('cide_active_module', JSON.stringify(data.activeModule));
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
localStorage.removeItem('cide_active_module');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (data.activeEntity !== undefined) {
|
|
488
|
+
if (data.activeEntity) {
|
|
489
|
+
localStorage.setItem('cide_active_entity', JSON.stringify(data.activeEntity));
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
localStorage.removeItem('cide_active_entity');
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
console.error('Error saving to localStorage:', error);
|
|
655
498
|
}
|
|
656
|
-
// Update tabs: deactivate all, then activate the target
|
|
657
|
-
const updatedTabs = tabs.map(tab => ({
|
|
658
|
-
...tab,
|
|
659
|
-
active: tab.id === tabId
|
|
660
|
-
}));
|
|
661
|
-
this.tabsSignal.set(updatedTabs);
|
|
662
|
-
this.activeTabIdSignal.set(tabId);
|
|
663
|
-
console.log('✅ REQUEST SERVICE: Tab activated:', tabToActivate.title);
|
|
664
|
-
// Navigate to the tab's route
|
|
665
|
-
this.router.navigate([tabToActivate.route], {
|
|
666
|
-
queryParams: tabToActivate.params || {},
|
|
667
|
-
replaceUrl: false
|
|
668
|
-
});
|
|
669
|
-
// Trigger sidebar sync for the activated tab by calling shared service
|
|
670
|
-
// The shared service will handle the sidebar sync when the route loads
|
|
671
|
-
console.log('🔗 REQUEST SERVICE: Sidebar sync will be handled by route change');
|
|
672
499
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
500
|
+
/**
|
|
501
|
+
* Load state from localStorage
|
|
502
|
+
*/
|
|
503
|
+
loadFromLocalStorage() {
|
|
504
|
+
if (!this.isLocalStorageAvailable()) {
|
|
677
505
|
return;
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
// Generate pathKey in the same format as CustomRouteReuseStrategy.getPathKey()
|
|
685
|
-
// Remove leading slash to match the format used by getPathKey
|
|
686
|
-
let pathKey = tabToClose.route.startsWith('/') ? tabToClose.route.substring(1) : tabToClose.route;
|
|
687
|
-
// Only add query params if the route is marked for reuse (matching getPathKey logic)
|
|
688
|
-
// But we'll try to clear with both formats to be safe
|
|
689
|
-
if (tabToClose.params && Object.keys(tabToClose.params).length > 0) {
|
|
690
|
-
const queryParamsString = Object.keys(tabToClose.params)
|
|
691
|
-
.sort()
|
|
692
|
-
.map(key => `${key}=${tabToClose.params[key]}`)
|
|
693
|
-
.join('&');
|
|
694
|
-
pathKey += '?' + queryParamsString;
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
const userData = localStorage.getItem('cide_auth_user');
|
|
509
|
+
if (userData) {
|
|
510
|
+
const user = JSON.parse(userData);
|
|
511
|
+
this.currentUserSignal.set(user);
|
|
695
512
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const pathKeyWithoutParams = tabToClose.route.startsWith('/') ? tabToClose.route.substring(1) : tabToClose.route;
|
|
701
|
-
if (pathKeyWithoutParams !== pathKey) {
|
|
702
|
-
this.routeReuseStrategy.clearStoredRoute(pathKeyWithoutParams);
|
|
513
|
+
const moduleData = localStorage.getItem('cide_active_module');
|
|
514
|
+
if (moduleData) {
|
|
515
|
+
const module = JSON.parse(moduleData);
|
|
516
|
+
this.activeModuleSignal.set(module);
|
|
703
517
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (wasActive) {
|
|
709
|
-
// The navigation to the new active tab will happen below, which will properly clear the router outlet
|
|
710
|
-
// But we also want to ensure the component is removed immediately
|
|
711
|
-
console.log(`🧹 REQUEST SERVICE: Active tab closed, component will be removed on navigation`);
|
|
518
|
+
const entityData = localStorage.getItem('cide_active_entity');
|
|
519
|
+
if (entityData) {
|
|
520
|
+
const entity = JSON.parse(entityData);
|
|
521
|
+
this.activeEntitySignal.set(entity);
|
|
712
522
|
}
|
|
713
523
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
this.sidedrawerService.updateDrawerItems(newActiveTab.layout);
|
|
722
|
-
newTabs[newActiveIndex].active = true;
|
|
723
|
-
this.activeTabIdSignal.set(newActiveTab.id);
|
|
724
|
-
this.tabStateService.setActiveTab(newActiveTab.id);
|
|
725
|
-
// Navigate to the new active tab's route
|
|
726
|
-
if (newActiveTab.params) {
|
|
727
|
-
this.router.navigate([newActiveTab.route], { queryParams: newActiveTab.params });
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
this.router.navigate([newActiveTab.route]);
|
|
524
|
+
catch (error) {
|
|
525
|
+
console.error('Error loading from localStorage:', error);
|
|
526
|
+
// Clear corrupted data
|
|
527
|
+
if (this.isLocalStorageAvailable()) {
|
|
528
|
+
localStorage.removeItem('cide_auth_user');
|
|
529
|
+
localStorage.removeItem('cide_active_module');
|
|
530
|
+
localStorage.removeItem('cide_active_entity');
|
|
731
531
|
}
|
|
732
532
|
}
|
|
733
|
-
else if (newTabs.length === 0) {
|
|
734
|
-
this.activeTabIdSignal.set(null);
|
|
735
|
-
this.tabStateService.setActiveTab(null);
|
|
736
|
-
// Clear sidedrawer as no tabs are open
|
|
737
|
-
this.sidedrawerService.updateDrawerItems(undefined);
|
|
738
|
-
// Redirect to home screen when all tabs are closed
|
|
739
|
-
this.router.navigate(['/control-panel']);
|
|
740
|
-
}
|
|
741
|
-
this.tabsSignal.set(newTabs);
|
|
742
|
-
// Request wrapper visibility is now controlled by layout configuration
|
|
743
|
-
// The wrapper will be hidden/shown based on sytm_layout_request.status in setPageData
|
|
744
533
|
}
|
|
745
534
|
/**
|
|
746
|
-
*
|
|
747
|
-
* This is used when entity changes to ensure all components are destroyed and refreshed
|
|
535
|
+
* Setup storage event listener to handle external localStorage changes
|
|
748
536
|
*/
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
.
|
|
766
|
-
|
|
537
|
+
setupStorageListener() {
|
|
538
|
+
if (!this.isLocalStorageAvailable()) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
// Listen for storage events (triggered when localStorage is modified from other tabs/windows)
|
|
542
|
+
window.addEventListener('storage', (event) => {
|
|
543
|
+
if (!event.key)
|
|
544
|
+
return;
|
|
545
|
+
try {
|
|
546
|
+
// Handle user data changes
|
|
547
|
+
if (event.key === 'cide_auth_user') {
|
|
548
|
+
if (event.newValue) {
|
|
549
|
+
const user = JSON.parse(event.newValue);
|
|
550
|
+
this.currentUserSignal.set(user);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
this.currentUserSignal.set(null);
|
|
554
|
+
}
|
|
767
555
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
556
|
+
// Handle active module changes
|
|
557
|
+
if (event.key === 'cide_active_module') {
|
|
558
|
+
if (event.newValue) {
|
|
559
|
+
const module = JSON.parse(event.newValue);
|
|
560
|
+
this.activeModuleSignal.set(module);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
this.activeModuleSignal.set(null);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// Handle active entity changes
|
|
567
|
+
if (event.key === 'cide_active_entity') {
|
|
568
|
+
if (event.newValue) {
|
|
569
|
+
const entity = JSON.parse(event.newValue);
|
|
570
|
+
this.activeEntitySignal.set(entity);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
this.activeEntitySignal.set(null);
|
|
574
|
+
}
|
|
773
575
|
}
|
|
774
576
|
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
console.error('Error parsing localStorage update:', error);
|
|
579
|
+
}
|
|
775
580
|
});
|
|
776
|
-
// Clear all tabs
|
|
777
|
-
this.tabsSignal.set([]);
|
|
778
|
-
this.activeTabIdSignal.set(null);
|
|
779
|
-
this.tabStateService.setActiveTab(null);
|
|
780
|
-
// Clear sidedrawer
|
|
781
|
-
this.sidedrawerService.updateDrawerItems(undefined);
|
|
782
|
-
// Navigate to home
|
|
783
|
-
this.router.navigate(['/control-panel']);
|
|
784
|
-
console.log('✅ REQUEST SERVICE: All tabs and floating containers closed, navigated to home');
|
|
785
|
-
}
|
|
786
|
-
// Hide Request
|
|
787
|
-
hideRequest() {
|
|
788
|
-
console.log('🚫 REQUEST SERVICE - Hiding request wrapper');
|
|
789
|
-
this.requestVisible = false;
|
|
790
|
-
document.querySelector(`#cide-lyt-request-wrapper`)?.classList.add('cide-lyt-request-wrapper-hide');
|
|
791
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-request-exist');
|
|
792
581
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
582
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
583
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, providedIn: 'root' });
|
|
584
|
+
}
|
|
585
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, decorators: [{
|
|
586
|
+
type: Injectable,
|
|
587
|
+
args: [{
|
|
588
|
+
providedIn: 'root'
|
|
589
|
+
}]
|
|
590
|
+
}], ctorParameters: () => [] });
|
|
591
|
+
|
|
592
|
+
class AppStateHelperService {
|
|
593
|
+
appStateService = inject(AppStateService);
|
|
594
|
+
sidebarService = inject(CideLytSidebarService);
|
|
595
|
+
// Computed signals for derived state
|
|
596
|
+
currentUser = this.appStateService.currentUser;
|
|
597
|
+
isAuthenticated = this.appStateService.isAuthenticated;
|
|
598
|
+
userInfo = this.appStateService.userInfo;
|
|
599
|
+
activeModule = this.appStateService.activeModule;
|
|
600
|
+
activeModuleInfo = this.appStateService.activeModuleInfo;
|
|
601
|
+
activeEntity = this.appStateService.activeEntity;
|
|
602
|
+
/**
|
|
603
|
+
* Get current user value (non-reactive)
|
|
604
|
+
*/
|
|
605
|
+
getCurrentUser() {
|
|
606
|
+
return this.appStateService.getUserValue();
|
|
799
607
|
}
|
|
800
|
-
|
|
801
|
-
|
|
608
|
+
/**
|
|
609
|
+
* Set user data
|
|
610
|
+
*/
|
|
611
|
+
setUser(user) {
|
|
612
|
+
this.appStateService.setUser(user);
|
|
802
613
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
tabToUpdate.scrollTop = scrollTop;
|
|
809
|
-
tabToUpdate.scrollLeft = scrollLeft;
|
|
810
|
-
// Create a new array reference to trigger change detection
|
|
811
|
-
this.tabsSignal.set([...currentTabs]);
|
|
812
|
-
}
|
|
614
|
+
/**
|
|
615
|
+
* Update user data (partial update)
|
|
616
|
+
*/
|
|
617
|
+
updateUser(updates) {
|
|
618
|
+
this.appStateService.updateUser(updates);
|
|
813
619
|
}
|
|
814
620
|
/**
|
|
815
|
-
*
|
|
816
|
-
* This is useful when you want to ensure a clean component state
|
|
817
|
-
* @param route The route path to refresh
|
|
818
|
-
* @param params Optional query parameters
|
|
621
|
+
* Clear user data
|
|
819
622
|
*/
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
let pathKey = route.startsWith('/') ? route.substring(1) : route;
|
|
823
|
-
if (params && Object.keys(params).length > 0) {
|
|
824
|
-
const queryParamsString = Object.keys(params)
|
|
825
|
-
.sort()
|
|
826
|
-
.map(key => `${key}=${params[key]}`)
|
|
827
|
-
.join('&');
|
|
828
|
-
pathKey += '?' + queryParamsString;
|
|
829
|
-
}
|
|
830
|
-
// Clear the stored route to force a fresh component instance
|
|
831
|
-
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
832
|
-
console.log(`🔄 REQUEST SERVICE: Force refreshed route ${pathKey}`);
|
|
833
|
-
}
|
|
623
|
+
clearUser() {
|
|
624
|
+
this.appStateService.clearUser();
|
|
834
625
|
}
|
|
835
626
|
/**
|
|
836
|
-
*
|
|
627
|
+
* Check if user is authenticated
|
|
837
628
|
*/
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const count = this.routeReuseStrategy.getStoredRoutesCount();
|
|
841
|
-
return { count, routes: [] }; // Could be enhanced to return actual route paths
|
|
842
|
-
}
|
|
843
|
-
return { count: 0, routes: [] };
|
|
629
|
+
isUserAuthenticated() {
|
|
630
|
+
return this.appStateService.isAuthenticated();
|
|
844
631
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
args: [{
|
|
851
|
-
providedIn: 'root'
|
|
852
|
-
}]
|
|
853
|
-
}], ctorParameters: () => [] });
|
|
854
|
-
|
|
855
|
-
class AppStateService {
|
|
856
|
-
// Inject services
|
|
857
|
-
fileManagerService = inject(CideEleFileManagerService);
|
|
858
|
-
router = inject(Router);
|
|
859
|
-
requestService = inject(CideLytRequestService);
|
|
860
|
-
// Private signal for current user
|
|
861
|
-
currentUserSignal = signal(null, ...(ngDevMode ? [{ debugName: "currentUserSignal" }] : []));
|
|
862
|
-
// Private signal for active module
|
|
863
|
-
activeModuleSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeModuleSignal" }] : []));
|
|
864
|
-
// Private signal for active entity
|
|
865
|
-
activeEntitySignal = signal(null, ...(ngDevMode ? [{ debugName: "activeEntitySignal" }] : []));
|
|
866
|
-
// Track if entity change handler is initialized (to prevent triggering on initial load)
|
|
867
|
-
entityChangeHandlerInitialized = false;
|
|
868
|
-
// Public computed signal for current user, for other services to get the user value
|
|
869
|
-
currentUser = computed(() => this.currentUserSignal(), ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
|
|
870
|
-
// Public computed signal for active module
|
|
871
|
-
activeModule = computed(() => this.activeModuleSignal(), ...(ngDevMode ? [{ debugName: "activeModule" }] : []));
|
|
872
|
-
// Public computed signal for active entity
|
|
873
|
-
activeEntity = computed(() => this.activeEntitySignal(), ...(ngDevMode ? [{ debugName: "activeEntity" }] : []));
|
|
874
|
-
// Computed signals for derived state
|
|
875
|
-
isAuthenticated = computed(() => !!this.currentUserSignal(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
|
|
876
|
-
userInfo = computed(() => {
|
|
877
|
-
const user = this.currentUserSignal();
|
|
878
|
-
if (!user)
|
|
879
|
-
return null;
|
|
880
|
-
return {
|
|
881
|
-
id: user._id,
|
|
882
|
-
name: user.user_fullname || `${user.user_firstname} ${user.user_lastname}`,
|
|
883
|
-
email: user.user_emailid,
|
|
884
|
-
role: "" // user.user_id_role
|
|
885
|
-
};
|
|
886
|
-
}, ...(ngDevMode ? [{ debugName: "userInfo" }] : []));
|
|
887
|
-
// Computed signal for active module info
|
|
888
|
-
activeModuleInfo = computed(() => {
|
|
889
|
-
const module = this.activeModuleSignal();
|
|
890
|
-
if (!module)
|
|
891
|
-
return null;
|
|
892
|
-
return {
|
|
893
|
-
id: module._id,
|
|
894
|
-
title: module.syme_title,
|
|
895
|
-
icon: module.syme_icon,
|
|
896
|
-
path: module.syme_path,
|
|
897
|
-
type: module.syme_type
|
|
898
|
-
};
|
|
899
|
-
}, ...(ngDevMode ? [{ debugName: "activeModuleInfo" }] : []));
|
|
900
|
-
constructor() {
|
|
901
|
-
// Initialize file manager base URL on app startup
|
|
902
|
-
this.fileManagerService.setBaseUrl(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager]));
|
|
903
|
-
this.fileManagerService.setObjectIdEndpoint(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, commonRoutesUrl?.module, commonRoutesUrl?.generateObjectId]));
|
|
904
|
-
// Load initial state from localStorage
|
|
905
|
-
this.loadFromLocalStorage();
|
|
906
|
-
// Save to localStorage whenever state changes
|
|
907
|
-
effect(() => {
|
|
908
|
-
const currentUser = this.currentUserSignal();
|
|
909
|
-
const activeModule = this.activeModuleSignal();
|
|
910
|
-
const activeEntity = this.activeEntitySignal();
|
|
911
|
-
this.saveToLocalStorage({
|
|
912
|
-
user: currentUser,
|
|
913
|
-
activeModule: activeModule,
|
|
914
|
-
activeEntity: activeEntity
|
|
915
|
-
});
|
|
916
|
-
});
|
|
917
|
-
// Automatically update file manager user ID whenever user changes
|
|
918
|
-
effect(() => {
|
|
919
|
-
const currentUser = this.currentUserSignal();
|
|
920
|
-
console.log('🔍 [AppStateService] User changed, updating file manager user ID:', currentUser);
|
|
921
|
-
if (currentUser?._id) {
|
|
922
|
-
this.fileManagerService.setUserId(currentUser._id);
|
|
923
|
-
}
|
|
924
|
-
else {
|
|
925
|
-
this.fileManagerService.setUserId('');
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
// GLOBAL ENTITY CHANGE HANDLER: When entity changes, close all tabs and navigate to home
|
|
929
|
-
// This ensures all components are destroyed and data is refreshed for the new entity
|
|
930
|
-
// No need to inject this service in any component - it works globally
|
|
931
|
-
let previousEntityId = null;
|
|
932
|
-
effect(() => {
|
|
933
|
-
const currentEntity = this.activeEntitySignal();
|
|
934
|
-
const currentEntityId = currentEntity?._id || null;
|
|
935
|
-
// Mark handler as initialized after first run (to skip initial load from localStorage)
|
|
936
|
-
if (!this.entityChangeHandlerInitialized) {
|
|
937
|
-
previousEntityId = currentEntityId;
|
|
938
|
-
this.entityChangeHandlerInitialized = true;
|
|
939
|
-
console.log('🏢 [AppStateService] Entity change handler initialized with initial entity:', currentEntityId);
|
|
940
|
-
return; // Skip on initial load
|
|
941
|
-
}
|
|
942
|
-
// Only trigger if entity actually changed (not on initial load)
|
|
943
|
-
if (previousEntityId !== null && currentEntityId !== previousEntityId && currentEntityId !== null) {
|
|
944
|
-
console.log('🏢 [AppStateService] Entity changed - closing all tabs and navigating to home', {
|
|
945
|
-
previousEntityId,
|
|
946
|
-
newEntityId: currentEntityId,
|
|
947
|
-
newEntityName: currentEntity?.syen_name
|
|
948
|
-
});
|
|
949
|
-
// Close all tabs - this will destroy all components and navigate to home
|
|
950
|
-
this.requestService.closeAllTabs();
|
|
951
|
-
}
|
|
952
|
-
else if (previousEntityId !== null && currentEntityId === null) {
|
|
953
|
-
// Entity was cleared - also close tabs
|
|
954
|
-
console.log('🏢 [AppStateService] Entity cleared - closing all tabs');
|
|
955
|
-
this.requestService.closeAllTabs();
|
|
956
|
-
}
|
|
957
|
-
// Update previous entity ID for next comparison
|
|
958
|
-
previousEntityId = currentEntityId;
|
|
959
|
-
});
|
|
960
|
-
// Listen for localStorage changes from other tabs/windows
|
|
961
|
-
this.setupStorageListener();
|
|
632
|
+
/**
|
|
633
|
+
* Get user info object
|
|
634
|
+
*/
|
|
635
|
+
getUserInfo() {
|
|
636
|
+
return this.appStateService.userInfo();
|
|
962
637
|
}
|
|
963
638
|
/**
|
|
964
|
-
*
|
|
639
|
+
* Get user ID
|
|
965
640
|
*/
|
|
966
|
-
|
|
967
|
-
this.
|
|
968
|
-
|
|
641
|
+
getUserId() {
|
|
642
|
+
const user = this.getCurrentUser();
|
|
643
|
+
return user?._id || null;
|
|
969
644
|
}
|
|
970
645
|
/**
|
|
971
|
-
*
|
|
646
|
+
* Get user name
|
|
972
647
|
*/
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
this.currentUserSignal.set({ ...currentUser, ...updates });
|
|
977
|
-
}
|
|
648
|
+
getUserName() {
|
|
649
|
+
const userInfo = this.getUserInfo();
|
|
650
|
+
return userInfo?.name || null;
|
|
978
651
|
}
|
|
979
652
|
/**
|
|
980
|
-
*
|
|
653
|
+
* Get user email
|
|
981
654
|
*/
|
|
982
|
-
|
|
983
|
-
this.
|
|
655
|
+
getUserEmail() {
|
|
656
|
+
const userInfo = this.getUserInfo();
|
|
657
|
+
return userInfo?.email || null;
|
|
984
658
|
}
|
|
985
659
|
/**
|
|
986
|
-
* Get
|
|
660
|
+
* Get user username
|
|
987
661
|
*/
|
|
988
|
-
|
|
989
|
-
|
|
662
|
+
getUserUsername() {
|
|
663
|
+
const user = this.getCurrentUser();
|
|
664
|
+
return user?.user_username || null;
|
|
990
665
|
}
|
|
991
666
|
/**
|
|
992
|
-
*
|
|
667
|
+
* Get user full name
|
|
993
668
|
*/
|
|
994
|
-
|
|
995
|
-
this.
|
|
669
|
+
getUserFullName() {
|
|
670
|
+
const user = this.getCurrentUser();
|
|
671
|
+
return user?.user_fullname || null;
|
|
996
672
|
}
|
|
997
673
|
/**
|
|
998
|
-
* Get
|
|
674
|
+
* Get user first name
|
|
999
675
|
*/
|
|
1000
|
-
|
|
1001
|
-
|
|
676
|
+
getUserFirstName() {
|
|
677
|
+
const user = this.getCurrentUser();
|
|
678
|
+
return user?.user_firstname || null;
|
|
1002
679
|
}
|
|
1003
680
|
/**
|
|
1004
|
-
*
|
|
681
|
+
* Get user last name
|
|
1005
682
|
*/
|
|
1006
|
-
|
|
1007
|
-
this.
|
|
683
|
+
getUserLastName() {
|
|
684
|
+
const user = this.getCurrentUser();
|
|
685
|
+
return user?.user_lastname || null;
|
|
1008
686
|
}
|
|
1009
687
|
/**
|
|
1010
|
-
* Get
|
|
688
|
+
* Get user mobile number
|
|
1011
689
|
*/
|
|
1012
|
-
|
|
1013
|
-
|
|
690
|
+
getUserMobile() {
|
|
691
|
+
const user = this.getCurrentUser();
|
|
692
|
+
return user?.user_mobileno || null;
|
|
1014
693
|
}
|
|
1015
694
|
/**
|
|
1016
|
-
*
|
|
1017
|
-
* Call this method when you need to sync with external localStorage changes
|
|
695
|
+
* Set the active module
|
|
1018
696
|
*/
|
|
1019
|
-
|
|
1020
|
-
this.
|
|
1021
|
-
// Note: File manager user ID is automatically updated via effect() when user signal changes
|
|
697
|
+
setActiveModule(module) {
|
|
698
|
+
this.appStateService.setActiveModule(module);
|
|
1022
699
|
}
|
|
1023
700
|
/**
|
|
1024
|
-
*
|
|
701
|
+
* Get active module value (non-reactive)
|
|
1025
702
|
*/
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
return typeof window !== 'undefined' && typeof localStorage !== 'undefined';
|
|
1029
|
-
}
|
|
1030
|
-
catch {
|
|
1031
|
-
return false;
|
|
1032
|
-
}
|
|
703
|
+
getActiveModule() {
|
|
704
|
+
return this.appStateService.getActiveModuleValue();
|
|
1033
705
|
}
|
|
1034
706
|
/**
|
|
1035
|
-
*
|
|
1036
|
-
*/
|
|
1037
|
-
saveToLocalStorage(data) {
|
|
1038
|
-
if (!this.isLocalStorageAvailable()) {
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
try {
|
|
1042
|
-
if (data.user !== undefined) {
|
|
1043
|
-
if (data.user) {
|
|
1044
|
-
localStorage.setItem('cide_auth_user', JSON.stringify(data.user));
|
|
1045
|
-
}
|
|
1046
|
-
else {
|
|
1047
|
-
localStorage.removeItem('cide_auth_user');
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (data.activeModule !== undefined) {
|
|
1051
|
-
if (data.activeModule) {
|
|
1052
|
-
localStorage.setItem('cide_active_module', JSON.stringify(data.activeModule));
|
|
1053
|
-
}
|
|
1054
|
-
else {
|
|
1055
|
-
localStorage.removeItem('cide_active_module');
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
if (data.activeEntity !== undefined) {
|
|
1059
|
-
if (data.activeEntity) {
|
|
1060
|
-
localStorage.setItem('cide_active_entity', JSON.stringify(data.activeEntity));
|
|
1061
|
-
}
|
|
1062
|
-
else {
|
|
1063
|
-
localStorage.removeItem('cide_active_entity');
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
catch (error) {
|
|
1068
|
-
console.error('Error saving to localStorage:', error);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
/**
|
|
1072
|
-
* Load state from localStorage
|
|
1073
|
-
*/
|
|
1074
|
-
loadFromLocalStorage() {
|
|
1075
|
-
if (!this.isLocalStorageAvailable()) {
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
try {
|
|
1079
|
-
const userData = localStorage.getItem('cide_auth_user');
|
|
1080
|
-
if (userData) {
|
|
1081
|
-
const user = JSON.parse(userData);
|
|
1082
|
-
this.currentUserSignal.set(user);
|
|
1083
|
-
}
|
|
1084
|
-
const moduleData = localStorage.getItem('cide_active_module');
|
|
1085
|
-
if (moduleData) {
|
|
1086
|
-
const module = JSON.parse(moduleData);
|
|
1087
|
-
this.activeModuleSignal.set(module);
|
|
1088
|
-
}
|
|
1089
|
-
const entityData = localStorage.getItem('cide_active_entity');
|
|
1090
|
-
if (entityData) {
|
|
1091
|
-
const entity = JSON.parse(entityData);
|
|
1092
|
-
this.activeEntitySignal.set(entity);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
catch (error) {
|
|
1096
|
-
console.error('Error loading from localStorage:', error);
|
|
1097
|
-
// Clear corrupted data
|
|
1098
|
-
if (this.isLocalStorageAvailable()) {
|
|
1099
|
-
localStorage.removeItem('cide_auth_user');
|
|
1100
|
-
localStorage.removeItem('cide_active_module');
|
|
1101
|
-
localStorage.removeItem('cide_active_entity');
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
/**
|
|
1106
|
-
* Setup storage event listener to handle external localStorage changes
|
|
1107
|
-
*/
|
|
1108
|
-
setupStorageListener() {
|
|
1109
|
-
if (!this.isLocalStorageAvailable()) {
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
// Listen for storage events (triggered when localStorage is modified from other tabs/windows)
|
|
1113
|
-
window.addEventListener('storage', (event) => {
|
|
1114
|
-
if (!event.key)
|
|
1115
|
-
return;
|
|
1116
|
-
try {
|
|
1117
|
-
// Handle user data changes
|
|
1118
|
-
if (event.key === 'cide_auth_user') {
|
|
1119
|
-
if (event.newValue) {
|
|
1120
|
-
const user = JSON.parse(event.newValue);
|
|
1121
|
-
this.currentUserSignal.set(user);
|
|
1122
|
-
}
|
|
1123
|
-
else {
|
|
1124
|
-
this.currentUserSignal.set(null);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
// Handle active module changes
|
|
1128
|
-
if (event.key === 'cide_active_module') {
|
|
1129
|
-
if (event.newValue) {
|
|
1130
|
-
const module = JSON.parse(event.newValue);
|
|
1131
|
-
this.activeModuleSignal.set(module);
|
|
1132
|
-
}
|
|
1133
|
-
else {
|
|
1134
|
-
this.activeModuleSignal.set(null);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
// Handle active entity changes
|
|
1138
|
-
if (event.key === 'cide_active_entity') {
|
|
1139
|
-
if (event.newValue) {
|
|
1140
|
-
const entity = JSON.parse(event.newValue);
|
|
1141
|
-
this.activeEntitySignal.set(entity);
|
|
1142
|
-
}
|
|
1143
|
-
else {
|
|
1144
|
-
this.activeEntitySignal.set(null);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
catch (error) {
|
|
1149
|
-
console.error('Error parsing localStorage update:', error);
|
|
1150
|
-
}
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1154
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, providedIn: 'root' });
|
|
1155
|
-
}
|
|
1156
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, decorators: [{
|
|
1157
|
-
type: Injectable,
|
|
1158
|
-
args: [{
|
|
1159
|
-
providedIn: 'root'
|
|
1160
|
-
}]
|
|
1161
|
-
}], ctorParameters: () => [] });
|
|
1162
|
-
|
|
1163
|
-
class AppStateHelperService {
|
|
1164
|
-
appStateService = inject(AppStateService);
|
|
1165
|
-
sidebarService = inject(CideLytSidebarService);
|
|
1166
|
-
// Computed signals for derived state
|
|
1167
|
-
currentUser = this.appStateService.currentUser;
|
|
1168
|
-
isAuthenticated = this.appStateService.isAuthenticated;
|
|
1169
|
-
userInfo = this.appStateService.userInfo;
|
|
1170
|
-
activeModule = this.appStateService.activeModule;
|
|
1171
|
-
activeModuleInfo = this.appStateService.activeModuleInfo;
|
|
1172
|
-
activeEntity = this.appStateService.activeEntity;
|
|
1173
|
-
/**
|
|
1174
|
-
* Get current user value (non-reactive)
|
|
1175
|
-
*/
|
|
1176
|
-
getCurrentUser() {
|
|
1177
|
-
return this.appStateService.getUserValue();
|
|
1178
|
-
}
|
|
1179
|
-
/**
|
|
1180
|
-
* Set user data
|
|
1181
|
-
*/
|
|
1182
|
-
setUser(user) {
|
|
1183
|
-
this.appStateService.setUser(user);
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Update user data (partial update)
|
|
1187
|
-
*/
|
|
1188
|
-
updateUser(updates) {
|
|
1189
|
-
this.appStateService.updateUser(updates);
|
|
1190
|
-
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Clear user data
|
|
1193
|
-
*/
|
|
1194
|
-
clearUser() {
|
|
1195
|
-
this.appStateService.clearUser();
|
|
1196
|
-
}
|
|
1197
|
-
/**
|
|
1198
|
-
* Check if user is authenticated
|
|
1199
|
-
*/
|
|
1200
|
-
isUserAuthenticated() {
|
|
1201
|
-
return this.appStateService.isAuthenticated();
|
|
1202
|
-
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Get user info object
|
|
1205
|
-
*/
|
|
1206
|
-
getUserInfo() {
|
|
1207
|
-
return this.appStateService.userInfo();
|
|
1208
|
-
}
|
|
1209
|
-
/**
|
|
1210
|
-
* Get user ID
|
|
1211
|
-
*/
|
|
1212
|
-
getUserId() {
|
|
1213
|
-
const user = this.getCurrentUser();
|
|
1214
|
-
return user?._id || null;
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Get user name
|
|
1218
|
-
*/
|
|
1219
|
-
getUserName() {
|
|
1220
|
-
const userInfo = this.getUserInfo();
|
|
1221
|
-
return userInfo?.name || null;
|
|
1222
|
-
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Get user email
|
|
1225
|
-
*/
|
|
1226
|
-
getUserEmail() {
|
|
1227
|
-
const userInfo = this.getUserInfo();
|
|
1228
|
-
return userInfo?.email || null;
|
|
1229
|
-
}
|
|
1230
|
-
/**
|
|
1231
|
-
* Get user username
|
|
1232
|
-
*/
|
|
1233
|
-
getUserUsername() {
|
|
1234
|
-
const user = this.getCurrentUser();
|
|
1235
|
-
return user?.user_username || null;
|
|
1236
|
-
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Get user full name
|
|
1239
|
-
*/
|
|
1240
|
-
getUserFullName() {
|
|
1241
|
-
const user = this.getCurrentUser();
|
|
1242
|
-
return user?.user_fullname || null;
|
|
1243
|
-
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Get user first name
|
|
1246
|
-
*/
|
|
1247
|
-
getUserFirstName() {
|
|
1248
|
-
const user = this.getCurrentUser();
|
|
1249
|
-
return user?.user_firstname || null;
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Get user last name
|
|
1253
|
-
*/
|
|
1254
|
-
getUserLastName() {
|
|
1255
|
-
const user = this.getCurrentUser();
|
|
1256
|
-
return user?.user_lastname || null;
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Get user mobile number
|
|
1260
|
-
*/
|
|
1261
|
-
getUserMobile() {
|
|
1262
|
-
const user = this.getCurrentUser();
|
|
1263
|
-
return user?.user_mobileno || null;
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Set the active module
|
|
1267
|
-
*/
|
|
1268
|
-
setActiveModule(module) {
|
|
1269
|
-
this.appStateService.setActiveModule(module);
|
|
1270
|
-
}
|
|
1271
|
-
/**
|
|
1272
|
-
* Get active module value (non-reactive)
|
|
1273
|
-
*/
|
|
1274
|
-
getActiveModule() {
|
|
1275
|
-
return this.appStateService.getActiveModuleValue();
|
|
1276
|
-
}
|
|
1277
|
-
/**
|
|
1278
|
-
* Get active module info
|
|
707
|
+
* Get active module info
|
|
1279
708
|
*/
|
|
1280
709
|
getActiveModuleInfo() {
|
|
1281
710
|
return this.appStateService.activeModuleInfo();
|
|
@@ -1924,7 +1353,7 @@ class CideLytFloatingEntitySelectionService {
|
|
|
1924
1353
|
}
|
|
1925
1354
|
try {
|
|
1926
1355
|
// Use relative import to avoid circular dependency
|
|
1927
|
-
const module = await import('./cloud-ide-layout-floating-entity-selection.component-
|
|
1356
|
+
const module = await import('./cloud-ide-layout-floating-entity-selection.component-DteJtUqk.mjs');
|
|
1928
1357
|
if (module.CideLytFloatingEntitySelectionComponent) {
|
|
1929
1358
|
this.containerService.registerComponent('entity-selection-header', module.CideLytFloatingEntitySelectionComponent);
|
|
1930
1359
|
console.log('✅ Entity selection component registered successfully');
|
|
@@ -4830,6 +4259,618 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
4830
4259
|
args: ['document:click', ['$event']]
|
|
4831
4260
|
}] } });
|
|
4832
4261
|
|
|
4262
|
+
/**
|
|
4263
|
+
* A custom route reuse strategy that allows storing and retrieving routes
|
|
4264
|
+
* to persist component state, especially for tabbed navigation.
|
|
4265
|
+
*/
|
|
4266
|
+
class CustomRouteReuseStrategy {
|
|
4267
|
+
storedRoutes = {};
|
|
4268
|
+
// Generates a unique key for a route.
|
|
4269
|
+
// For routes marked with 'reuseTab', this key includes sorted query parameters
|
|
4270
|
+
// to ensure distinct states for tabs differing by query params.
|
|
4271
|
+
getPathKey(route) {
|
|
4272
|
+
let path = route.pathFromRoot
|
|
4273
|
+
.map(r => r.url.map(segment => segment.path).join('/'))
|
|
4274
|
+
.filter(p => p.length > 0)
|
|
4275
|
+
.join('/');
|
|
4276
|
+
// If the route is marked for tab reuse and has query parameters,
|
|
4277
|
+
// append them to the key in a sorted, consistent manner.
|
|
4278
|
+
if (route.data && route.data['reuseTab'] && route.queryParamMap.keys.length > 0) {
|
|
4279
|
+
const queryParams = route.queryParamMap.keys
|
|
4280
|
+
.sort() // Ensure consistent order of query parameters
|
|
4281
|
+
.map(key => `${key}=${route.queryParamMap.get(key)}`)
|
|
4282
|
+
.join('&');
|
|
4283
|
+
path += '?' + queryParams;
|
|
4284
|
+
}
|
|
4285
|
+
return path;
|
|
4286
|
+
}
|
|
4287
|
+
// Determines if this route (and its subtree) should be detached to be reused later.
|
|
4288
|
+
shouldDetach(route) {
|
|
4289
|
+
return !!(route.data && route.data['reuseTab']);
|
|
4290
|
+
}
|
|
4291
|
+
// Stores the detached route.
|
|
4292
|
+
store(route, handle) {
|
|
4293
|
+
const pathKey = this.getPathKey(route);
|
|
4294
|
+
if (handle && route.data && route.data['reuseTab'] && pathKey) {
|
|
4295
|
+
this.storedRoutes[pathKey] = handle;
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
// Determines if this route (and its subtree) should be reattached.
|
|
4299
|
+
shouldAttach(route) {
|
|
4300
|
+
const pathKey = this.getPathKey(route);
|
|
4301
|
+
return !!pathKey && !!this.storedRoutes[pathKey];
|
|
4302
|
+
}
|
|
4303
|
+
// Retrieves the previously stored route.
|
|
4304
|
+
retrieve(route) {
|
|
4305
|
+
const pathKey = this.getPathKey(route);
|
|
4306
|
+
if (!pathKey || !this.storedRoutes[pathKey]) {
|
|
4307
|
+
return null;
|
|
4308
|
+
}
|
|
4309
|
+
const handle = this.storedRoutes[pathKey];
|
|
4310
|
+
// Verify the handle is still valid (not destroyed)
|
|
4311
|
+
// If the component was destroyed, remove it from stored routes
|
|
4312
|
+
if (handle && typeof handle === 'object') {
|
|
4313
|
+
const handleAny = handle;
|
|
4314
|
+
// Check if ComponentRef is destroyed
|
|
4315
|
+
if (handleAny.destroyed === true || (handleAny.hostView && handleAny.hostView.destroyed)) {
|
|
4316
|
+
console.log(`⚠️ Stored route handle for ${pathKey} is already destroyed, removing from cache`);
|
|
4317
|
+
delete this.storedRoutes[pathKey];
|
|
4318
|
+
return null;
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
return handle;
|
|
4322
|
+
}
|
|
4323
|
+
// Determines if a route should be reused.
|
|
4324
|
+
shouldReuseRoute(future, curr) {
|
|
4325
|
+
return future.routeConfig === curr.routeConfig;
|
|
4326
|
+
}
|
|
4327
|
+
// Clears a stored route, typically when a tab is closed.
|
|
4328
|
+
// This method now properly destroys the component instance to prevent memory leaks
|
|
4329
|
+
// and ensure old data doesn't persist when reopening the same route.
|
|
4330
|
+
clearStoredRoute(pathKey) {
|
|
4331
|
+
const handle = this.storedRoutes[pathKey];
|
|
4332
|
+
if (handle) {
|
|
4333
|
+
// Properly destroy the component instance to prevent memory leaks
|
|
4334
|
+
// and ensure clean state when the route is reopened
|
|
4335
|
+
try {
|
|
4336
|
+
// In Angular, DetachedRouteHandle is typically a ComponentRef
|
|
4337
|
+
// We need to destroy it to remove the component from DOM and clean up resources
|
|
4338
|
+
// Method 1: Check if handle is a ComponentRef directly
|
|
4339
|
+
if (handle && typeof handle.destroy === 'function') {
|
|
4340
|
+
const componentRef = handle;
|
|
4341
|
+
// Verify it has hostView (ComponentRef signature)
|
|
4342
|
+
if (componentRef.hostView || componentRef.location) {
|
|
4343
|
+
console.log(`🗑️ Destroying component instance (ComponentRef) for route: ${pathKey}`);
|
|
4344
|
+
componentRef.destroy();
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
// Method 2: Check if handle is a ViewContainerRef
|
|
4348
|
+
if (handle && typeof handle.clear === 'function' && typeof handle.length === 'number') {
|
|
4349
|
+
const viewContainerRef = handle;
|
|
4350
|
+
console.log(`🗑️ Clearing view container for route: ${pathKey}`);
|
|
4351
|
+
viewContainerRef.clear();
|
|
4352
|
+
}
|
|
4353
|
+
// Method 3: Check if handle is an object with nested ComponentRef
|
|
4354
|
+
if (handle && typeof handle === 'object' && !(handle instanceof ComponentRef) && !(handle instanceof ViewContainerRef)) {
|
|
4355
|
+
const handleObj = handle;
|
|
4356
|
+
// Try to find and destroy ComponentRef from various possible properties
|
|
4357
|
+
let componentRef = null;
|
|
4358
|
+
// Check common property names
|
|
4359
|
+
if (handleObj.componentRef && typeof handleObj.componentRef.destroy === 'function') {
|
|
4360
|
+
componentRef = handleObj.componentRef;
|
|
4361
|
+
}
|
|
4362
|
+
else if (handleObj.component && typeof handleObj.component.destroy === 'function') {
|
|
4363
|
+
componentRef = handleObj.component;
|
|
4364
|
+
}
|
|
4365
|
+
else if (handleObj.context && typeof handleObj.context.destroy === 'function') {
|
|
4366
|
+
componentRef = handleObj.context;
|
|
4367
|
+
}
|
|
4368
|
+
else if (handleObj._componentRef && typeof handleObj._componentRef.destroy === 'function') {
|
|
4369
|
+
componentRef = handleObj._componentRef;
|
|
4370
|
+
}
|
|
4371
|
+
if (componentRef) {
|
|
4372
|
+
console.log(`🗑️ Destroying component instance from handle object for route: ${pathKey}`);
|
|
4373
|
+
componentRef.destroy();
|
|
4374
|
+
}
|
|
4375
|
+
// Also try to clear view container if it exists
|
|
4376
|
+
if (handleObj.viewContainerRef && typeof handleObj.viewContainerRef.clear === 'function') {
|
|
4377
|
+
console.log(`🗑️ Clearing view container from handle object for route: ${pathKey}`);
|
|
4378
|
+
handleObj.viewContainerRef.clear();
|
|
4379
|
+
}
|
|
4380
|
+
// Try to destroy any nested components
|
|
4381
|
+
if (handleObj.components && Array.isArray(handleObj.components)) {
|
|
4382
|
+
handleObj.components.forEach((comp, index) => {
|
|
4383
|
+
if (comp && typeof comp.destroy === 'function') {
|
|
4384
|
+
console.log(`🗑️ Destroying nested component ${index} for route: ${pathKey}`);
|
|
4385
|
+
comp.destroy();
|
|
4386
|
+
}
|
|
4387
|
+
});
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
catch (error) {
|
|
4392
|
+
console.warn(`⚠️ Error destroying route handle for ${pathKey}:`, error);
|
|
4393
|
+
}
|
|
4394
|
+
// Remove from stored routes - this ensures shouldAttach will return false for this route
|
|
4395
|
+
delete this.storedRoutes[pathKey];
|
|
4396
|
+
console.log(`✅ Cleared stored route: ${pathKey} (removed from storedRoutes map)`);
|
|
4397
|
+
}
|
|
4398
|
+
else {
|
|
4399
|
+
console.log(`ℹ️ No stored route found for: ${pathKey}`);
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
// Clears all stored routes (useful for cleanup)
|
|
4403
|
+
clearAllStoredRoutes() {
|
|
4404
|
+
Object.keys(this.storedRoutes).forEach(pathKey => {
|
|
4405
|
+
this.clearStoredRoute(pathKey);
|
|
4406
|
+
});
|
|
4407
|
+
}
|
|
4408
|
+
// Gets the count of stored routes (useful for debugging)
|
|
4409
|
+
getStoredRoutesCount() {
|
|
4410
|
+
return Object.keys(this.storedRoutes).length;
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
/**
|
|
4415
|
+
* Creates the default state for a new tab.
|
|
4416
|
+
*/
|
|
4417
|
+
const createDefaultComponentState = () => ({
|
|
4418
|
+
sideDrawer: { isOpen: false, activeComponent: null, componentState: {} },
|
|
4419
|
+
footer: { someData: '' },
|
|
4420
|
+
console: { history: [] },
|
|
4421
|
+
});
|
|
4422
|
+
class TabStateService {
|
|
4423
|
+
// Holds the state for all open tabs, keyed by tab ID.
|
|
4424
|
+
_tabsState = new BehaviorSubject(new Map());
|
|
4425
|
+
// Holds the ID of the currently active tab.
|
|
4426
|
+
_activeTabId = new BehaviorSubject(null);
|
|
4427
|
+
/** An observable of all open tabs. */
|
|
4428
|
+
tabs$ = this._tabsState.pipe(map(tabsMap => Array.from(tabsMap.values())));
|
|
4429
|
+
/** An observable of the currently active tab's ID. */
|
|
4430
|
+
activeTabId$ = this._activeTabId.asObservable().pipe(distinctUntilChanged());
|
|
4431
|
+
/** An observable of the state of the currently active tab. */
|
|
4432
|
+
activeTabState$ = this.activeTabId$.pipe(map(activeId => (activeId ? this._tabsState.getValue().get(activeId) ?? null : null)),
|
|
4433
|
+
// Use a deep-ish comparison to prevent emissions when only the object reference changes
|
|
4434
|
+
distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)));
|
|
4435
|
+
/** An observable of just the component-specific state for the active tab. */
|
|
4436
|
+
activeComponentState$ = this.activeTabState$.pipe(map(activeTab => activeTab?.componentState ?? null));
|
|
4437
|
+
/**
|
|
4438
|
+
* Adds a new tab to the state.
|
|
4439
|
+
* @param title The title for the new tab.
|
|
4440
|
+
* @param id An optional unique ID. If not provided, one will be generated.
|
|
4441
|
+
* @returns The ID of the newly created tab.
|
|
4442
|
+
*/
|
|
4443
|
+
addTab(title, id) {
|
|
4444
|
+
const newTabId = id || `tab_${Date.now()}_${Math.random()}`;
|
|
4445
|
+
const newTab = {
|
|
4446
|
+
id: newTabId,
|
|
4447
|
+
title,
|
|
4448
|
+
componentState: createDefaultComponentState(),
|
|
4449
|
+
};
|
|
4450
|
+
const currentTabs = this._tabsState.getValue();
|
|
4451
|
+
const newTabs = new Map(currentTabs).set(newTabId, newTab);
|
|
4452
|
+
this._tabsState.next(newTabs);
|
|
4453
|
+
this.setActiveTab(newTabId);
|
|
4454
|
+
return newTabId;
|
|
4455
|
+
}
|
|
4456
|
+
/**
|
|
4457
|
+
* Removes a tab from the state.
|
|
4458
|
+
* @param tabIdToRemove The ID of the tab to remove.
|
|
4459
|
+
*/
|
|
4460
|
+
removeTab(tabIdToRemove) {
|
|
4461
|
+
const currentTabs = this._tabsState.getValue();
|
|
4462
|
+
if (!currentTabs.has(tabIdToRemove)) {
|
|
4463
|
+
return;
|
|
4464
|
+
}
|
|
4465
|
+
const newTabs = new Map(currentTabs);
|
|
4466
|
+
newTabs.delete(tabIdToRemove);
|
|
4467
|
+
this._tabsState.next(newTabs);
|
|
4468
|
+
}
|
|
4469
|
+
constructor() {
|
|
4470
|
+
// You can initialize with default tabs if needed
|
|
4471
|
+
// For example:
|
|
4472
|
+
// this.addTab('Welcome Tab');
|
|
4473
|
+
}
|
|
4474
|
+
/**
|
|
4475
|
+
* Sets the currently active tab.
|
|
4476
|
+
* @param tabId The unique identifier of the tab to activate.
|
|
4477
|
+
*/
|
|
4478
|
+
setActiveTab(tabId) {
|
|
4479
|
+
if (this._activeTabId.getValue() !== tabId) {
|
|
4480
|
+
this._activeTabId.next(tabId);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
/**
|
|
4484
|
+
* Updates the state for the currently active tab.
|
|
4485
|
+
* This performs a deep merge to avoid overwriting nested state.
|
|
4486
|
+
* @param partialState The partial state to update for the active tab.
|
|
4487
|
+
*/
|
|
4488
|
+
updateActiveTabState(partialState) {
|
|
4489
|
+
const activeId = this._activeTabId.getValue();
|
|
4490
|
+
if (!activeId) {
|
|
4491
|
+
console.warn('Cannot update state, no active tab.');
|
|
4492
|
+
return;
|
|
4493
|
+
}
|
|
4494
|
+
const tabs = this._tabsState.getValue();
|
|
4495
|
+
const activeTabState = tabs.get(activeId);
|
|
4496
|
+
if (activeTabState) {
|
|
4497
|
+
// Use lodash merge for a deep merge
|
|
4498
|
+
const updatedState = merge({}, activeTabState, { componentState: partialState });
|
|
4499
|
+
const newTabsState = new Map(tabs);
|
|
4500
|
+
newTabsState.set(activeId, updatedState);
|
|
4501
|
+
this._tabsState.next(newTabsState);
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
/**
|
|
4505
|
+
* Gets an observable for a specific tab's state.
|
|
4506
|
+
* @param tabId The ID of the tab to observe.
|
|
4507
|
+
*/
|
|
4508
|
+
getTabState(tabId) {
|
|
4509
|
+
return this._tabsState.pipe(map(tabs => tabs.get(tabId)));
|
|
4510
|
+
}
|
|
4511
|
+
/**
|
|
4512
|
+
* Determines the ID of the next tab to activate when a tab is closed.
|
|
4513
|
+
* @param idToRemove The ID of the tab being removed.
|
|
4514
|
+
* @returns The ID of the next tab to activate, or null if no tabs are left.
|
|
4515
|
+
*/
|
|
4516
|
+
getNextActiveTabId(idToRemove) {
|
|
4517
|
+
const tabsMap = this._tabsState.getValue();
|
|
4518
|
+
if (tabsMap.size <= 1) {
|
|
4519
|
+
return null;
|
|
4520
|
+
}
|
|
4521
|
+
const tabIds = Array.from(tabsMap.keys());
|
|
4522
|
+
const removedIndex = tabIds.indexOf(idToRemove);
|
|
4523
|
+
// If it's the last tab in the list, activate the one before it. Otherwise, activate the next one.
|
|
4524
|
+
const nextIndex = removedIndex === tabIds.length - 1 ? removedIndex - 1 : removedIndex + 1;
|
|
4525
|
+
return tabIds[nextIndex];
|
|
4526
|
+
}
|
|
4527
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4528
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabStateService, providedIn: 'root' });
|
|
4529
|
+
}
|
|
4530
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabStateService, decorators: [{
|
|
4531
|
+
type: Injectable,
|
|
4532
|
+
args: [{
|
|
4533
|
+
providedIn: 'root',
|
|
4534
|
+
}]
|
|
4535
|
+
}], ctorParameters: () => [] });
|
|
4536
|
+
|
|
4537
|
+
class CideLytRequestService {
|
|
4538
|
+
requestVisible = false;
|
|
4539
|
+
// Modern Angular v20+ pattern: Use Signals instead of BehaviorSubject
|
|
4540
|
+
tabsSignal = signal([], ...(ngDevMode ? [{ debugName: "tabsSignal" }] : []));
|
|
4541
|
+
activeTabIdSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeTabIdSignal" }] : []));
|
|
4542
|
+
// Computed signals for derived state and public readonly access
|
|
4543
|
+
tabs = computed(() => this.tabsSignal(), ...(ngDevMode ? [{ debugName: "tabs" }] : []));
|
|
4544
|
+
activeTabId = computed(() => this.activeTabIdSignal(), ...(ngDevMode ? [{ debugName: "activeTabId" }] : []));
|
|
4545
|
+
activeTab = computed(() => this.tabsSignal().find(tab => tab.active), ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
4546
|
+
// Use inject() for all dependencies
|
|
4547
|
+
router = inject(Router);
|
|
4548
|
+
routeReuseStrategy = inject(RouteReuseStrategy);
|
|
4549
|
+
tabStateService = inject(TabStateService);
|
|
4550
|
+
sidedrawerService = inject(CideLytSidedrawerService);
|
|
4551
|
+
sharedService = inject(CideLytSharedService);
|
|
4552
|
+
floatingContainerService = inject(CideEleFloatingContainerService);
|
|
4553
|
+
appStateService = inject(AppStateService);
|
|
4554
|
+
constructor() {
|
|
4555
|
+
// Initialize router
|
|
4556
|
+
this.router = inject(Router);
|
|
4557
|
+
this.router.routeReuseStrategy = new CustomRouteReuseStrategy();
|
|
4558
|
+
// Register tab management callback with shared service to avoid circular dependency
|
|
4559
|
+
this.sharedService.registerTabManagement(this.handleRouteBasedTabManagement.bind(this));
|
|
4560
|
+
// Register request visibility callback
|
|
4561
|
+
this.sharedService.registerRequestVisibility(this.handleRequestVisibility.bind(this));
|
|
4562
|
+
// Register entity change handler with AppStateService to close tabs when entity changes
|
|
4563
|
+
// This is safe because AppStateService no longer imports CideLytRequestService
|
|
4564
|
+
this.appStateService.registerEntityChangeHandler((entity) => {
|
|
4565
|
+
console.log('🏢 [RequestService] Entity changed, closing all tabs:', entity?._id);
|
|
4566
|
+
this.closeAllTabs();
|
|
4567
|
+
});
|
|
4568
|
+
}
|
|
4569
|
+
/**
|
|
4570
|
+
* Handle request visibility changes from shared service
|
|
4571
|
+
*/
|
|
4572
|
+
handleRequestVisibility(show) {
|
|
4573
|
+
if (show) {
|
|
4574
|
+
this.showRequest();
|
|
4575
|
+
}
|
|
4576
|
+
else {
|
|
4577
|
+
this.hideRequest();
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
/**
|
|
4581
|
+
* Handle sidebar sync when tab becomes active
|
|
4582
|
+
*/
|
|
4583
|
+
handleSidebarSync(tabInfo) {
|
|
4584
|
+
// The sidebar sync will be handled automatically by the shared service
|
|
4585
|
+
// when page data is loaded, so we don't need additional logic here
|
|
4586
|
+
console.log('🔗 REQUEST SERVICE: Tab activated, sidebar sync handled by shared service:', tabInfo.title);
|
|
4587
|
+
}
|
|
4588
|
+
/**
|
|
4589
|
+
* Handle route-based tab management - called by shared service
|
|
4590
|
+
* This method checks for existing tabs and creates/activates as needed
|
|
4591
|
+
*/
|
|
4592
|
+
handleRouteBasedTabManagement(currentRoutePath, queryParams, layout, pageData, pageCode) {
|
|
4593
|
+
console.log('handleRouteBasedTabManagement', currentRoutePath, queryParams, layout, pageData, pageCode);
|
|
4594
|
+
const currentTabs = this.tabsSignal();
|
|
4595
|
+
const tabAlreadyExists = currentTabs.find((t) => {
|
|
4596
|
+
const tRoutePath = t.route.startsWith('/') ? t.route : '/' + t.route;
|
|
4597
|
+
const tParamsMatch = JSON.stringify(t.params || {}) === JSON.stringify(queryParams || {});
|
|
4598
|
+
return tRoutePath === currentRoutePath && tParamsMatch;
|
|
4599
|
+
});
|
|
4600
|
+
console.log('tabAlreadyExists', tabAlreadyExists);
|
|
4601
|
+
if (!tabAlreadyExists) {
|
|
4602
|
+
const title = pageData.data?.page?.sypg_title || pageCode.toString() || 'Untitled Tab';
|
|
4603
|
+
console.log(`🆕 Adding new tab: ${title}`);
|
|
4604
|
+
this.addTab(title, currentRoutePath, queryParams, layout);
|
|
4605
|
+
}
|
|
4606
|
+
else if (tabAlreadyExists && !tabAlreadyExists.active) {
|
|
4607
|
+
console.log(`🔄 Activating existing tab: ${tabAlreadyExists.title}`);
|
|
4608
|
+
this.activateTab(tabAlreadyExists.id);
|
|
4609
|
+
}
|
|
4610
|
+
else if (tabAlreadyExists && tabAlreadyExists.active) {
|
|
4611
|
+
console.log(`🔄 Activating existing tab: ${tabAlreadyExists.title}`);
|
|
4612
|
+
this.activateTab(tabAlreadyExists.id);
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
addTab(title, route, params, layout) {
|
|
4616
|
+
console.log('addTab', title, route, params, layout);
|
|
4617
|
+
const id = this.generateId();
|
|
4618
|
+
const currentTabs = this.tabsSignal();
|
|
4619
|
+
console.log('currentTabs', currentTabs, route);
|
|
4620
|
+
// Tab reuse disabled - always create new tab
|
|
4621
|
+
// Removed existing tab check to prevent tab reuse
|
|
4622
|
+
// Create new tab
|
|
4623
|
+
const newTab = {
|
|
4624
|
+
id,
|
|
4625
|
+
title,
|
|
4626
|
+
route,
|
|
4627
|
+
params,
|
|
4628
|
+
active: true,
|
|
4629
|
+
layout,
|
|
4630
|
+
sytm_page_id_sypg: '',
|
|
4631
|
+
themeId: ''
|
|
4632
|
+
};
|
|
4633
|
+
// Deactivate all other tabs
|
|
4634
|
+
currentTabs.forEach((tab) => tab.active = false);
|
|
4635
|
+
console.log('currentTabs after deactivate', currentTabs, 'activateTab', id);
|
|
4636
|
+
// Add new tab
|
|
4637
|
+
this.tabsSignal.set([...currentTabs, newTab]);
|
|
4638
|
+
this.activeTabIdSignal.set(id);
|
|
4639
|
+
// Sync with TabStateService
|
|
4640
|
+
this.tabStateService.addTab(title, id);
|
|
4641
|
+
// Request visibility is now controlled by layout configuration in setPageData
|
|
4642
|
+
// No need to automatically show request wrapper here
|
|
4643
|
+
// Navigate to the new route
|
|
4644
|
+
if (params) {
|
|
4645
|
+
this.router.navigate([route], { queryParams: params });
|
|
4646
|
+
}
|
|
4647
|
+
else {
|
|
4648
|
+
this.router.navigate([route]);
|
|
4649
|
+
}
|
|
4650
|
+
return id;
|
|
4651
|
+
}
|
|
4652
|
+
activateTab(tabId) {
|
|
4653
|
+
console.log('🔍 REQUEST SERVICE: Activating tab:', tabId);
|
|
4654
|
+
const tabs = this.tabsSignal();
|
|
4655
|
+
const tabToActivate = tabs.find(tab => tab.id === tabId);
|
|
4656
|
+
if (!tabToActivate) {
|
|
4657
|
+
console.warn('⚠️ Tab not found:', tabId);
|
|
4658
|
+
return;
|
|
4659
|
+
}
|
|
4660
|
+
// Before navigating, ensure the route is fresh by clearing any cached state
|
|
4661
|
+
// This prevents old component data from persisting when reopening a tab
|
|
4662
|
+
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
4663
|
+
let pathKey = tabToActivate.route.startsWith('/') ? tabToActivate.route.substring(1) : tabToActivate.route;
|
|
4664
|
+
if (tabToActivate.params && Object.keys(tabToActivate.params).length > 0) {
|
|
4665
|
+
const queryParamsString = Object.keys(tabToActivate.params)
|
|
4666
|
+
.sort()
|
|
4667
|
+
.map(key => `${key}=${tabToActivate.params[key]}`)
|
|
4668
|
+
.join('&');
|
|
4669
|
+
pathKey += '?' + queryParamsString;
|
|
4670
|
+
}
|
|
4671
|
+
// Clear any cached route to ensure fresh component state
|
|
4672
|
+
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
4673
|
+
console.log(`🔄 REQUEST SERVICE: Cleared cached route ${pathKey} before activating tab`);
|
|
4674
|
+
}
|
|
4675
|
+
// Update tabs: deactivate all, then activate the target
|
|
4676
|
+
const updatedTabs = tabs.map(tab => ({
|
|
4677
|
+
...tab,
|
|
4678
|
+
active: tab.id === tabId
|
|
4679
|
+
}));
|
|
4680
|
+
this.tabsSignal.set(updatedTabs);
|
|
4681
|
+
this.activeTabIdSignal.set(tabId);
|
|
4682
|
+
console.log('✅ REQUEST SERVICE: Tab activated:', tabToActivate.title);
|
|
4683
|
+
// Navigate to the tab's route
|
|
4684
|
+
this.router.navigate([tabToActivate.route], {
|
|
4685
|
+
queryParams: tabToActivate.params || {},
|
|
4686
|
+
replaceUrl: false
|
|
4687
|
+
});
|
|
4688
|
+
// Trigger sidebar sync for the activated tab by calling shared service
|
|
4689
|
+
// The shared service will handle the sidebar sync when the route loads
|
|
4690
|
+
console.log('🔗 REQUEST SERVICE: Sidebar sync will be handled by route change');
|
|
4691
|
+
}
|
|
4692
|
+
closeTab(id) {
|
|
4693
|
+
const currentTabs = this.tabsSignal();
|
|
4694
|
+
const tabIndex = currentTabs.findIndex((tab) => tab.id === id);
|
|
4695
|
+
if (tabIndex === -1)
|
|
4696
|
+
return;
|
|
4697
|
+
const tabToClose = currentTabs[tabIndex];
|
|
4698
|
+
const wasActive = tabToClose.active;
|
|
4699
|
+
// Sync with TabStateService by removing the tab state
|
|
4700
|
+
this.tabStateService.removeTab(id);
|
|
4701
|
+
// Clear stored route from strategy and ensure proper component cleanup
|
|
4702
|
+
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
4703
|
+
// Generate pathKey in the same format as CustomRouteReuseStrategy.getPathKey()
|
|
4704
|
+
// Remove leading slash to match the format used by getPathKey
|
|
4705
|
+
let pathKey = tabToClose.route.startsWith('/') ? tabToClose.route.substring(1) : tabToClose.route;
|
|
4706
|
+
// Only add query params if the route is marked for reuse (matching getPathKey logic)
|
|
4707
|
+
// But we'll try to clear with both formats to be safe
|
|
4708
|
+
if (tabToClose.params && Object.keys(tabToClose.params).length > 0) {
|
|
4709
|
+
const queryParamsString = Object.keys(tabToClose.params)
|
|
4710
|
+
.sort()
|
|
4711
|
+
.map(key => `${key}=${tabToClose.params[key]}`)
|
|
4712
|
+
.join('&');
|
|
4713
|
+
pathKey += '?' + queryParamsString;
|
|
4714
|
+
}
|
|
4715
|
+
// Clear the stored route and destroy the component instance
|
|
4716
|
+
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
4717
|
+
// Also try clearing without query params in case the route wasn't marked for reuse
|
|
4718
|
+
// This ensures we catch all possible pathKey variations
|
|
4719
|
+
const pathKeyWithoutParams = tabToClose.route.startsWith('/') ? tabToClose.route.substring(1) : tabToClose.route;
|
|
4720
|
+
if (pathKeyWithoutParams !== pathKey) {
|
|
4721
|
+
this.routeReuseStrategy.clearStoredRoute(pathKeyWithoutParams);
|
|
4722
|
+
}
|
|
4723
|
+
// Force a route refresh to ensure clean component state
|
|
4724
|
+
// This prevents old data from persisting when the same route is reopened
|
|
4725
|
+
console.log(`🧹 REQUEST SERVICE: Cleared stored route for ${pathKey} and destroyed component instance`);
|
|
4726
|
+
// If this was the active tab, navigate away to ensure component is removed from DOM
|
|
4727
|
+
if (wasActive) {
|
|
4728
|
+
// The navigation to the new active tab will happen below, which will properly clear the router outlet
|
|
4729
|
+
// But we also want to ensure the component is removed immediately
|
|
4730
|
+
console.log(`🧹 REQUEST SERVICE: Active tab closed, component will be removed on navigation`);
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
// Remove the tab from this service's list
|
|
4734
|
+
const newTabs = currentTabs.filter((tab) => tab.id !== id);
|
|
4735
|
+
// If we're closing the active tab, activate the next available tab
|
|
4736
|
+
if (wasActive && newTabs.length > 0) {
|
|
4737
|
+
const newActiveIndex = Math.max(0, Math.min(tabIndex, newTabs.length - 1));
|
|
4738
|
+
const newActiveTab = newTabs[newActiveIndex];
|
|
4739
|
+
// Update sidedrawer for the new active tab
|
|
4740
|
+
this.sidedrawerService.updateDrawerItems(newActiveTab.layout);
|
|
4741
|
+
newTabs[newActiveIndex].active = true;
|
|
4742
|
+
this.activeTabIdSignal.set(newActiveTab.id);
|
|
4743
|
+
this.tabStateService.setActiveTab(newActiveTab.id);
|
|
4744
|
+
// Navigate to the new active tab's route
|
|
4745
|
+
if (newActiveTab.params) {
|
|
4746
|
+
this.router.navigate([newActiveTab.route], { queryParams: newActiveTab.params });
|
|
4747
|
+
}
|
|
4748
|
+
else {
|
|
4749
|
+
this.router.navigate([newActiveTab.route]);
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
else if (newTabs.length === 0) {
|
|
4753
|
+
this.activeTabIdSignal.set(null);
|
|
4754
|
+
this.tabStateService.setActiveTab(null);
|
|
4755
|
+
// Clear sidedrawer as no tabs are open
|
|
4756
|
+
this.sidedrawerService.updateDrawerItems(undefined);
|
|
4757
|
+
// Redirect to home screen when all tabs are closed
|
|
4758
|
+
this.router.navigate(['/control-panel']);
|
|
4759
|
+
}
|
|
4760
|
+
this.tabsSignal.set(newTabs);
|
|
4761
|
+
// Request wrapper visibility is now controlled by layout configuration
|
|
4762
|
+
// The wrapper will be hidden/shown based on sytm_layout_request.status in setPageData
|
|
4763
|
+
}
|
|
4764
|
+
/**
|
|
4765
|
+
* Close all tabs and navigate to home
|
|
4766
|
+
* This is used when entity changes to ensure all components are destroyed and refreshed
|
|
4767
|
+
*/
|
|
4768
|
+
closeAllTabs() {
|
|
4769
|
+
console.log('🧹 REQUEST SERVICE: Closing all tabs due to entity change');
|
|
4770
|
+
const currentTabs = this.tabsSignal();
|
|
4771
|
+
// Close all floating containers first
|
|
4772
|
+
this.floatingContainerService.hideAll();
|
|
4773
|
+
console.log('🧹 REQUEST SERVICE: Closed all floating containers');
|
|
4774
|
+
// Clear all tabs from TabStateService
|
|
4775
|
+
currentTabs.forEach(tab => {
|
|
4776
|
+
this.tabStateService.removeTab(tab.id);
|
|
4777
|
+
// Clear stored routes from route reuse strategy
|
|
4778
|
+
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
4779
|
+
let pathKey = tab.route.startsWith('/') ? tab.route.substring(1) : tab.route;
|
|
4780
|
+
if (tab.params && Object.keys(tab.params).length > 0) {
|
|
4781
|
+
const queryParamsString = Object.keys(tab.params)
|
|
4782
|
+
.sort()
|
|
4783
|
+
.map(key => `${key}=${tab.params[key]}`)
|
|
4784
|
+
.join('&');
|
|
4785
|
+
pathKey += '?' + queryParamsString;
|
|
4786
|
+
}
|
|
4787
|
+
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
4788
|
+
// Also clear without query params
|
|
4789
|
+
const pathKeyWithoutParams = tab.route.startsWith('/') ? tab.route.substring(1) : tab.route;
|
|
4790
|
+
if (pathKeyWithoutParams !== pathKey) {
|
|
4791
|
+
this.routeReuseStrategy.clearStoredRoute(pathKeyWithoutParams);
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
});
|
|
4795
|
+
// Clear all tabs
|
|
4796
|
+
this.tabsSignal.set([]);
|
|
4797
|
+
this.activeTabIdSignal.set(null);
|
|
4798
|
+
this.tabStateService.setActiveTab(null);
|
|
4799
|
+
// Clear sidedrawer
|
|
4800
|
+
this.sidedrawerService.updateDrawerItems(undefined);
|
|
4801
|
+
// Navigate to home
|
|
4802
|
+
this.router.navigate(['/control-panel']);
|
|
4803
|
+
console.log('✅ REQUEST SERVICE: All tabs and floating containers closed, navigated to home');
|
|
4804
|
+
}
|
|
4805
|
+
// Hide Request
|
|
4806
|
+
hideRequest() {
|
|
4807
|
+
console.log('🚫 REQUEST SERVICE - Hiding request wrapper');
|
|
4808
|
+
this.requestVisible = false;
|
|
4809
|
+
document.querySelector(`#cide-lyt-request-wrapper`)?.classList.add('cide-lyt-request-wrapper-hide');
|
|
4810
|
+
document.querySelector(`body`)?.classList.remove('cide-lyt-request-exist');
|
|
4811
|
+
}
|
|
4812
|
+
// Show Request
|
|
4813
|
+
showRequest() {
|
|
4814
|
+
console.log('✅ REQUEST SERVICE - Showing request wrapper');
|
|
4815
|
+
this.requestVisible = true;
|
|
4816
|
+
document.querySelector(`#cide-lyt-request-wrapper`)?.classList.remove('cide-lyt-request-wrapper-hide');
|
|
4817
|
+
document.querySelector(`body`)?.classList.add('cide-lyt-request-exist');
|
|
4818
|
+
}
|
|
4819
|
+
generateId() {
|
|
4820
|
+
return 'tab-' + Math.random().toString(36).substr(2, 9);
|
|
4821
|
+
}
|
|
4822
|
+
updateTabScrollPosition(tabId, scrollTop, scrollLeft) {
|
|
4823
|
+
const currentTabs = this.tabsSignal();
|
|
4824
|
+
const tabIndex = currentTabs.findIndex((t) => t.id === tabId);
|
|
4825
|
+
if (tabIndex !== -1) {
|
|
4826
|
+
const tabToUpdate = currentTabs[tabIndex];
|
|
4827
|
+
tabToUpdate.scrollTop = scrollTop;
|
|
4828
|
+
tabToUpdate.scrollLeft = scrollLeft;
|
|
4829
|
+
// Create a new array reference to trigger change detection
|
|
4830
|
+
this.tabsSignal.set([...currentTabs]);
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
/**
|
|
4834
|
+
* Force refresh a route by clearing its cached state
|
|
4835
|
+
* This is useful when you want to ensure a clean component state
|
|
4836
|
+
* @param route The route path to refresh
|
|
4837
|
+
* @param params Optional query parameters
|
|
4838
|
+
*/
|
|
4839
|
+
forceRefreshRoute(route, params) {
|
|
4840
|
+
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
4841
|
+
let pathKey = route.startsWith('/') ? route.substring(1) : route;
|
|
4842
|
+
if (params && Object.keys(params).length > 0) {
|
|
4843
|
+
const queryParamsString = Object.keys(params)
|
|
4844
|
+
.sort()
|
|
4845
|
+
.map(key => `${key}=${params[key]}`)
|
|
4846
|
+
.join('&');
|
|
4847
|
+
pathKey += '?' + queryParamsString;
|
|
4848
|
+
}
|
|
4849
|
+
// Clear the stored route to force a fresh component instance
|
|
4850
|
+
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
4851
|
+
console.log(`🔄 REQUEST SERVICE: Force refreshed route ${pathKey}`);
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
/**
|
|
4855
|
+
* Get information about cached routes (useful for debugging)
|
|
4856
|
+
*/
|
|
4857
|
+
getCachedRoutesInfo() {
|
|
4858
|
+
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
4859
|
+
const count = this.routeReuseStrategy.getStoredRoutesCount();
|
|
4860
|
+
return { count, routes: [] }; // Could be enhanced to return actual route paths
|
|
4861
|
+
}
|
|
4862
|
+
return { count: 0, routes: [] };
|
|
4863
|
+
}
|
|
4864
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4865
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestService, providedIn: 'root' });
|
|
4866
|
+
}
|
|
4867
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestService, decorators: [{
|
|
4868
|
+
type: Injectable,
|
|
4869
|
+
args: [{
|
|
4870
|
+
providedIn: 'root'
|
|
4871
|
+
}]
|
|
4872
|
+
}], ctorParameters: () => [] });
|
|
4873
|
+
|
|
4833
4874
|
// Type guard for setState/getState
|
|
4834
4875
|
function hasStateMethods(instance) {
|
|
4835
4876
|
return !!instance && typeof instance.getState === 'function' && typeof instance.setState === 'function';
|
|
@@ -4913,8 +4954,8 @@ class CideLytSidedrawerWrapperComponent {
|
|
|
4913
4954
|
}
|
|
4914
4955
|
ngOnInit() {
|
|
4915
4956
|
// Initialize the component map (You'd likely populate this from a config or service)
|
|
4916
|
-
this.componentMap['drowar_notes'] = () => import('./cloud-ide-layout-sidedrawer-notes.component-
|
|
4917
|
-
this.componentMap['drawer_theme'] = () => import('./cloud-ide-layout-drawer-theme.component-
|
|
4957
|
+
this.componentMap['drowar_notes'] = () => import('./cloud-ide-layout-sidedrawer-notes.component-C2tOsEC4.mjs').then(m => m.CideLytSidedrawerNotesComponent);
|
|
4958
|
+
this.componentMap['drawer_theme'] = () => import('./cloud-ide-layout-drawer-theme.component-DV0QsDsO.mjs').then(m => m.CideLytDrawerThemeComponent);
|
|
4918
4959
|
}
|
|
4919
4960
|
async loadComponent(configFor) {
|
|
4920
4961
|
console.log('🔍 SIDEDRAWER - Loading component:', configFor, 'Current tab:', this.currentTabId);
|
|
@@ -6581,7 +6622,7 @@ const layoutControlPannelChildRoutes = [{
|
|
|
6581
6622
|
},
|
|
6582
6623
|
{
|
|
6583
6624
|
path: "home",
|
|
6584
|
-
loadComponent: () => import('./cloud-ide-layout-home-wrapper.component-
|
|
6625
|
+
loadComponent: () => import('./cloud-ide-layout-home-wrapper.component-DjRHVU59.mjs').then(c => c.CideLytHomeWrapperComponent),
|
|
6585
6626
|
canActivate: [authGuard],
|
|
6586
6627
|
data: {
|
|
6587
6628
|
sypg_page_code: "cide_lyt_home" // Used by RequestService to fetch tab properties
|
|
@@ -6589,7 +6630,7 @@ const layoutControlPannelChildRoutes = [{
|
|
|
6589
6630
|
},
|
|
6590
6631
|
{
|
|
6591
6632
|
path: "dashboard-manager",
|
|
6592
|
-
loadComponent: () => import('./cloud-ide-layout-dashboard-manager.component-
|
|
6633
|
+
loadComponent: () => import('./cloud-ide-layout-dashboard-manager.component-C8rnVZpP.mjs').then(c => c.DashboardManagerComponent),
|
|
6593
6634
|
canActivate: [authGuard],
|
|
6594
6635
|
data: {
|
|
6595
6636
|
sypg_page_code: "cide_lyt_dashboard_manager"
|
|
@@ -8156,4 +8197,4 @@ var floatingEntityRightsSharing_component = /*#__PURE__*/Object.freeze({
|
|
|
8156
8197
|
*/
|
|
8157
8198
|
|
|
8158
8199
|
export { AppStateHelperService as A, CideLytSharedWrapperComponent as C, ENVIRONMENT_CONFIG as E, NotificationSettingsService as N, RightsService as R, CideLytSidebarService as a, CideLytRequestService as b, CideLytSidedrawerService as c, CideLytThemeService as d, AppStateService as e, CloudIdeLayoutService as f, CloudIdeLayoutComponent as g, CideLytSharedService as h, ComponentContextService as i, layoutControlPannelChildRoutes as j, CustomRouteReuseStrategy as k, layoutRoutes as l, CideLytUserStatusService as m, CacheManagerService as n, CideLytFileManagerService as o, processThemeVariable as p, CideLytFloatingEntityRightsSharingComponent as q, CideLytFloatingEntityRightsSharingService as r, setCSSVariable as s, themeFactory as t };
|
|
8159
|
-
//# sourceMappingURL=cloud-ide-layout-cloud-ide-layout-
|
|
8200
|
+
//# sourceMappingURL=cloud-ide-layout-cloud-ide-layout-DdMf1j7_.mjs.map
|