cloud-ide-layout 1.0.25 → 1.0.28
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-CJzTf7P-.mjs +4763 -0
- package/fesm2022/cloud-ide-layout-cloud-ide-layout-CJzTf7P-.mjs.map +1 -0
- package/fesm2022/{cloud-ide-layout-drawer-theme.component-gmPE1a4W.mjs → cloud-ide-layout-drawer-theme.component-D89HAFms.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-drawer-theme.component-gmPE1a4W.mjs.map → cloud-ide-layout-drawer-theme.component-D89HAFms.mjs.map} +1 -1
- package/fesm2022/cloud-ide-layout-floating-entity-selection.component-BLGbmXaB.mjs +515 -0
- package/fesm2022/cloud-ide-layout-floating-entity-selection.component-BLGbmXaB.mjs.map +1 -0
- package/fesm2022/{cloud-ide-layout-home-wrapper.component-iPkvBOgE.mjs → cloud-ide-layout-home-wrapper.component-CVv3swkS.mjs} +6 -6
- package/fesm2022/cloud-ide-layout-home-wrapper.component-CVv3swkS.mjs.map +1 -0
- package/fesm2022/{cloud-ide-layout-sidedrawer-notes.component-OKwCnhEf.mjs → cloud-ide-layout-sidedrawer-notes.component-Dhc7L8pg.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-sidedrawer-notes.component-OKwCnhEf.mjs.map → cloud-ide-layout-sidedrawer-notes.component-Dhc7L8pg.mjs.map} +1 -1
- package/fesm2022/cloud-ide-layout.mjs +1 -1
- package/index.d.ts +156 -9
- package/package.json +1 -1
- package/fesm2022/cloud-ide-layout-cloud-ide-layout-B7zJJ9zM.mjs +0 -3806
- package/fesm2022/cloud-ide-layout-cloud-ide-layout-B7zJJ9zM.mjs.map +0 -1
- package/fesm2022/cloud-ide-layout-home-wrapper.component-iPkvBOgE.mjs.map +0 -1
|
@@ -1,3806 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, signal, computed, effect, Component, ElementRef, HostListener, ViewContainerRef, ViewChild, ViewChildren, InjectionToken, PLATFORM_ID, EventEmitter, Output, Input } from '@angular/core';
|
|
3
|
-
import { HttpClient } from '@angular/common/http';
|
|
4
|
-
import { cidePath, hostManagerRoutesUrl, coreRoutesUrl, commonRoutesUrl, designConfigRoutesUrl } from 'cloud-ide-lms-model';
|
|
5
|
-
import { Observable, throwError, of, BehaviorSubject, interval, take as take$1 } from 'rxjs';
|
|
6
|
-
import { map, filter, tap, catchError, shareReplay, take, distinctUntilChanged } from 'rxjs/operators';
|
|
7
|
-
import * as i2 from '@angular/router';
|
|
8
|
-
import { Router, NavigationEnd, RouteReuseStrategy, RouterModule } from '@angular/router';
|
|
9
|
-
import { Title } from '@angular/platform-browser';
|
|
10
|
-
import { CideEleFileManagerService, CideElementsService, CideInputComponent, CideIconComponent, CideEleDropdownComponent, CideEleResizerDirective, TooltipDirective, CideSpinnerComponent, CideEleSkeletonLoaderComponent, CideEleFloatingContainerManagerComponent, CideEleGlobalNotificationsComponent, CideEleButtonComponent, CideEleFloatingContainerService } from 'cloud-ide-element';
|
|
11
|
-
import * as i1 from '@angular/common';
|
|
12
|
-
import { CommonModule, NgClass, NgFor, NgIf, isPlatformBrowser } from '@angular/common';
|
|
13
|
-
import { CloudIdeAuthService, authGuard } from 'cloud-ide-auth';
|
|
14
|
-
import { trigger, state, transition, style, animate } from '@angular/animations';
|
|
15
|
-
import { merge } from 'lodash';
|
|
16
|
-
import * as i2$1 from '@angular/forms';
|
|
17
|
-
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
|
18
|
-
|
|
19
|
-
class CloudIdeLayoutService {
|
|
20
|
-
constructor() { }
|
|
21
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CloudIdeLayoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
22
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CloudIdeLayoutService, providedIn: 'root' });
|
|
23
|
-
}
|
|
24
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CloudIdeLayoutService, decorators: [{
|
|
25
|
-
type: Injectable,
|
|
26
|
-
args: [{
|
|
27
|
-
providedIn: 'root'
|
|
28
|
-
}]
|
|
29
|
-
}], ctorParameters: () => [] });
|
|
30
|
-
|
|
31
|
-
class CideLytHeaderService {
|
|
32
|
-
headerVisible = false;
|
|
33
|
-
constructor() { }
|
|
34
|
-
// Hide Header
|
|
35
|
-
hideHeader() {
|
|
36
|
-
this.headerVisible = false;
|
|
37
|
-
document.querySelector(`#cide-lyt-header-wrapper`)?.classList.add('cide-lyt-header-wrapper-hide');
|
|
38
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-header-exist');
|
|
39
|
-
}
|
|
40
|
-
// Show Header
|
|
41
|
-
showHeader() {
|
|
42
|
-
this.headerVisible = true;
|
|
43
|
-
document.querySelector(`#cide-lyt-header-wrapper`)?.classList.remove('cide-lyt-header-wrapper-hide');
|
|
44
|
-
document.querySelector(`body`)?.classList.add('cide-lyt-header-exist');
|
|
45
|
-
}
|
|
46
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytHeaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
47
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytHeaderService, providedIn: 'root' });
|
|
48
|
-
}
|
|
49
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytHeaderService, decorators: [{
|
|
50
|
-
type: Injectable,
|
|
51
|
-
args: [{
|
|
52
|
-
providedIn: 'root'
|
|
53
|
-
}]
|
|
54
|
-
}], ctorParameters: () => [] });
|
|
55
|
-
|
|
56
|
-
class CideLytFileManagerService {
|
|
57
|
-
http = inject(HttpClient);
|
|
58
|
-
getFileDetails(body) {
|
|
59
|
-
return this.http.post(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager]), body);
|
|
60
|
-
}
|
|
61
|
-
uploadFile(file, additionalData) {
|
|
62
|
-
// Convert file to base64
|
|
63
|
-
return new Observable(observer => {
|
|
64
|
-
const reader = new FileReader();
|
|
65
|
-
reader.onload = () => {
|
|
66
|
-
const base64String = reader.result;
|
|
67
|
-
const base64Data = base64String.split(',')[1]; // Remove data:image/jpeg;base64, prefix
|
|
68
|
-
const payload = {
|
|
69
|
-
cyfm_name: file.name,
|
|
70
|
-
cyfm_alt_text: additionalData?.altText || file.name,
|
|
71
|
-
cyfm_path: '', // Will be set by backend
|
|
72
|
-
cyfm_size_in_byte: file.size,
|
|
73
|
-
cyfm_type: file.type,
|
|
74
|
-
cyfm_creation_dt: new Date().toISOString(),
|
|
75
|
-
cyfm_id_user: additionalData?.userId || '',
|
|
76
|
-
cyfm_permissions: additionalData?.permissions || [],
|
|
77
|
-
cyfm_tags: additionalData?.tags || [],
|
|
78
|
-
cyfm_version: 1,
|
|
79
|
-
cyfm_file_status_sygmt: additionalData?.fileStatus || 'active',
|
|
80
|
-
cyfm_isactive: true,
|
|
81
|
-
fileData: base64Data, // Base64 file data
|
|
82
|
-
...additionalData
|
|
83
|
-
};
|
|
84
|
-
this.http.post(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, coreRoutesUrl?.module, 'upload']), payload)
|
|
85
|
-
.subscribe({
|
|
86
|
-
next: (response) => observer.next(response),
|
|
87
|
-
error: (error) => observer.error(error)
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
reader.onerror = () => observer.error('Failed to read file');
|
|
91
|
-
reader.readAsDataURL(file);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFileManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
95
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFileManagerService, providedIn: 'root' });
|
|
96
|
-
}
|
|
97
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFileManagerService, decorators: [{
|
|
98
|
-
type: Injectable,
|
|
99
|
-
args: [{
|
|
100
|
-
providedIn: 'root'
|
|
101
|
-
}]
|
|
102
|
-
}] });
|
|
103
|
-
|
|
104
|
-
class CideLytSidebarService {
|
|
105
|
-
sidebarMenueVisible = false;
|
|
106
|
-
http = inject(HttpClient);
|
|
107
|
-
// method to get sidebar menues
|
|
108
|
-
getSidebarMenues(body) {
|
|
109
|
-
return this.http?.post(cidePath?.join([hostManagerRoutesUrl?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.getSidebarMenues]), body);
|
|
110
|
-
}
|
|
111
|
-
// Hide Sidebar
|
|
112
|
-
hideSidebar() {
|
|
113
|
-
this.sidebarMenueVisible = false;
|
|
114
|
-
document.querySelector(`#cide-lyt-sidebar-page`)?.classList.add('cide-lyt-sidebar-page-hide');
|
|
115
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-sidebar-exist');
|
|
116
|
-
}
|
|
117
|
-
// Show Sidebar
|
|
118
|
-
showSidebar() {
|
|
119
|
-
this.sidebarMenueVisible = true;
|
|
120
|
-
document.querySelector(`#cide-lyt-sidebar-page`)?.classList.remove('cide-lyt-sidebar-page-hide');
|
|
121
|
-
document.querySelector(`body`)?.classList.add('cide-lyt-sidebar-exist');
|
|
122
|
-
}
|
|
123
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidebarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
124
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidebarService, providedIn: 'root' });
|
|
125
|
-
}
|
|
126
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidebarService, decorators: [{
|
|
127
|
-
type: Injectable,
|
|
128
|
-
args: [{
|
|
129
|
-
providedIn: 'root'
|
|
130
|
-
}]
|
|
131
|
-
}] });
|
|
132
|
-
|
|
133
|
-
class CideLytFooterService {
|
|
134
|
-
footerVisible = false;
|
|
135
|
-
constructor() { }
|
|
136
|
-
// Hide Footer
|
|
137
|
-
hideFooter() {
|
|
138
|
-
this.footerVisible = false;
|
|
139
|
-
document.querySelector(`#cide-lyt-footer-console-wrapper`)?.classList.add('cide-lyt-footer-console-wrapper-hide');
|
|
140
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-footer-exist');
|
|
141
|
-
}
|
|
142
|
-
// Show Footer
|
|
143
|
-
showFooter() {
|
|
144
|
-
this.footerVisible = true;
|
|
145
|
-
document.querySelector(`#cide-lyt-footer-console-wrapper`)?.classList.remove('cide-lyt-footer-console-wrapper-hide');
|
|
146
|
-
document.querySelector(`body`)?.classList.add('cide-lyt-footer-exist');
|
|
147
|
-
}
|
|
148
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFooterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
149
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFooterService, providedIn: 'root' });
|
|
150
|
-
}
|
|
151
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFooterService, decorators: [{
|
|
152
|
-
type: Injectable,
|
|
153
|
-
args: [{
|
|
154
|
-
providedIn: 'root'
|
|
155
|
-
}]
|
|
156
|
-
}], ctorParameters: () => [] });
|
|
157
|
-
|
|
158
|
-
class CideLytConsoleService {
|
|
159
|
-
headerVisible = false;
|
|
160
|
-
constructor() { }
|
|
161
|
-
// Hide Console
|
|
162
|
-
hideConsole() {
|
|
163
|
-
this.headerVisible = false;
|
|
164
|
-
document.querySelector(`#cide-lyt-console-wrapper`)?.classList.add('cide-lyt-console-wrapper-hide');
|
|
165
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-console-exist');
|
|
166
|
-
}
|
|
167
|
-
// Show Console
|
|
168
|
-
showConsole() {
|
|
169
|
-
this.headerVisible = true;
|
|
170
|
-
document.querySelector(`#cide-lyt-console-wrapper`)?.classList.remove('cide-lyt-console-wrapper-hide');
|
|
171
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-console-exist');
|
|
172
|
-
}
|
|
173
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytConsoleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
174
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytConsoleService, providedIn: 'root' });
|
|
175
|
-
}
|
|
176
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytConsoleService, decorators: [{
|
|
177
|
-
type: Injectable,
|
|
178
|
-
args: [{
|
|
179
|
-
providedIn: 'root'
|
|
180
|
-
}]
|
|
181
|
-
}], ctorParameters: () => [] });
|
|
182
|
-
|
|
183
|
-
class CideLytSidedrawerService {
|
|
184
|
-
sidedrawerVisible = false;
|
|
185
|
-
// Modern Angular signals for reactive state management
|
|
186
|
-
drawerItemsSignal = signal([], ...(ngDevMode ? [{ debugName: "drawerItemsSignal" }] : []));
|
|
187
|
-
drawerItems = this.drawerItemsSignal.asReadonly();
|
|
188
|
-
notesVisibleSignal = signal(false, ...(ngDevMode ? [{ debugName: "notesVisibleSignal" }] : []));
|
|
189
|
-
notesVisible = this.notesVisibleSignal.asReadonly();
|
|
190
|
-
activeComponentSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeComponentSignal" }] : []));
|
|
191
|
-
activeComponent = this.activeComponentSignal.asReadonly();
|
|
192
|
-
_pageId = null;
|
|
193
|
-
_themeId = null;
|
|
194
|
-
setContext(pageId, themeId) {
|
|
195
|
-
this._pageId = pageId;
|
|
196
|
-
this._themeId = themeId;
|
|
197
|
-
}
|
|
198
|
-
get pageId() {
|
|
199
|
-
return this._pageId;
|
|
200
|
-
}
|
|
201
|
-
get themeId() {
|
|
202
|
-
return this._themeId;
|
|
203
|
-
}
|
|
204
|
-
// Hide Sidedrawer
|
|
205
|
-
hideSidedrawer() {
|
|
206
|
-
this.sidedrawerVisible = false;
|
|
207
|
-
document.querySelector(`#cide-lyt-sidedrawer-wrapper`)?.classList.add('cide-lyt-sidedrawer-wrapper-hide');
|
|
208
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-sidedrawer-exist');
|
|
209
|
-
}
|
|
210
|
-
// Show Sidedrawer
|
|
211
|
-
showSidedrawer() {
|
|
212
|
-
this.sidedrawerVisible = true;
|
|
213
|
-
document.querySelector(`#cide-lyt-sidedrawer-wrapper`)?.classList.remove('cide-lyt-sidedrawer-wrapper-hide');
|
|
214
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-sidedrawer-exist');
|
|
215
|
-
}
|
|
216
|
-
// Method to update drawer items from layout data
|
|
217
|
-
updateDrawerItems(layout) {
|
|
218
|
-
if (layout?.sytm_layout_drawer && layout.sytm_layout_drawer.length > 0) {
|
|
219
|
-
const items = layout.sytm_layout_drawer
|
|
220
|
-
.filter((item) => item.syth_status && item?.core_system_config?.syco_isactive) // Only show active items
|
|
221
|
-
.map(item => ({
|
|
222
|
-
status: item.syth_status,
|
|
223
|
-
configFor: item.syth_config_syco_for,
|
|
224
|
-
title: item.core_system_config?.syco_title,
|
|
225
|
-
icon: item.core_system_config?.syco_configuration.syco_icon,
|
|
226
|
-
description: item.core_system_config?.syco_desc || "",
|
|
227
|
-
isActive: item.core_system_config?.syco_isactive || false
|
|
228
|
-
}));
|
|
229
|
-
this.drawerItemsSignal.set(items);
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
this.drawerItemsSignal.set([]); // Clear items if no drawer data
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// Set the active component to display in the side drawer
|
|
236
|
-
setActiveComponent(configFor) {
|
|
237
|
-
this.activeComponentSignal.set(configFor);
|
|
238
|
-
}
|
|
239
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidedrawerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
240
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidedrawerService, providedIn: 'root' });
|
|
241
|
-
}
|
|
242
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidedrawerService, decorators: [{
|
|
243
|
-
type: Injectable,
|
|
244
|
-
args: [{
|
|
245
|
-
providedIn: 'root'
|
|
246
|
-
}]
|
|
247
|
-
}] });
|
|
248
|
-
|
|
249
|
-
class AppStateService {
|
|
250
|
-
// Inject file manager service
|
|
251
|
-
fileManagerService = inject(CideEleFileManagerService);
|
|
252
|
-
// Private signal for current user
|
|
253
|
-
currentUserSignal = signal(null, ...(ngDevMode ? [{ debugName: "currentUserSignal" }] : []));
|
|
254
|
-
// Private signal for active module
|
|
255
|
-
activeModuleSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeModuleSignal" }] : []));
|
|
256
|
-
// Private signal for active entity
|
|
257
|
-
activeEntitySignal = signal(null, ...(ngDevMode ? [{ debugName: "activeEntitySignal" }] : []));
|
|
258
|
-
// Public computed signal for current user, for other services to get the user value
|
|
259
|
-
currentUser = computed(() => this.currentUserSignal(), ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
|
|
260
|
-
// Public computed signal for active module
|
|
261
|
-
activeModule = computed(() => this.activeModuleSignal(), ...(ngDevMode ? [{ debugName: "activeModule" }] : []));
|
|
262
|
-
// Public computed signal for active entity
|
|
263
|
-
activeEntity = computed(() => this.activeEntitySignal(), ...(ngDevMode ? [{ debugName: "activeEntity" }] : []));
|
|
264
|
-
// Computed signals for derived state
|
|
265
|
-
isAuthenticated = computed(() => !!this.currentUserSignal(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
|
|
266
|
-
userInfo = computed(() => {
|
|
267
|
-
const user = this.currentUserSignal();
|
|
268
|
-
if (!user)
|
|
269
|
-
return null;
|
|
270
|
-
return {
|
|
271
|
-
id: user._id,
|
|
272
|
-
name: user.user_fullname || `${user.user_firstname} ${user.user_lastname}`,
|
|
273
|
-
email: user.user_emailid,
|
|
274
|
-
role: "" // user.user_id_role
|
|
275
|
-
};
|
|
276
|
-
}, ...(ngDevMode ? [{ debugName: "userInfo" }] : []));
|
|
277
|
-
// Computed signal for active module info
|
|
278
|
-
activeModuleInfo = computed(() => {
|
|
279
|
-
const module = this.activeModuleSignal();
|
|
280
|
-
if (!module)
|
|
281
|
-
return null;
|
|
282
|
-
return {
|
|
283
|
-
id: module._id,
|
|
284
|
-
title: module.syme_title,
|
|
285
|
-
icon: module.syme_icon,
|
|
286
|
-
path: module.syme_path,
|
|
287
|
-
type: module.syme_type
|
|
288
|
-
};
|
|
289
|
-
}, ...(ngDevMode ? [{ debugName: "activeModuleInfo" }] : []));
|
|
290
|
-
constructor() {
|
|
291
|
-
// Initialize file manager base URL on app startup
|
|
292
|
-
this.fileManagerService.setBaseUrl(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager]));
|
|
293
|
-
this.fileManagerService.setObjectIdEndpoint(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, commonRoutesUrl?.module, commonRoutesUrl?.generateObjectId]));
|
|
294
|
-
// Load initial state from localStorage
|
|
295
|
-
this.loadFromLocalStorage();
|
|
296
|
-
// Save to localStorage whenever state changes
|
|
297
|
-
effect(() => {
|
|
298
|
-
const currentUser = this.currentUserSignal();
|
|
299
|
-
const activeModule = this.activeModuleSignal();
|
|
300
|
-
const activeEntity = this.activeEntitySignal();
|
|
301
|
-
this.saveToLocalStorage({
|
|
302
|
-
user: currentUser,
|
|
303
|
-
activeModule: activeModule,
|
|
304
|
-
activeEntity: activeEntity
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
// Automatically update file manager user ID whenever user changes
|
|
308
|
-
effect(() => {
|
|
309
|
-
const currentUser = this.currentUserSignal();
|
|
310
|
-
console.log('🔍 [AppStateService] User changed, updating file manager user ID:', currentUser);
|
|
311
|
-
if (currentUser?._id) {
|
|
312
|
-
this.fileManagerService.setUserId(currentUser._id);
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
this.fileManagerService.setUserId('');
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
// Listen for localStorage changes from other tabs/windows
|
|
319
|
-
this.setupStorageListener();
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Set the current user
|
|
323
|
-
*/
|
|
324
|
-
setUser(user) {
|
|
325
|
-
this.currentUserSignal.set(user);
|
|
326
|
-
// Note: File manager user ID is automatically updated via effect() in constructor
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Update user data (partial update)
|
|
330
|
-
*/
|
|
331
|
-
updateUser(updates) {
|
|
332
|
-
const currentUser = this.currentUserSignal();
|
|
333
|
-
if (currentUser) {
|
|
334
|
-
this.currentUserSignal.set({ ...currentUser, ...updates });
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Clear user data
|
|
339
|
-
*/
|
|
340
|
-
clearUser() {
|
|
341
|
-
this.currentUserSignal.set(null);
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Get current user value (non-reactive)
|
|
345
|
-
*/
|
|
346
|
-
getUserValue() {
|
|
347
|
-
return this.currentUserSignal();
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Set the active module
|
|
351
|
-
*/
|
|
352
|
-
setActiveModule(module) {
|
|
353
|
-
this.activeModuleSignal.set(module);
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Get active module value (non-reactive)
|
|
357
|
-
*/
|
|
358
|
-
getActiveModuleValue() {
|
|
359
|
-
return this.activeModuleSignal();
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Set the active entity
|
|
363
|
-
*/
|
|
364
|
-
setActiveEntity(entity) {
|
|
365
|
-
this.activeEntitySignal.set(entity);
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Get active entity value (non-reactive)
|
|
369
|
-
*/
|
|
370
|
-
getActiveEntityValue() {
|
|
371
|
-
return this.activeEntitySignal();
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Manually refresh state from localStorage
|
|
375
|
-
* Call this method when you need to sync with external localStorage changes
|
|
376
|
-
*/
|
|
377
|
-
refreshFromLocalStorage() {
|
|
378
|
-
this.loadFromLocalStorage();
|
|
379
|
-
// Note: File manager user ID is automatically updated via effect() when user signal changes
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Check if localStorage is available (browser environment)
|
|
383
|
-
*/
|
|
384
|
-
isLocalStorageAvailable() {
|
|
385
|
-
try {
|
|
386
|
-
return typeof window !== 'undefined' && typeof localStorage !== 'undefined';
|
|
387
|
-
}
|
|
388
|
-
catch {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Save state to localStorage
|
|
394
|
-
*/
|
|
395
|
-
saveToLocalStorage(data) {
|
|
396
|
-
if (!this.isLocalStorageAvailable()) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
try {
|
|
400
|
-
if (data.user !== undefined) {
|
|
401
|
-
if (data.user) {
|
|
402
|
-
localStorage.setItem('cide_auth_user', JSON.stringify(data.user));
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
localStorage.removeItem('cide_auth_user');
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (data.activeModule !== undefined) {
|
|
409
|
-
if (data.activeModule) {
|
|
410
|
-
localStorage.setItem('cide_active_module', JSON.stringify(data.activeModule));
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
localStorage.removeItem('cide_active_module');
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (data.activeEntity !== undefined) {
|
|
417
|
-
if (data.activeEntity) {
|
|
418
|
-
localStorage.setItem('cide_active_entity', JSON.stringify(data.activeEntity));
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
localStorage.removeItem('cide_active_entity');
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
catch (error) {
|
|
426
|
-
console.error('Error saving to localStorage:', error);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Load state from localStorage
|
|
431
|
-
*/
|
|
432
|
-
loadFromLocalStorage() {
|
|
433
|
-
if (!this.isLocalStorageAvailable()) {
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
try {
|
|
437
|
-
const userData = localStorage.getItem('cide_auth_user');
|
|
438
|
-
if (userData) {
|
|
439
|
-
const user = JSON.parse(userData);
|
|
440
|
-
this.currentUserSignal.set(user);
|
|
441
|
-
}
|
|
442
|
-
const moduleData = localStorage.getItem('cide_active_module');
|
|
443
|
-
if (moduleData) {
|
|
444
|
-
const module = JSON.parse(moduleData);
|
|
445
|
-
this.activeModuleSignal.set(module);
|
|
446
|
-
}
|
|
447
|
-
const entityData = localStorage.getItem('cide_active_entity');
|
|
448
|
-
if (entityData) {
|
|
449
|
-
const entity = JSON.parse(entityData);
|
|
450
|
-
this.activeEntitySignal.set(entity);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
console.error('Error loading from localStorage:', error);
|
|
455
|
-
// Clear corrupted data
|
|
456
|
-
if (this.isLocalStorageAvailable()) {
|
|
457
|
-
localStorage.removeItem('cide_auth_user');
|
|
458
|
-
localStorage.removeItem('cide_active_module');
|
|
459
|
-
localStorage.removeItem('cide_active_entity');
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Setup storage event listener to handle external localStorage changes
|
|
465
|
-
*/
|
|
466
|
-
setupStorageListener() {
|
|
467
|
-
if (!this.isLocalStorageAvailable()) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
// Listen for storage events (triggered when localStorage is modified from other tabs/windows)
|
|
471
|
-
window.addEventListener('storage', (event) => {
|
|
472
|
-
if (!event.key)
|
|
473
|
-
return;
|
|
474
|
-
try {
|
|
475
|
-
// Handle user data changes
|
|
476
|
-
if (event.key === 'cide_auth_user') {
|
|
477
|
-
if (event.newValue) {
|
|
478
|
-
const user = JSON.parse(event.newValue);
|
|
479
|
-
this.currentUserSignal.set(user);
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
this.currentUserSignal.set(null);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
// Handle active module changes
|
|
486
|
-
if (event.key === 'cide_active_module') {
|
|
487
|
-
if (event.newValue) {
|
|
488
|
-
const module = JSON.parse(event.newValue);
|
|
489
|
-
this.activeModuleSignal.set(module);
|
|
490
|
-
}
|
|
491
|
-
else {
|
|
492
|
-
this.activeModuleSignal.set(null);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
// Handle active entity changes
|
|
496
|
-
if (event.key === 'cide_active_entity') {
|
|
497
|
-
if (event.newValue) {
|
|
498
|
-
const entity = JSON.parse(event.newValue);
|
|
499
|
-
this.activeEntitySignal.set(entity);
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
this.activeEntitySignal.set(null);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
catch (error) {
|
|
507
|
-
console.error('Error parsing localStorage update:', error);
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
512
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, providedIn: 'root' });
|
|
513
|
-
}
|
|
514
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateService, decorators: [{
|
|
515
|
-
type: Injectable,
|
|
516
|
-
args: [{
|
|
517
|
-
providedIn: 'root'
|
|
518
|
-
}]
|
|
519
|
-
}], ctorParameters: () => [] });
|
|
520
|
-
|
|
521
|
-
class AppStateHelperService {
|
|
522
|
-
appStateService = inject(AppStateService);
|
|
523
|
-
sidebarService = inject(CideLytSidebarService);
|
|
524
|
-
// Computed signals for derived state
|
|
525
|
-
currentUser = this.appStateService.currentUser;
|
|
526
|
-
isAuthenticated = this.appStateService.isAuthenticated;
|
|
527
|
-
userInfo = this.appStateService.userInfo;
|
|
528
|
-
activeModule = this.appStateService.activeModule;
|
|
529
|
-
activeModuleInfo = this.appStateService.activeModuleInfo;
|
|
530
|
-
activeEntity = this.appStateService.activeEntity;
|
|
531
|
-
/**
|
|
532
|
-
* Get current user value (non-reactive)
|
|
533
|
-
*/
|
|
534
|
-
getCurrentUser() {
|
|
535
|
-
return this.appStateService.getUserValue();
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Set user data
|
|
539
|
-
*/
|
|
540
|
-
setUser(user) {
|
|
541
|
-
this.appStateService.setUser(user);
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Update user data (partial update)
|
|
545
|
-
*/
|
|
546
|
-
updateUser(updates) {
|
|
547
|
-
this.appStateService.updateUser(updates);
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Clear user data
|
|
551
|
-
*/
|
|
552
|
-
clearUser() {
|
|
553
|
-
this.appStateService.clearUser();
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Check if user is authenticated
|
|
557
|
-
*/
|
|
558
|
-
isUserAuthenticated() {
|
|
559
|
-
return this.appStateService.isAuthenticated();
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Get user info object
|
|
563
|
-
*/
|
|
564
|
-
getUserInfo() {
|
|
565
|
-
return this.appStateService.userInfo();
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Get user ID
|
|
569
|
-
*/
|
|
570
|
-
getUserId() {
|
|
571
|
-
const user = this.getCurrentUser();
|
|
572
|
-
return user?._id || null;
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Get user name
|
|
576
|
-
*/
|
|
577
|
-
getUserName() {
|
|
578
|
-
const userInfo = this.getUserInfo();
|
|
579
|
-
return userInfo?.name || null;
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Get user email
|
|
583
|
-
*/
|
|
584
|
-
getUserEmail() {
|
|
585
|
-
const userInfo = this.getUserInfo();
|
|
586
|
-
return userInfo?.email || null;
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Get user username
|
|
590
|
-
*/
|
|
591
|
-
getUserUsername() {
|
|
592
|
-
const user = this.getCurrentUser();
|
|
593
|
-
return user?.user_username || null;
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Get user full name
|
|
597
|
-
*/
|
|
598
|
-
getUserFullName() {
|
|
599
|
-
const user = this.getCurrentUser();
|
|
600
|
-
return user?.user_fullname || null;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Get user first name
|
|
604
|
-
*/
|
|
605
|
-
getUserFirstName() {
|
|
606
|
-
const user = this.getCurrentUser();
|
|
607
|
-
return user?.user_firstname || null;
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Get user last name
|
|
611
|
-
*/
|
|
612
|
-
getUserLastName() {
|
|
613
|
-
const user = this.getCurrentUser();
|
|
614
|
-
return user?.user_lastname || null;
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* Get user mobile number
|
|
618
|
-
*/
|
|
619
|
-
getUserMobile() {
|
|
620
|
-
const user = this.getCurrentUser();
|
|
621
|
-
return user?.user_mobileno || null;
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Set the active module
|
|
625
|
-
*/
|
|
626
|
-
setActiveModule(module) {
|
|
627
|
-
this.appStateService.setActiveModule(module);
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Get active module value (non-reactive)
|
|
631
|
-
*/
|
|
632
|
-
getActiveModule() {
|
|
633
|
-
return this.appStateService.getActiveModuleValue();
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Get active module info
|
|
637
|
-
*/
|
|
638
|
-
getActiveModuleInfo() {
|
|
639
|
-
return this.appStateService.activeModuleInfo();
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Get active module ID
|
|
643
|
-
*/
|
|
644
|
-
getActiveModuleId() {
|
|
645
|
-
const module = this.getActiveModule();
|
|
646
|
-
return module?._id || null;
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Get active module title
|
|
650
|
-
*/
|
|
651
|
-
getActiveModuleTitle() {
|
|
652
|
-
const moduleInfo = this.getActiveModuleInfo();
|
|
653
|
-
return moduleInfo?.title || null;
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Get active module icon
|
|
657
|
-
*/
|
|
658
|
-
getActiveModuleIcon() {
|
|
659
|
-
const moduleInfo = this.getActiveModuleInfo();
|
|
660
|
-
return moduleInfo?.icon || null;
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Set a module as active by its ID
|
|
664
|
-
* This replaces the previous isModuleActive method to use setActiveModule instead of getActiveModuleId
|
|
665
|
-
*/
|
|
666
|
-
setActiveModuleById(moduleId) {
|
|
667
|
-
return this.sidebarService.getSidebarMenues({ syme_type: "module", sort: 'asc', syme_title: "" }).pipe(map(response => {
|
|
668
|
-
const modules = response?.data || [];
|
|
669
|
-
const targetModule = modules.find(module => module._id === moduleId);
|
|
670
|
-
if (targetModule) {
|
|
671
|
-
this.setActiveModule(targetModule);
|
|
672
|
-
return true;
|
|
673
|
-
}
|
|
674
|
-
else {
|
|
675
|
-
console.warn(`Module with ID "${moduleId}" not found`);
|
|
676
|
-
return false;
|
|
677
|
-
}
|
|
678
|
-
}));
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Check if a module is active (keeping the original functionality for backward compatibility)
|
|
682
|
-
*/
|
|
683
|
-
isModuleActive(moduleId) {
|
|
684
|
-
const activeModuleId = this.getActiveModuleId();
|
|
685
|
-
return activeModuleId === moduleId;
|
|
686
|
-
}
|
|
687
|
-
/**
|
|
688
|
-
* Set the active entity
|
|
689
|
-
*/
|
|
690
|
-
setActiveEntity(entity) {
|
|
691
|
-
this.appStateService.setActiveEntity(entity);
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Get active entity value (non-reactive)
|
|
695
|
-
*/
|
|
696
|
-
getActiveEntity() {
|
|
697
|
-
return this.appStateService.getActiveEntityValue();
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Get active entity ID
|
|
701
|
-
*/
|
|
702
|
-
getActiveEntityId() {
|
|
703
|
-
const entity = this.getActiveEntity();
|
|
704
|
-
return entity?._id || '';
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* Get active entity name
|
|
708
|
-
*/
|
|
709
|
-
getActiveEntityName() {
|
|
710
|
-
const entity = this.getActiveEntity();
|
|
711
|
-
return entity?.syen_name || null;
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* Get active entity code
|
|
715
|
-
*/
|
|
716
|
-
getActiveEntityCode() {
|
|
717
|
-
const entity = this.getActiveEntity();
|
|
718
|
-
return entity?.syen_entity_code || null;
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Manually refresh state from localStorage
|
|
722
|
-
*/
|
|
723
|
-
refreshFromLocalStorage() {
|
|
724
|
-
this.appStateService.refreshFromLocalStorage();
|
|
725
|
-
}
|
|
726
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
727
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateHelperService, providedIn: 'root' });
|
|
728
|
-
}
|
|
729
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: AppStateHelperService, decorators: [{
|
|
730
|
-
type: Injectable,
|
|
731
|
-
args: [{
|
|
732
|
-
providedIn: 'root'
|
|
733
|
-
}]
|
|
734
|
-
}] });
|
|
735
|
-
|
|
736
|
-
class CideLytSharedService {
|
|
737
|
-
// Enhanced reactive cache with signals
|
|
738
|
-
pageDataCacheSignal = signal({}, ...(ngDevMode ? [{ debugName: "pageDataCacheSignal" }] : []));
|
|
739
|
-
loadingStateSignal = signal({}, ...(ngDevMode ? [{ debugName: "loadingStateSignal" }] : []));
|
|
740
|
-
// Public readonly access to cache
|
|
741
|
-
pageDataCache = this.pageDataCacheSignal.asReadonly();
|
|
742
|
-
loadingStates = this.loadingStateSignal.asReadonly();
|
|
743
|
-
// Computed signal for cache status
|
|
744
|
-
cacheInfo = computed(() => {
|
|
745
|
-
const cache = this.pageDataCacheSignal();
|
|
746
|
-
return {
|
|
747
|
-
totalCached: Object.keys(cache).length,
|
|
748
|
-
validCached: Object.values(cache).filter(item => this.isCacheValid(item)).length,
|
|
749
|
-
expiredCached: Object.values(cache).filter(item => !this.isCacheValid(item)).length
|
|
750
|
-
};
|
|
751
|
-
}, ...(ngDevMode ? [{ debugName: "cacheInfo" }] : []));
|
|
752
|
-
// In-flight requests to prevent duplicate API calls
|
|
753
|
-
inflightRequests = {};
|
|
754
|
-
// Default cache TTL (5 minutes)
|
|
755
|
-
DEFAULT_CACHE_TTL = 5 * 60 * 1000;
|
|
756
|
-
// Modern Angular v20 dependency injection pattern
|
|
757
|
-
elementService = inject(CideElementsService);
|
|
758
|
-
http = inject(HttpClient);
|
|
759
|
-
titleService = inject(Title);
|
|
760
|
-
sidebarService = inject(CideLytSidebarService);
|
|
761
|
-
headerService = inject(CideLytHeaderService);
|
|
762
|
-
footerService = inject(CideLytFooterService);
|
|
763
|
-
consoleService = inject(CideLytConsoleService);
|
|
764
|
-
sidedrawerService = inject(CideLytSidedrawerService);
|
|
765
|
-
router = inject(Router);
|
|
766
|
-
// Import AppStateHelperService for sidebar sync
|
|
767
|
-
appState = inject(AppStateHelperService);
|
|
768
|
-
// Route change subscription for cleanup
|
|
769
|
-
routeSubscription;
|
|
770
|
-
pageDataSubscription;
|
|
771
|
-
// Tab management callback - to avoid circular dependency
|
|
772
|
-
tabManagementCallback;
|
|
773
|
-
// Request visibility callback
|
|
774
|
-
requestVisibilityCallback;
|
|
775
|
-
constructor() {
|
|
776
|
-
// Clean up expired cache entries every minute
|
|
777
|
-
// setInterval(() => this.cleanExpiredCache(), 60000);
|
|
778
|
-
// Listen to route changes - moved from request service
|
|
779
|
-
this.routeSubscription = this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
|
|
780
|
-
// Cancel any ongoing page data fetching
|
|
781
|
-
this.pageDataSubscription?.unsubscribe();
|
|
782
|
-
this.handleRouteChange();
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Register tab management callback from request service
|
|
787
|
-
* This allows shared service to call request service without circular dependency
|
|
788
|
-
*/
|
|
789
|
-
registerTabManagement(callback) {
|
|
790
|
-
this.tabManagementCallback = callback;
|
|
791
|
-
console.log('✅ Tab management callback registered');
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Register request visibility callback from request service
|
|
795
|
-
* This allows shared service to control request wrapper visibility
|
|
796
|
-
*/
|
|
797
|
-
registerRequestVisibility(callback) {
|
|
798
|
-
this.requestVisibilityCallback = callback;
|
|
799
|
-
console.log('✅ Request visibility callback registered');
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Enhanced loadPageData with caching and deduplication
|
|
803
|
-
* @param body - Request payload
|
|
804
|
-
* @param forceRefresh - Force API call even if cached data exists
|
|
805
|
-
* @param customTTL - Custom cache TTL in milliseconds
|
|
806
|
-
*/
|
|
807
|
-
loadPageData(body, forceRefresh = false, customTTL = this.DEFAULT_CACHE_TTL) {
|
|
808
|
-
const pageCode = body.sypg_page_code?.toString() || '';
|
|
809
|
-
const sytm_entity_id_syen = body.sytm_entity_id_syen?.toString() || this.appState.getActiveEntityId();
|
|
810
|
-
console.log(sytm_entity_id_syen, "sytm_entity_id_syen");
|
|
811
|
-
if (!body.sytm_entity_id_syen) {
|
|
812
|
-
body.sytm_entity_id_syen = sytm_entity_id_syen;
|
|
813
|
-
}
|
|
814
|
-
if (!pageCode) {
|
|
815
|
-
return throwError(() => new Error('Page code is required'));
|
|
816
|
-
}
|
|
817
|
-
// Check if we have valid cached data and not forcing refresh
|
|
818
|
-
if (!forceRefresh && this.hasValidCache(pageCode)) {
|
|
819
|
-
console.log(`🔄 CACHE HIT: Using cached data for page code: ${pageCode}`);
|
|
820
|
-
const cachedData = this.pageDataCacheSignal()[pageCode];
|
|
821
|
-
return of(cachedData.response);
|
|
822
|
-
}
|
|
823
|
-
// Check if there's already an in-flight request for this page code
|
|
824
|
-
if (this.inflightRequests[pageCode]) {
|
|
825
|
-
console.log(`🔄 INFLIGHT REQUEST: Returning existing request for page code: ${pageCode}`);
|
|
826
|
-
return this.inflightRequests[pageCode];
|
|
827
|
-
}
|
|
828
|
-
// Set loading state
|
|
829
|
-
this.setLoadingState(pageCode, true);
|
|
830
|
-
console.log(`🌐 API CALL: Loading fresh data for page code: ${pageCode}`);
|
|
831
|
-
// Create new API request
|
|
832
|
-
const request$ = this.http.post(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, designConfigRoutesUrl?.module, designConfigRoutesUrl?.getPageData]), body);
|
|
833
|
-
// Cache the observable to prevent duplicate requests
|
|
834
|
-
this.inflightRequests[pageCode] = request$.pipe(tap(response => {
|
|
835
|
-
console.log(`✅ API SUCCESS: Caching data for page code: ${pageCode}`);
|
|
836
|
-
this.cachePageData(pageCode, body, response, customTTL);
|
|
837
|
-
this.setLoadingState(pageCode, false);
|
|
838
|
-
}), catchError(error => {
|
|
839
|
-
console.error(`❌ API ERROR: Failed to load data for page code: ${pageCode}`, error);
|
|
840
|
-
this.setLoadingState(pageCode, false);
|
|
841
|
-
return throwError(() => error);
|
|
842
|
-
}), shareReplay(1) // Share the result with multiple subscribers
|
|
843
|
-
);
|
|
844
|
-
// Clean up inflight request after completion
|
|
845
|
-
this.inflightRequests[pageCode].subscribe({
|
|
846
|
-
complete: () => {
|
|
847
|
-
delete this.inflightRequests[pageCode];
|
|
848
|
-
},
|
|
849
|
-
error: () => {
|
|
850
|
-
delete this.inflightRequests[pageCode];
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
return this.inflightRequests[pageCode];
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
|
-
* Consolidated method to load and process page data
|
|
857
|
-
* This replaces the separate calls in request service and shared wrapper
|
|
858
|
-
*/
|
|
859
|
-
loadAndProcessPageData(body, options = {}) {
|
|
860
|
-
const { forceRefresh = false, customTTL = this.DEFAULT_CACHE_TTL, setTitle = true, setSidebarContext = true, updateLayout = true } = options;
|
|
861
|
-
return this.loadPageData(body, forceRefresh, customTTL).pipe(tap(response => {
|
|
862
|
-
if (response) {
|
|
863
|
-
console.log('🔍 SHARED SERVICE: loadAndProcessPageData', response);
|
|
864
|
-
// Process the response
|
|
865
|
-
if (updateLayout) {
|
|
866
|
-
this.setPageData(response, body);
|
|
867
|
-
}
|
|
868
|
-
// Set page title
|
|
869
|
-
if (setTitle && response.data?.page?.sypg_title) {
|
|
870
|
-
this.titleService.setTitle(response.data.page.sypg_title);
|
|
871
|
-
}
|
|
872
|
-
// Set sidebar context
|
|
873
|
-
if (setSidebarContext) {
|
|
874
|
-
// This will be handled by the component that needs the context
|
|
875
|
-
// as it's specific to the sidedrawer service
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}));
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Get cached page data without making API call
|
|
882
|
-
*/
|
|
883
|
-
getCachedPageData(pageCode) {
|
|
884
|
-
const cached = this.pageDataCacheSignal()[pageCode];
|
|
885
|
-
return this.isCacheValid(cached) ? cached.response : null;
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Check if loading for a specific page code
|
|
889
|
-
*/
|
|
890
|
-
isLoading(pageCode) {
|
|
891
|
-
return this.loadingStates()[pageCode] || false;
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Invalidate cache for specific page code
|
|
895
|
-
*/
|
|
896
|
-
invalidateCache(pageCode) {
|
|
897
|
-
const cache = { ...this.pageDataCacheSignal() };
|
|
898
|
-
delete cache[pageCode];
|
|
899
|
-
this.pageDataCacheSignal.set(cache);
|
|
900
|
-
console.log(`🗑️ CACHE INVALIDATED: Removed cache for page code: ${pageCode}`);
|
|
901
|
-
}
|
|
902
|
-
/**
|
|
903
|
-
* Clear all cached data
|
|
904
|
-
*/
|
|
905
|
-
clearAllCache() {
|
|
906
|
-
this.pageDataCacheSignal.set({});
|
|
907
|
-
this.loadingStateSignal.set({});
|
|
908
|
-
console.log('🗑️ CACHE CLEARED: All cached data removed');
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Preload page data for better performance
|
|
912
|
-
*/
|
|
913
|
-
preloadPageData(pageCodes) {
|
|
914
|
-
pageCodes.forEach(pageCode => {
|
|
915
|
-
if (!this.hasValidCache(pageCode) && !this.isLoading(pageCode)) {
|
|
916
|
-
this.loadPageData({ sypg_page_code: pageCode }).subscribe({
|
|
917
|
-
next: () => console.log(`🚀 PRELOADED: Page data for ${pageCode}`),
|
|
918
|
-
error: (err) => console.warn(`⚠️ PRELOAD FAILED: Could not preload ${pageCode}`, err)
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
// Private helper methods
|
|
924
|
-
hasValidCache(pageCode) {
|
|
925
|
-
const cached = this.pageDataCacheSignal()[pageCode];
|
|
926
|
-
return cached && this.isCacheValid(cached);
|
|
927
|
-
}
|
|
928
|
-
isCacheValid(cached) {
|
|
929
|
-
if (!cached)
|
|
930
|
-
return false;
|
|
931
|
-
return Date.now() - cached.timestamp < cached.ttl;
|
|
932
|
-
}
|
|
933
|
-
cachePageData(pageCode, request, response, ttl) {
|
|
934
|
-
const cache = { ...this.pageDataCacheSignal() };
|
|
935
|
-
cache[pageCode] = {
|
|
936
|
-
request,
|
|
937
|
-
response,
|
|
938
|
-
timestamp: Date.now(),
|
|
939
|
-
ttl
|
|
940
|
-
};
|
|
941
|
-
this.pageDataCacheSignal.set(cache);
|
|
942
|
-
}
|
|
943
|
-
setLoadingState(pageCode, loading) {
|
|
944
|
-
const loadingStates = { ...this.loadingStateSignal() };
|
|
945
|
-
if (loading) {
|
|
946
|
-
loadingStates[pageCode] = true;
|
|
947
|
-
}
|
|
948
|
-
else {
|
|
949
|
-
delete loadingStates[pageCode];
|
|
950
|
-
}
|
|
951
|
-
this.loadingStateSignal.set(loadingStates);
|
|
952
|
-
}
|
|
953
|
-
cleanExpiredCache() {
|
|
954
|
-
const cache = { ...this.pageDataCacheSignal() };
|
|
955
|
-
let cleaned = false;
|
|
956
|
-
Object.keys(cache).forEach(pageCode => {
|
|
957
|
-
if (!this.isCacheValid(cache[pageCode])) {
|
|
958
|
-
delete cache[pageCode];
|
|
959
|
-
cleaned = true;
|
|
960
|
-
}
|
|
961
|
-
});
|
|
962
|
-
if (cleaned) {
|
|
963
|
-
this.pageDataCacheSignal.set(cache);
|
|
964
|
-
console.log('🧹 CACHE CLEANUP: Removed expired cache entries');
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
setPageData(page_data_response, page_data_payload) {
|
|
968
|
-
// to set the controls data to the element service
|
|
969
|
-
console.log('🔍 SHARED SERVICE: setPageData', page_data_response, page_data_payload);
|
|
970
|
-
if (page_data_response?.data?.controls) {
|
|
971
|
-
// Set controls here
|
|
972
|
-
this.elementService.cide_element_data = { ...this.elementService.cide_element_data, ...page_data_response?.data?.controls };
|
|
973
|
-
this.elementService.is_cide_element_data_updated?.next(true);
|
|
974
|
-
}
|
|
975
|
-
// while doing this we need to think about not to touch if this page is not pening in the popup or drower insted of main window
|
|
976
|
-
// Set the layout data
|
|
977
|
-
console.log('🔍 Layout data:', page_data_response);
|
|
978
|
-
if (page_data_response?.data?.theme?.sytm_layout) {
|
|
979
|
-
console.log('🔍 SIDEBAR DEBUG - Layout data:', page_data_response?.data?.theme?.sytm_layout, page_data_payload);
|
|
980
|
-
// check is sidebar is hiden or not (default is visible)
|
|
981
|
-
if (page_data_response?.data?.theme?.sytm_layout?.sytm_layout_sidebar) {
|
|
982
|
-
const sidebarStatus = page_data_response?.data?.theme?.sytm_layout?.sytm_layout_sidebar?.status;
|
|
983
|
-
console.log('🔍 SIDEBAR DEBUG - Sidebar status found:', sidebarStatus);
|
|
984
|
-
if (!sidebarStatus) {
|
|
985
|
-
console.log('🚫 SIDEBAR DEBUG - Hiding sidebar because status is false');
|
|
986
|
-
this.sidebarService.hideSidebar();
|
|
987
|
-
}
|
|
988
|
-
else {
|
|
989
|
-
console.log('✅ SIDEBAR DEBUG - Showing sidebar because status is true');
|
|
990
|
-
this.sidebarService.showSidebar();
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
else {
|
|
994
|
-
console.log('⚠️ SIDEBAR DEBUG - No sidebar config found, hiding sidebar');
|
|
995
|
-
this.sidebarService.hideSidebar();
|
|
996
|
-
}
|
|
997
|
-
// check header visible or not
|
|
998
|
-
console.log('🔍 HEADER DEBUG - Header config found:', page_data_response?.data?.theme?.sytm_layout?.sytm_layout_header);
|
|
999
|
-
if (page_data_response?.data?.theme?.sytm_layout?.sytm_layout_header) {
|
|
1000
|
-
if (!page_data_response?.data?.theme?.sytm_layout?.sytm_layout_header?.status) {
|
|
1001
|
-
this.headerService.hideHeader();
|
|
1002
|
-
}
|
|
1003
|
-
else {
|
|
1004
|
-
this.headerService.showHeader();
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
else {
|
|
1008
|
-
this.headerService.hideHeader();
|
|
1009
|
-
}
|
|
1010
|
-
// check footer visible or not
|
|
1011
|
-
if (page_data_response?.data?.theme?.sytm_layout?.sytm_layout_footer) {
|
|
1012
|
-
if (!page_data_response?.data?.theme?.sytm_layout?.sytm_layout_footer?.status) {
|
|
1013
|
-
this.footerService.hideFooter();
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
this.footerService.showFooter();
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
this.footerService.hideFooter();
|
|
1021
|
-
}
|
|
1022
|
-
// check console visible or not
|
|
1023
|
-
if (page_data_response?.data?.theme?.sytm_layout?.sytm_layout_console) {
|
|
1024
|
-
if (!page_data_response?.data?.theme?.sytm_layout?.sytm_layout_console?.status) {
|
|
1025
|
-
this.consoleService.hideConsole();
|
|
1026
|
-
}
|
|
1027
|
-
else {
|
|
1028
|
-
this.consoleService.showConsole();
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
else {
|
|
1032
|
-
this.consoleService.hideConsole();
|
|
1033
|
-
}
|
|
1034
|
-
// check for the request status and log it for debugging
|
|
1035
|
-
console.log('🔍 REQUEST DEBUG - Request config found:', page_data_response?.data?.theme?.sytm_layout?.sytm_layout_request);
|
|
1036
|
-
if (page_data_response?.data?.theme?.sytm_layout?.sytm_layout_request) {
|
|
1037
|
-
const requestStatus = page_data_response?.data?.theme?.sytm_layout?.sytm_layout_request?.status;
|
|
1038
|
-
console.log('🔍 REQUEST DEBUG - Request status:', requestStatus, 'Type:', typeof requestStatus);
|
|
1039
|
-
// The request status controls whether this page should support tabs
|
|
1040
|
-
// This is used by the request service to determine if tabs should be created
|
|
1041
|
-
if (requestStatus === true) {
|
|
1042
|
-
console.log('✅ REQUEST DEBUG - Request is enabled for tabbing');
|
|
1043
|
-
// Show request wrapper when request is enabled
|
|
1044
|
-
if (this.requestVisibilityCallback) {
|
|
1045
|
-
this.requestVisibilityCallback(true);
|
|
1046
|
-
console.log('📋 REQUEST VISIBILITY - Showing request wrapper');
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
else if (requestStatus === false) {
|
|
1050
|
-
console.log('🚫 REQUEST DEBUG - Request is disabled for tabbing');
|
|
1051
|
-
// Hide request wrapper when request is disabled
|
|
1052
|
-
if (this.requestVisibilityCallback) {
|
|
1053
|
-
this.requestVisibilityCallback(false);
|
|
1054
|
-
console.log('📋 REQUEST VISIBILITY - Hiding request wrapper');
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
else {
|
|
1058
|
-
console.log('⚠️ REQUEST DEBUG - Unexpected request status value:', requestStatus);
|
|
1059
|
-
// Default to hiding request wrapper for unexpected values
|
|
1060
|
-
if (this.requestVisibilityCallback) {
|
|
1061
|
-
this.requestVisibilityCallback(false);
|
|
1062
|
-
console.log('📋 REQUEST VISIBILITY - Hiding request wrapper (unexpected value)');
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
else {
|
|
1067
|
-
console.log('⚠️ REQUEST DEBUG - No request config found, tabs will not be created');
|
|
1068
|
-
// Hide request wrapper when no request config found
|
|
1069
|
-
if (this.requestVisibilityCallback) {
|
|
1070
|
-
this.requestVisibilityCallback(false);
|
|
1071
|
-
console.log('📋 REQUEST VISIBILITY - Hiding request wrapper (no config)');
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Handle route changes - moved from request service for better architecture
|
|
1078
|
-
* This method loads page data and calls request service to manage tabs
|
|
1079
|
-
*/
|
|
1080
|
-
handleRouteChange() {
|
|
1081
|
-
const parsedUrl = this.router.parseUrl(this.router.url);
|
|
1082
|
-
const currentRoutePath = '/' + (parsedUrl.root.children['primary']?.segments.map(s => s.path).join('/') || '');
|
|
1083
|
-
const queryParams = parsedUrl.queryParams;
|
|
1084
|
-
console.log('🔍 Route change:', {
|
|
1085
|
-
currentRoutePath,
|
|
1086
|
-
queryParams
|
|
1087
|
-
});
|
|
1088
|
-
// Get page code from route data
|
|
1089
|
-
let route = this.router.routerState.snapshot.root;
|
|
1090
|
-
while (route.firstChild) {
|
|
1091
|
-
route = route.firstChild;
|
|
1092
|
-
}
|
|
1093
|
-
const pageCode = route.data && route.data['sypg_page_code'];
|
|
1094
|
-
console.log('📄 Processing route with page code:', pageCode);
|
|
1095
|
-
if (pageCode) {
|
|
1096
|
-
const entityId = this.appState.getActiveEntityId();
|
|
1097
|
-
console.log(`📡 Loading and processing page data for pageCode: ${pageCode}`);
|
|
1098
|
-
this.pageDataSubscription = this.loadAndProcessPageData({ sypg_page_code: pageCode, sytm_entity_id_syen: entityId || undefined }, {
|
|
1099
|
-
forceRefresh: false,
|
|
1100
|
-
setTitle: true,
|
|
1101
|
-
setSidebarContext: true,
|
|
1102
|
-
updateLayout: true
|
|
1103
|
-
}).pipe(take(1))
|
|
1104
|
-
.subscribe({
|
|
1105
|
-
next: (page_data_response) => {
|
|
1106
|
-
const layout = page_data_response?.data?.theme?.sytm_layout;
|
|
1107
|
-
// Update sidedrawer with processed layout
|
|
1108
|
-
this.sidedrawerService.updateDrawerItems(layout);
|
|
1109
|
-
// Sync sidebar active menu with loaded page
|
|
1110
|
-
this.syncSidebarActiveMenu(page_data_response, pageCode);
|
|
1111
|
-
if (layout?.sytm_layout_request?.status === true) {
|
|
1112
|
-
console.log('✅ Layout processed - calling request service to manage tabs');
|
|
1113
|
-
// Call request service to handle tab creation
|
|
1114
|
-
this.handleTabRequest(currentRoutePath, queryParams, layout, page_data_response, pageCode);
|
|
1115
|
-
}
|
|
1116
|
-
else {
|
|
1117
|
-
console.log('❌ Layout request disabled - tab not created');
|
|
1118
|
-
}
|
|
1119
|
-
},
|
|
1120
|
-
error: (err) => {
|
|
1121
|
-
console.error(`❌ Error loading page data for sypg_page_code '${pageCode}':`, err);
|
|
1122
|
-
this.sidedrawerService.updateDrawerItems(undefined);
|
|
1123
|
-
}
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
else {
|
|
1127
|
-
console.log('⚠️ No page code found in route data - clearing drawer');
|
|
1128
|
-
this.sidedrawerService.updateDrawerItems(undefined);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
/**
|
|
1132
|
-
* Handle tab request by calling request service
|
|
1133
|
-
*/
|
|
1134
|
-
handleTabRequest(currentRoutePath, queryParams, layout, pageData, pageCode) {
|
|
1135
|
-
if (this.tabManagementCallback) {
|
|
1136
|
-
console.log('🔄 Calling registered tab management callback');
|
|
1137
|
-
this.tabManagementCallback(currentRoutePath, queryParams, layout, pageData, pageCode);
|
|
1138
|
-
}
|
|
1139
|
-
else {
|
|
1140
|
-
console.log('⚠️ No tab management callback registered');
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
/**
|
|
1144
|
-
* Sync the active menu in the sidebar based on the loaded page data.
|
|
1145
|
-
* This ensures the correct module is highlighted in the sidebar.
|
|
1146
|
-
*/
|
|
1147
|
-
syncSidebarActiveMenu(page_data_response, pageCode) {
|
|
1148
|
-
console.log('🔍 SIDEBAR SYNC: Starting sidebar active menu sync for:', pageCode);
|
|
1149
|
-
// Try to extract module information from route
|
|
1150
|
-
const currentRoute = this.router.url;
|
|
1151
|
-
const routeParts = currentRoute.split('/').filter(part => part);
|
|
1152
|
-
let routeModuleName = '';
|
|
1153
|
-
// Look for control-panel or module identifier in route
|
|
1154
|
-
if (routeParts.includes('control-panel') && routeParts.length > 1) {
|
|
1155
|
-
const moduleIndex = routeParts.indexOf('control-panel') + 1;
|
|
1156
|
-
routeModuleName = routeParts[moduleIndex] || '';
|
|
1157
|
-
}
|
|
1158
|
-
else if (routeParts.length > 0) {
|
|
1159
|
-
routeModuleName = routeParts[0];
|
|
1160
|
-
}
|
|
1161
|
-
console.log('🔍 SIDEBAR SYNC: Route module name:', routeModuleName);
|
|
1162
|
-
console.log('🔍 SIDEBAR SYNC: Current route:', currentRoute);
|
|
1163
|
-
console.log('🔍 SIDEBAR SYNC: Page code:', pageCode);
|
|
1164
|
-
// Load sidebar modules and find the matching one
|
|
1165
|
-
this.sidebarService.getSidebarMenues({ syme_type: "module", sort: 'asc', syme_title: "" })
|
|
1166
|
-
.subscribe({
|
|
1167
|
-
next: (modulesResponse) => {
|
|
1168
|
-
const modules = modulesResponse?.data || [];
|
|
1169
|
-
console.log('🔍 SIDEBAR SYNC: Available modules:', modules.map(m => ({ id: m._id, title: m.syme_title, path: m.syme_path })));
|
|
1170
|
-
let matchedModule = null;
|
|
1171
|
-
// Try to match by route path
|
|
1172
|
-
if (routeModuleName) {
|
|
1173
|
-
matchedModule = modules.find(m => m.syme_path?.includes(routeModuleName) ||
|
|
1174
|
-
m.syme_title?.toLowerCase().includes(routeModuleName.toLowerCase()) ||
|
|
1175
|
-
routeModuleName.includes(m.syme_path || '') ||
|
|
1176
|
-
m.syme_path === routeModuleName);
|
|
1177
|
-
if (matchedModule) {
|
|
1178
|
-
console.log('✅ SIDEBAR SYNC: Matched module by route:', matchedModule.syme_title);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
// If not found, try to match by page code pattern
|
|
1182
|
-
if (!matchedModule && pageCode) {
|
|
1183
|
-
matchedModule = modules.find(m => pageCode.includes(m.syme_path || '') ||
|
|
1184
|
-
(m.syme_path && pageCode.toLowerCase().includes(m.syme_path.toLowerCase())) ||
|
|
1185
|
-
m.syme_path === pageCode);
|
|
1186
|
-
if (matchedModule) {
|
|
1187
|
-
console.log('✅ SIDEBAR SYNC: Matched module by page code:', matchedModule.syme_title);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
// If still not found, try partial matching for common patterns
|
|
1191
|
-
if (!matchedModule) {
|
|
1192
|
-
// Try to match common patterns like 'admin', 'dashboard', etc.
|
|
1193
|
-
const searchTerms = [routeModuleName, pageCode].filter(Boolean);
|
|
1194
|
-
for (const term of searchTerms) {
|
|
1195
|
-
matchedModule = modules.find(m => m.syme_title?.toLowerCase().includes(term.toLowerCase()) ||
|
|
1196
|
-
term.toLowerCase().includes(m.syme_title?.toLowerCase() || '') ||
|
|
1197
|
-
m.syme_path?.toLowerCase().includes(term.toLowerCase()) ||
|
|
1198
|
-
term.toLowerCase().includes(m.syme_path?.toLowerCase() || ''));
|
|
1199
|
-
if (matchedModule) {
|
|
1200
|
-
console.log('✅ SIDEBAR SYNC: Matched module by pattern:', matchedModule.syme_title, 'for term:', term);
|
|
1201
|
-
break;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
// Check if we should set the matched module as active
|
|
1206
|
-
if (matchedModule) {
|
|
1207
|
-
const currentActiveModule = this.appState.getActiveModule();
|
|
1208
|
-
// Be more conservative about overriding user selections
|
|
1209
|
-
// Only set the module if no module is currently active
|
|
1210
|
-
const shouldSetModule = !currentActiveModule;
|
|
1211
|
-
if (shouldSetModule) {
|
|
1212
|
-
console.log('🎯 SIDEBAR SYNC: No active module, setting:', matchedModule.syme_title, matchedModule._id);
|
|
1213
|
-
this.appState.setActiveModule(matchedModule);
|
|
1214
|
-
}
|
|
1215
|
-
else {
|
|
1216
|
-
console.log('✅ SIDEBAR SYNC: Module already active, respecting user selection:', currentActiveModule?.syme_title);
|
|
1217
|
-
console.log('🔍 SIDEBAR SYNC: Route suggests:', matchedModule.syme_title, '- not overriding');
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
else {
|
|
1221
|
-
console.log('⚠️ SIDEBAR SYNC: No matching module found for page:', pageCode);
|
|
1222
|
-
console.log('🔍 SIDEBAR SYNC: Available modules:', modules.map(m => m.syme_title).join(', '));
|
|
1223
|
-
console.log('🔍 SIDEBAR SYNC: Searched for route:', routeModuleName, 'and page code:', pageCode);
|
|
1224
|
-
}
|
|
1225
|
-
},
|
|
1226
|
-
error: (err) => {
|
|
1227
|
-
console.error('❌ SIDEBAR SYNC: Error loading modules for sync:', err);
|
|
1228
|
-
}
|
|
1229
|
-
});
|
|
1230
|
-
}
|
|
1231
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSharedService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1232
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSharedService, providedIn: 'root' });
|
|
1233
|
-
}
|
|
1234
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSharedService, decorators: [{
|
|
1235
|
-
type: Injectable,
|
|
1236
|
-
args: [{
|
|
1237
|
-
providedIn: 'root'
|
|
1238
|
-
}]
|
|
1239
|
-
}], ctorParameters: () => [] });
|
|
1240
|
-
|
|
1241
|
-
class CideLytSharedWrapperComponent {
|
|
1242
|
-
shared_wrapper_setup_param = {};
|
|
1243
|
-
page_data = {};
|
|
1244
|
-
sharedService = inject(CideLytSharedService);
|
|
1245
|
-
sidedrawerService = inject(CideLytSidedrawerService);
|
|
1246
|
-
appState = inject(AppStateHelperService);
|
|
1247
|
-
ngOnInit() {
|
|
1248
|
-
// Load and process page data using modern approach
|
|
1249
|
-
if (this.shared_wrapper_setup_param?.sypg_page_code) {
|
|
1250
|
-
const entityId = this.appState.getActiveEntityId();
|
|
1251
|
-
const page_data_payload = {
|
|
1252
|
-
sypg_page_code: this.shared_wrapper_setup_param?.sypg_page_code,
|
|
1253
|
-
sytm_entity_id_syen: entityId || undefined
|
|
1254
|
-
};
|
|
1255
|
-
console.log('🔍 Page data payload:', page_data_payload);
|
|
1256
|
-
this.sharedService.loadAndProcessPageData(page_data_payload, {
|
|
1257
|
-
setTitle: true,
|
|
1258
|
-
setSidebarContext: true,
|
|
1259
|
-
updateLayout: true
|
|
1260
|
-
}).subscribe({
|
|
1261
|
-
next: (page_data_response) => {
|
|
1262
|
-
console.log('✅ SHARED WRAPPER: Page data loaded and processed for:', page_data_payload.sypg_page_code);
|
|
1263
|
-
this.page_data = page_data_response?.data || {};
|
|
1264
|
-
// Sidebar context is automatically set by loadAndProcessPageData when setSidebarContext: true
|
|
1265
|
-
const pageId = page_data_response?.data?.page?._id || '';
|
|
1266
|
-
const themeId = page_data_response?.data?.theme?._id || '';
|
|
1267
|
-
this.sidedrawerService.setContext(pageId, themeId);
|
|
1268
|
-
},
|
|
1269
|
-
error: (err) => {
|
|
1270
|
-
console.error('❌ SHARED WRAPPER: Error loading page data:', err);
|
|
1271
|
-
}
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSharedWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1276
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideLytSharedWrapperComponent, isStandalone: true, selector: "cide-lyt-shared-wrapper", ngImport: i0, template: "<p>shared-wrapper works!</p>\r\n", styles: [""] });
|
|
1277
|
-
}
|
|
1278
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSharedWrapperComponent, decorators: [{
|
|
1279
|
-
type: Component,
|
|
1280
|
-
args: [{ selector: 'cide-lyt-shared-wrapper', standalone: true, imports: [], template: "<p>shared-wrapper works!</p>\r\n" }]
|
|
1281
|
-
}] });
|
|
1282
|
-
|
|
1283
|
-
class CideLytHeaderWrapperComponent extends CideLytSharedWrapperComponent {
|
|
1284
|
-
shared_wrapper_setup_param = {
|
|
1285
|
-
sypg_page_code: "cide_lyt_header"
|
|
1286
|
-
};
|
|
1287
|
-
syen_photo = {};
|
|
1288
|
-
// More Options Dropdown Configuration
|
|
1289
|
-
moreOptionsItems = [
|
|
1290
|
-
{
|
|
1291
|
-
id: 'settings',
|
|
1292
|
-
label: 'Settings',
|
|
1293
|
-
icon: 'settings',
|
|
1294
|
-
iconColor: 'tw-text-gray-500'
|
|
1295
|
-
},
|
|
1296
|
-
{
|
|
1297
|
-
id: 'help',
|
|
1298
|
-
label: 'Help & Support',
|
|
1299
|
-
icon: 'help',
|
|
1300
|
-
iconColor: 'tw-text-gray-500'
|
|
1301
|
-
},
|
|
1302
|
-
{
|
|
1303
|
-
id: 'divider',
|
|
1304
|
-
label: '',
|
|
1305
|
-
divider: true
|
|
1306
|
-
},
|
|
1307
|
-
{
|
|
1308
|
-
id: 'logout',
|
|
1309
|
-
label: 'Logout',
|
|
1310
|
-
icon: 'logout',
|
|
1311
|
-
iconColor: 'tw-text-red-500',
|
|
1312
|
-
textColor: 'tw-text-red-600',
|
|
1313
|
-
hoverBgColor: 'hover:tw-bg-red-50'
|
|
1314
|
-
}
|
|
1315
|
-
];
|
|
1316
|
-
moreOptionsConfig = {
|
|
1317
|
-
triggerIcon: 'more_vert',
|
|
1318
|
-
triggerSize: 'sm',
|
|
1319
|
-
menuPosition: 'auto',
|
|
1320
|
-
menuWidth: 'tw-w-48',
|
|
1321
|
-
usePortal: true
|
|
1322
|
-
};
|
|
1323
|
-
headerService = inject(CideLytHeaderService);
|
|
1324
|
-
fileManagerService = inject(CideLytFileManagerService);
|
|
1325
|
-
router = inject(Router);
|
|
1326
|
-
authService = inject(CloudIdeAuthService);
|
|
1327
|
-
constructor() {
|
|
1328
|
-
super();
|
|
1329
|
-
}
|
|
1330
|
-
ngAfterViewInit() {
|
|
1331
|
-
this.fileManagerService?.getFileDetails({ cyfm_id: '6724b247c2b5da7d83b04c91' })?.subscribe({
|
|
1332
|
-
next: (fileDetails) => {
|
|
1333
|
-
if (fileDetails?.data?.length) {
|
|
1334
|
-
this.syen_photo = fileDetails?.data[0];
|
|
1335
|
-
}
|
|
1336
|
-
},
|
|
1337
|
-
error: (error) => {
|
|
1338
|
-
console.error('Error loading file details:', error);
|
|
1339
|
-
}
|
|
1340
|
-
});
|
|
1341
|
-
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Handles logo click to navigate back to control panel home
|
|
1344
|
-
*/
|
|
1345
|
-
onLogoClick() {
|
|
1346
|
-
this.router.navigate(['/control-panel']);
|
|
1347
|
-
}
|
|
1348
|
-
/**
|
|
1349
|
-
* Updates the tooltip position based on mouse event
|
|
1350
|
-
* Similar to the sidebar tooltip positioning
|
|
1351
|
-
* @param event Mouse event from icon hover
|
|
1352
|
-
*/
|
|
1353
|
-
updateTooltipPosition(event) {
|
|
1354
|
-
// Find the tooltip element within the event target's parent
|
|
1355
|
-
const iconElement = event.currentTarget;
|
|
1356
|
-
const tooltip = iconElement.querySelector('.header-tooltip');
|
|
1357
|
-
if (tooltip) {
|
|
1358
|
-
// Ensure the tooltip is properly positioned
|
|
1359
|
-
const rect = iconElement.getBoundingClientRect();
|
|
1360
|
-
const headerRect = document.getElementById('cide-lyt-header-wrapper')?.getBoundingClientRect();
|
|
1361
|
-
if (headerRect) {
|
|
1362
|
-
// Adjust tooltip position if it would go outside the viewport
|
|
1363
|
-
const viewportWidth = window.innerWidth;
|
|
1364
|
-
const tooltipWidth = tooltip.offsetWidth;
|
|
1365
|
-
// Center the tooltip under the icon
|
|
1366
|
-
let leftPosition = '50%';
|
|
1367
|
-
let transform = 'translateX(-50%)';
|
|
1368
|
-
// Check if tooltip would extend beyond right edge
|
|
1369
|
-
if (rect.left + tooltipWidth / 2 > viewportWidth) {
|
|
1370
|
-
leftPosition = 'auto';
|
|
1371
|
-
tooltip.style.right = '0';
|
|
1372
|
-
transform = 'translateX(0)';
|
|
1373
|
-
}
|
|
1374
|
-
// Check if tooltip would extend beyond left edge
|
|
1375
|
-
if (rect.left - tooltipWidth / 2 < 0) {
|
|
1376
|
-
leftPosition = '0';
|
|
1377
|
-
tooltip.style.right = 'auto';
|
|
1378
|
-
transform = 'translateX(0)';
|
|
1379
|
-
}
|
|
1380
|
-
if (leftPosition !== 'auto' && leftPosition !== '0') {
|
|
1381
|
-
tooltip.style.left = leftPosition;
|
|
1382
|
-
}
|
|
1383
|
-
tooltip.style.transform = transform;
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
/**
|
|
1388
|
-
* Handle more options dropdown item clicks
|
|
1389
|
-
* @param item The clicked dropdown item
|
|
1390
|
-
*/
|
|
1391
|
-
onMoreOptionsClick(item) {
|
|
1392
|
-
console.log('🔍 [HeaderComponent] More options clicked:', item);
|
|
1393
|
-
switch (item.id) {
|
|
1394
|
-
case 'settings':
|
|
1395
|
-
this.navigateToSettings();
|
|
1396
|
-
break;
|
|
1397
|
-
case 'help':
|
|
1398
|
-
this.navigateToHelp();
|
|
1399
|
-
break;
|
|
1400
|
-
case 'logout':
|
|
1401
|
-
this.handleLogout();
|
|
1402
|
-
break;
|
|
1403
|
-
default:
|
|
1404
|
-
console.log('🔍 [HeaderComponent] Unknown option:', item.id);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Navigate to settings page
|
|
1409
|
-
*/
|
|
1410
|
-
navigateToSettings() {
|
|
1411
|
-
console.log('🔍 [HeaderComponent] Navigating to settings...');
|
|
1412
|
-
// TODO: Implement settings navigation
|
|
1413
|
-
// this.router.navigate(['/settings']);
|
|
1414
|
-
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Navigate to help and support page
|
|
1417
|
-
*/
|
|
1418
|
-
navigateToHelp() {
|
|
1419
|
-
console.log('🔍 [HeaderComponent] Navigating to help...');
|
|
1420
|
-
// TODO: Implement help navigation
|
|
1421
|
-
// this.router.navigate(['/help']);
|
|
1422
|
-
}
|
|
1423
|
-
/**
|
|
1424
|
-
* Handle user logout
|
|
1425
|
-
* Uses the auth service to sign out and navigate to login page
|
|
1426
|
-
*/
|
|
1427
|
-
handleLogout() {
|
|
1428
|
-
console.log('🔍 [HeaderComponent] Logging out user...');
|
|
1429
|
-
// Show confirmation dialog
|
|
1430
|
-
if (confirm('Are you sure you want to logout?')) {
|
|
1431
|
-
try {
|
|
1432
|
-
// Use the auth service to sign out
|
|
1433
|
-
this.authService.signOut();
|
|
1434
|
-
// Navigate to login page
|
|
1435
|
-
this.router.navigate(['/auth/sign-in']).then(success => {
|
|
1436
|
-
if (success) {
|
|
1437
|
-
console.log('✅ [HeaderComponent] Successfully logged out and navigated to login page');
|
|
1438
|
-
}
|
|
1439
|
-
else {
|
|
1440
|
-
console.error('❌ [HeaderComponent] Failed to navigate to login page');
|
|
1441
|
-
// Fallback: reload the page to force navigation
|
|
1442
|
-
window.location.href = '/auth/sign-in';
|
|
1443
|
-
}
|
|
1444
|
-
}).catch(error => {
|
|
1445
|
-
console.error('❌ [HeaderComponent] Navigation error:', error);
|
|
1446
|
-
// Fallback: reload the page to force navigation
|
|
1447
|
-
window.location.href = '/auth/sign-in';
|
|
1448
|
-
});
|
|
1449
|
-
}
|
|
1450
|
-
catch (error) {
|
|
1451
|
-
console.error('❌ [HeaderComponent] Logout error:', error);
|
|
1452
|
-
// Still try to navigate to login page even if logout fails
|
|
1453
|
-
this.router.navigate(['/auth/sign-in']);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytHeaderWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1458
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideLytHeaderWrapperComponent, isStandalone: true, selector: "cide-lyt-header-wrapper", usesInheritance: true, ngImport: i0, template: "<header id=\"cide-lyt-header-wrapper\" class=\"cide-lyt-header tw-w-full tw-select-none cide-lyt-header-wrapper-hide\">\n <!-- Logo Section -->\n <div class=\"header-logo-container tw-cursor-pointer\" \n (click)=\"onLogoClick()\" \n (keydown.enter)=\"onLogoClick()\" \n (keydown.space)=\"onLogoClick()\"\n tabindex=\"0\" \n role=\"button\"\n aria-label=\"Navigate to home\"\n title=\"Click to go to control panel home\">\n <img [src]=\"'data:image/png;base64,' + syen_photo.cyfm_file_base64\"\n [alt]=\"syen_photo.cyfm_alt_text ? syen_photo.cyfm_alt_text : 'Entity Logo'\">\n </div>\n <!-- Search Section -->\n <div class=\"header-search-container\">\n <cide-ele-input id=\"cide_lyt_header_search\" placeholder=\"Search...\" leadingIcon=\"search\" size=\"md\"></cide-ele-input>\n </div>\n \n <!-- Icons Section -->\n <div class=\"header-icons-container\">\n <div class=\"header-icon\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-icon>dashboard</cide-ele-icon>\n <div class=\"header-tooltip\">Dashboard</div>\n </div>\n \n <div class=\"header-icon\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-icon>notifications</cide-ele-icon>\n <div class=\"header-badge\">3</div>\n <div class=\"header-tooltip\">Notifications</div>\n </div>\n \n <div class=\"header-divider\"></div>\n \n <div class=\"header-icon user-profile\" (mouseenter)=\"updateTooltipPosition($event)\">\n <div class=\"profile-avatar\">AK</div>\n <div class=\"header-tooltip\">My Account</div>\n </div>\n \n <div class=\"header-icon\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown\n [items]=\"moreOptionsItems\"\n [config]=\"moreOptionsConfig\"\n (itemClick)=\"onMoreOptionsClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">More Options</div>\n </div>\n </div>\n</header>", styles: [".cide-lyt-header{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(to right,#fffffff2,#f9fafbf2);box-shadow:0 2px 8px #00000008;padding:0 1rem;position:relative;z-index:20;transition:all .3s cubic-bezier(.4,0,.2,1);will-change:transform;border-bottom:1px solid rgba(229,231,235,.8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-logo-container{height:100%;display:flex;align-items:center;padding:.5rem 0;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);border-radius:8px;outline:none}.header-logo-container img{height:30px;max-height:100%;transition:all .3s ease;border-radius:5px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.header-logo-container:hover img{transform:scale(1.03);filter:brightness(1.05);box-shadow:0 2px 6px #00000014}.header-logo-container:after{content:\"\";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(to bottom right,#fff0,#ffffff4d,#fff0);transform:rotate(30deg);opacity:0;transition:transform .6s ease,opacity .6s ease;pointer-events:none}.header-logo-container:hover:after,.header-logo-container:focus:after{opacity:1;transform:rotate(30deg) translate(50%,50%)}.header-search-container{flex-grow:1;max-width:600px;margin:0 2rem;position:relative;transition:all .3s ease}::ng-deep .header-search-container #cide_lyt_header_search{width:100%;background-color:#f9fafbcc;border-radius:20px!important;transition:all .3s ease;overflow:visible;transform:translateZ(0)}::ng-deep .header-search-container #cide_lyt_header_search:hover{box-shadow:0 3px 12px #00000014;background-color:#fff;transform:translateY(-1px)}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-input{background-color:transparent;font-size:.85rem!important;letter-spacing:.01em}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:#6b7280b3!important;font-size:1.1rem!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within{transform:translateY(-1px) scale(1.01)}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-input{border-color:#3b82f6!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-leading-icon{color:#3b82f6!important}.header-icons-container{display:flex;align-items:center;gap:1rem}.header-icon{position:relative;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-icon:before{content:\"\";position:absolute;inset:0;background-color:#3b82f61a;border-radius:.5rem;opacity:0;transform:scale(.8);transition:all .2s cubic-bezier(.4,0,.2,1)}.header-icon:hover:before{opacity:1;transform:scale(1)}.header-icon:hover{color:#3b82f6}.header-icon:active{transform:scale(.95)}.header-tooltip{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);background-color:#374151e6;color:#fff;padding:.25rem .6rem;border-radius:.25rem;font-size:.7rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 5px #0003;letter-spacing:.01em;will-change:transform,opacity}.header-tooltip:before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:transparent transparent rgba(55,65,81,.9) transparent}.header-icon:hover .header-tooltip{opacity:1;transform:translate(-50%) translateY(0)}.header-badge{position:absolute;top:0;right:0;min-width:16px;height:16px;border-radius:8px;background-color:#ef4444;color:#fff;font-size:9px;display:flex;align-items:center;justify-content:center;padding:0 4px;box-shadow:0 1px 3px #ef44444d;font-weight:600;z-index:2;transition:all .2s ease}.header-icon:hover .header-badge{transform:scale(1.1)}.header-divider{height:20px;width:1px;background-color:#e5e7ebcc;margin:0 6px}.profile-avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #2563eb33;transition:all .2s cubic-bezier(.4,0,.2,1);letter-spacing:-.5px}.header-icon:hover .profile-avatar{transform:scale(1.08);box-shadow:0 3px 8px #2563eb4d}.header-avatar{width:36px;height:36px;border-radius:50%;overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1);border:2px solid transparent;box-shadow:0 2px 4px #0000001a}.header-avatar:hover{border-color:#3b82f6;transform:scale(1.05);box-shadow:0 3px 6px #3b82f64d}@media (max-width: 768px){.header-search-container{margin:0 1rem}.header-icons-container{gap:.5rem}}@media (max-width: 640px){.header-search-container{max-width:200px;margin:0 .5rem}}\n"], dependencies: [{ kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "size"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleDropdownComponent, selector: "cide-ele-dropdown", inputs: ["items", "config", "triggerTemplate", "menuTemplate"], outputs: ["itemClick", "dropdownToggle"] }] });
|
|
1459
|
-
}
|
|
1460
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytHeaderWrapperComponent, decorators: [{
|
|
1461
|
-
type: Component,
|
|
1462
|
-
args: [{ selector: 'cide-lyt-header-wrapper', standalone: true, imports: [CideInputComponent, CommonModule, CideIconComponent, CideEleDropdownComponent], template: "<header id=\"cide-lyt-header-wrapper\" class=\"cide-lyt-header tw-w-full tw-select-none cide-lyt-header-wrapper-hide\">\n <!-- Logo Section -->\n <div class=\"header-logo-container tw-cursor-pointer\" \n (click)=\"onLogoClick()\" \n (keydown.enter)=\"onLogoClick()\" \n (keydown.space)=\"onLogoClick()\"\n tabindex=\"0\" \n role=\"button\"\n aria-label=\"Navigate to home\"\n title=\"Click to go to control panel home\">\n <img [src]=\"'data:image/png;base64,' + syen_photo.cyfm_file_base64\"\n [alt]=\"syen_photo.cyfm_alt_text ? syen_photo.cyfm_alt_text : 'Entity Logo'\">\n </div>\n <!-- Search Section -->\n <div class=\"header-search-container\">\n <cide-ele-input id=\"cide_lyt_header_search\" placeholder=\"Search...\" leadingIcon=\"search\" size=\"md\"></cide-ele-input>\n </div>\n \n <!-- Icons Section -->\n <div class=\"header-icons-container\">\n <div class=\"header-icon\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-icon>dashboard</cide-ele-icon>\n <div class=\"header-tooltip\">Dashboard</div>\n </div>\n \n <div class=\"header-icon\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-icon>notifications</cide-ele-icon>\n <div class=\"header-badge\">3</div>\n <div class=\"header-tooltip\">Notifications</div>\n </div>\n \n <div class=\"header-divider\"></div>\n \n <div class=\"header-icon user-profile\" (mouseenter)=\"updateTooltipPosition($event)\">\n <div class=\"profile-avatar\">AK</div>\n <div class=\"header-tooltip\">My Account</div>\n </div>\n \n <div class=\"header-icon\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown\n [items]=\"moreOptionsItems\"\n [config]=\"moreOptionsConfig\"\n (itemClick)=\"onMoreOptionsClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">More Options</div>\n </div>\n </div>\n</header>", styles: [".cide-lyt-header{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(to right,#fffffff2,#f9fafbf2);box-shadow:0 2px 8px #00000008;padding:0 1rem;position:relative;z-index:20;transition:all .3s cubic-bezier(.4,0,.2,1);will-change:transform;border-bottom:1px solid rgba(229,231,235,.8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-logo-container{height:100%;display:flex;align-items:center;padding:.5rem 0;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);border-radius:8px;outline:none}.header-logo-container img{height:30px;max-height:100%;transition:all .3s ease;border-radius:5px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.header-logo-container:hover img{transform:scale(1.03);filter:brightness(1.05);box-shadow:0 2px 6px #00000014}.header-logo-container:after{content:\"\";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(to bottom right,#fff0,#ffffff4d,#fff0);transform:rotate(30deg);opacity:0;transition:transform .6s ease,opacity .6s ease;pointer-events:none}.header-logo-container:hover:after,.header-logo-container:focus:after{opacity:1;transform:rotate(30deg) translate(50%,50%)}.header-search-container{flex-grow:1;max-width:600px;margin:0 2rem;position:relative;transition:all .3s ease}::ng-deep .header-search-container #cide_lyt_header_search{width:100%;background-color:#f9fafbcc;border-radius:20px!important;transition:all .3s ease;overflow:visible;transform:translateZ(0)}::ng-deep .header-search-container #cide_lyt_header_search:hover{box-shadow:0 3px 12px #00000014;background-color:#fff;transform:translateY(-1px)}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-input{background-color:transparent;font-size:.85rem!important;letter-spacing:.01em}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:#6b7280b3!important;font-size:1.1rem!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within{transform:translateY(-1px) scale(1.01)}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-input{border-color:#3b82f6!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-leading-icon{color:#3b82f6!important}.header-icons-container{display:flex;align-items:center;gap:1rem}.header-icon{position:relative;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-icon:before{content:\"\";position:absolute;inset:0;background-color:#3b82f61a;border-radius:.5rem;opacity:0;transform:scale(.8);transition:all .2s cubic-bezier(.4,0,.2,1)}.header-icon:hover:before{opacity:1;transform:scale(1)}.header-icon:hover{color:#3b82f6}.header-icon:active{transform:scale(.95)}.header-tooltip{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);background-color:#374151e6;color:#fff;padding:.25rem .6rem;border-radius:.25rem;font-size:.7rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 5px #0003;letter-spacing:.01em;will-change:transform,opacity}.header-tooltip:before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:transparent transparent rgba(55,65,81,.9) transparent}.header-icon:hover .header-tooltip{opacity:1;transform:translate(-50%) translateY(0)}.header-badge{position:absolute;top:0;right:0;min-width:16px;height:16px;border-radius:8px;background-color:#ef4444;color:#fff;font-size:9px;display:flex;align-items:center;justify-content:center;padding:0 4px;box-shadow:0 1px 3px #ef44444d;font-weight:600;z-index:2;transition:all .2s ease}.header-icon:hover .header-badge{transform:scale(1.1)}.header-divider{height:20px;width:1px;background-color:#e5e7ebcc;margin:0 6px}.profile-avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #2563eb33;transition:all .2s cubic-bezier(.4,0,.2,1);letter-spacing:-.5px}.header-icon:hover .profile-avatar{transform:scale(1.08);box-shadow:0 3px 8px #2563eb4d}.header-avatar{width:36px;height:36px;border-radius:50%;overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1);border:2px solid transparent;box-shadow:0 2px 4px #0000001a}.header-avatar:hover{border-color:#3b82f6;transform:scale(1.05);box-shadow:0 3px 6px #3b82f64d}@media (max-width: 768px){.header-search-container{margin:0 1rem}.header-icons-container{gap:.5rem}}@media (max-width: 640px){.header-search-container{max-width:200px;margin:0 .5rem}}\n"] }]
|
|
1463
|
-
}], ctorParameters: () => [] });
|
|
1464
|
-
|
|
1465
|
-
class CideLytUserStatusService {
|
|
1466
|
-
// Modern Angular Signals pattern
|
|
1467
|
-
_userStatus = signal({
|
|
1468
|
-
status: 'online',
|
|
1469
|
-
lastActive: new Date(),
|
|
1470
|
-
message: undefined
|
|
1471
|
-
}, ...(ngDevMode ? [{ debugName: "_userStatus" }] : []));
|
|
1472
|
-
// Public readonly access to status
|
|
1473
|
-
userStatus = this._userStatus.asReadonly();
|
|
1474
|
-
// Computed properties
|
|
1475
|
-
isOnline = computed(() => this._userStatus().status !== 'offline', ...(ngDevMode ? [{ debugName: "isOnline" }] : []));
|
|
1476
|
-
statusColor = computed(() => this.getStatusColor(this._userStatus().status), ...(ngDevMode ? [{ debugName: "statusColor" }] : []));
|
|
1477
|
-
statusText = computed(() => this.getStatusText(this._userStatus().status), ...(ngDevMode ? [{ debugName: "statusText" }] : []));
|
|
1478
|
-
// Legacy observable for compatibility
|
|
1479
|
-
statusSubject = new BehaviorSubject(this._userStatus());
|
|
1480
|
-
status$ = this.statusSubject.asObservable();
|
|
1481
|
-
// Auto-away functionality
|
|
1482
|
-
lastActivityTime = Date.now();
|
|
1483
|
-
autoAwayTimeout = 5 * 60 * 1000; // 5 minutes
|
|
1484
|
-
autoAwayEnabled = true;
|
|
1485
|
-
constructor() {
|
|
1486
|
-
this.initializeActivityTracking();
|
|
1487
|
-
this.startAutoAwayTimer();
|
|
1488
|
-
}
|
|
1489
|
-
/**
|
|
1490
|
-
* Sets the user status
|
|
1491
|
-
* @param status The new status to set
|
|
1492
|
-
* @param message Optional custom status message
|
|
1493
|
-
*/
|
|
1494
|
-
setStatus(status, message) {
|
|
1495
|
-
const statusData = {
|
|
1496
|
-
status,
|
|
1497
|
-
lastActive: new Date(),
|
|
1498
|
-
message
|
|
1499
|
-
};
|
|
1500
|
-
this._userStatus.set(statusData);
|
|
1501
|
-
this.statusSubject.next(statusData);
|
|
1502
|
-
this.lastActivityTime = Date.now();
|
|
1503
|
-
}
|
|
1504
|
-
/**
|
|
1505
|
-
* Marks user as active (resets away timer)
|
|
1506
|
-
*/
|
|
1507
|
-
markUserActive() {
|
|
1508
|
-
this.lastActivityTime = Date.now();
|
|
1509
|
-
// If user was away, bring them back to online
|
|
1510
|
-
if (this._userStatus().status === 'away') {
|
|
1511
|
-
this.setStatus('online');
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
/**
|
|
1515
|
-
* Gets CSS class for status color
|
|
1516
|
-
*/
|
|
1517
|
-
getStatusColor(status) {
|
|
1518
|
-
switch (status) {
|
|
1519
|
-
case 'online': return 'tw-bg-green-500';
|
|
1520
|
-
case 'away': return 'tw-bg-yellow-500';
|
|
1521
|
-
case 'busy': return 'tw-bg-orange-500';
|
|
1522
|
-
case 'do-not-disturb': return 'tw-bg-red-500';
|
|
1523
|
-
case 'offline': return 'tw-bg-gray-400';
|
|
1524
|
-
default: return 'tw-bg-gray-400';
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
/**
|
|
1528
|
-
* Gets human-readable status text
|
|
1529
|
-
*/
|
|
1530
|
-
getStatusText(status) {
|
|
1531
|
-
const current = this._userStatus();
|
|
1532
|
-
const baseText = this.getBaseStatusText(status);
|
|
1533
|
-
return current.message ? `${baseText} - ${current.message}` : baseText;
|
|
1534
|
-
}
|
|
1535
|
-
getBaseStatusText(status) {
|
|
1536
|
-
switch (status) {
|
|
1537
|
-
case 'online': return 'Online';
|
|
1538
|
-
case 'away': return 'Away';
|
|
1539
|
-
case 'busy': return 'Busy';
|
|
1540
|
-
case 'do-not-disturb': return 'Do not disturb';
|
|
1541
|
-
case 'offline': return 'Offline';
|
|
1542
|
-
default: return 'Unknown';
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
/**
|
|
1546
|
-
* Initialize activity tracking for auto-away
|
|
1547
|
-
*/
|
|
1548
|
-
initializeActivityTracking() {
|
|
1549
|
-
if (typeof window === 'undefined')
|
|
1550
|
-
return;
|
|
1551
|
-
const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'];
|
|
1552
|
-
events.forEach(event => {
|
|
1553
|
-
document.addEventListener(event, () => {
|
|
1554
|
-
this.markUserActive();
|
|
1555
|
-
}, { passive: true });
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
/**
|
|
1559
|
-
* Start the auto-away timer
|
|
1560
|
-
*/
|
|
1561
|
-
startAutoAwayTimer() {
|
|
1562
|
-
if (!this.autoAwayEnabled)
|
|
1563
|
-
return;
|
|
1564
|
-
interval(30000).subscribe({
|
|
1565
|
-
next: () => {
|
|
1566
|
-
const now = Date.now();
|
|
1567
|
-
const timeSinceActivity = now - this.lastActivityTime;
|
|
1568
|
-
if (timeSinceActivity > this.autoAwayTimeout &&
|
|
1569
|
-
this._userStatus().status === 'online') {
|
|
1570
|
-
this.setStatus('away', 'Auto-away due to inactivity');
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
});
|
|
1574
|
-
}
|
|
1575
|
-
/**
|
|
1576
|
-
* Enable or disable auto-away functionality
|
|
1577
|
-
*/
|
|
1578
|
-
setAutoAwayEnabled(enabled) {
|
|
1579
|
-
this.autoAwayEnabled = enabled;
|
|
1580
|
-
}
|
|
1581
|
-
/**
|
|
1582
|
-
* Set the auto-away timeout (in milliseconds)
|
|
1583
|
-
*/
|
|
1584
|
-
setAutoAwayTimeout(timeout) {
|
|
1585
|
-
this.autoAwayTimeout = timeout;
|
|
1586
|
-
}
|
|
1587
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytUserStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1588
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytUserStatusService, providedIn: 'root' });
|
|
1589
|
-
}
|
|
1590
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytUserStatusService, decorators: [{
|
|
1591
|
-
type: Injectable,
|
|
1592
|
-
args: [{
|
|
1593
|
-
providedIn: 'root'
|
|
1594
|
-
}]
|
|
1595
|
-
}], ctorParameters: () => [] });
|
|
1596
|
-
|
|
1597
|
-
class CideLytSidebarWrapperComponent {
|
|
1598
|
-
sidebarSetupData = {
|
|
1599
|
-
cide_lyt_sidebar_width: 0
|
|
1600
|
-
};
|
|
1601
|
-
isCollapsed = false;
|
|
1602
|
-
darkMode = false;
|
|
1603
|
-
showUserMenu = false;
|
|
1604
|
-
showOptions = false;
|
|
1605
|
-
showNotifications = false;
|
|
1606
|
-
showNotificationsPanel = false;
|
|
1607
|
-
searchText = '';
|
|
1608
|
-
isExpanded = true;
|
|
1609
|
-
searchResults = [];
|
|
1610
|
-
// Collapsed section states tracking
|
|
1611
|
-
collapsedSections = {
|
|
1612
|
-
'company': false,
|
|
1613
|
-
'account': false,
|
|
1614
|
-
'advanced': false,
|
|
1615
|
-
'recent': false
|
|
1616
|
-
};
|
|
1617
|
-
// Track expanded state for menu sections
|
|
1618
|
-
expandedSections = {};
|
|
1619
|
-
// For scroll-based animation
|
|
1620
|
-
animateSections = [false, false, false, false, false];
|
|
1621
|
-
lastScrollTop = 0;
|
|
1622
|
-
scrolledPast = [false, false, false, false, false];
|
|
1623
|
-
core_system_menu = [];
|
|
1624
|
-
core_system_module = [];
|
|
1625
|
-
// Track active state for enhanced visual effects
|
|
1626
|
-
activeModuleId = '';
|
|
1627
|
-
hoverModuleId = '';
|
|
1628
|
-
animateItems = false;
|
|
1629
|
-
moduleLoadComplete = false;
|
|
1630
|
-
menuLoadComplete = false;
|
|
1631
|
-
loadingMenus = false;
|
|
1632
|
-
// Add this getter to retrieve the menu tree for the selected module
|
|
1633
|
-
get selectedModuleMenus() {
|
|
1634
|
-
return this.core_system_menu || [];
|
|
1635
|
-
}
|
|
1636
|
-
/**
|
|
1637
|
-
* Gets the CSS classes for the user status indicator
|
|
1638
|
-
* Uses the injected user status service
|
|
1639
|
-
*/
|
|
1640
|
-
getUserStatusClass() {
|
|
1641
|
-
return this.userStatusService.statusColor();
|
|
1642
|
-
}
|
|
1643
|
-
/**
|
|
1644
|
-
* Gets the tooltip text for the user status
|
|
1645
|
-
* Uses the injected user status service
|
|
1646
|
-
*/
|
|
1647
|
-
getUserStatusText() {
|
|
1648
|
-
return this.userStatusService.statusText();
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Sets the user status (delegates to service)
|
|
1652
|
-
* @param status The new status to set
|
|
1653
|
-
* @param message Optional custom status message
|
|
1654
|
-
*/
|
|
1655
|
-
setUserStatus(status, message) {
|
|
1656
|
-
this.userStatusService.setStatus(status, message);
|
|
1657
|
-
}
|
|
1658
|
-
/**
|
|
1659
|
-
* Gets the current user status data
|
|
1660
|
-
*/
|
|
1661
|
-
get currentUserStatus() {
|
|
1662
|
-
return this.userStatusService.userStatus();
|
|
1663
|
-
}
|
|
1664
|
-
/**
|
|
1665
|
-
* Gets the icon for the currently active module
|
|
1666
|
-
* @returns The icon name for the active module or a default icon
|
|
1667
|
-
*/
|
|
1668
|
-
getActiveModuleIcon() {
|
|
1669
|
-
if (this.core_system_module && this.core_system_module.length > 0 && this.activeModuleId) {
|
|
1670
|
-
const activeModule = this.core_system_module.find(m => m._id === this.activeModuleId);
|
|
1671
|
-
return activeModule?.syme_icon || 'dashboard';
|
|
1672
|
-
}
|
|
1673
|
-
return 'dashboard';
|
|
1674
|
-
}
|
|
1675
|
-
// Add a click handler for menu navigation
|
|
1676
|
-
onMenuClick(menu) {
|
|
1677
|
-
if (menu.syme_path) {
|
|
1678
|
-
// Check if this is an entity-related menu
|
|
1679
|
-
// Navigate to entity list component
|
|
1680
|
-
this.router.navigate([`/control-panel/${menu.syme_path}`]);
|
|
1681
|
-
// window.location.hash = menu.syme_path;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Toggle section expand/collapse state
|
|
1686
|
-
*/
|
|
1687
|
-
toggleSection(sectionId) {
|
|
1688
|
-
this.expandedSections[sectionId] = !this.expandedSections[sectionId];
|
|
1689
|
-
console.log('📂 SIDEBAR: Section', sectionId, 'expanded:', this.expandedSections[sectionId]);
|
|
1690
|
-
}
|
|
1691
|
-
/**
|
|
1692
|
-
* Check if section is expanded
|
|
1693
|
-
*/
|
|
1694
|
-
isSectionExpanded(sectionId) {
|
|
1695
|
-
return this.expandedSections[sectionId] ?? true; // Default to expanded
|
|
1696
|
-
}
|
|
1697
|
-
sidebarService = inject(CideLytSidebarService);
|
|
1698
|
-
elementRef = inject(ElementRef);
|
|
1699
|
-
appState = inject(AppStateHelperService);
|
|
1700
|
-
router = inject(Router);
|
|
1701
|
-
userStatusService = inject(CideLytUserStatusService);
|
|
1702
|
-
constructor() {
|
|
1703
|
-
// React to active module changes using signal effects
|
|
1704
|
-
effect(() => {
|
|
1705
|
-
const activeModule = this.appState.activeModule();
|
|
1706
|
-
if (activeModule) {
|
|
1707
|
-
console.log('🔄 SIDEBAR: Active module signal changed:', activeModule.syme_title);
|
|
1708
|
-
// Update local state when active module changes
|
|
1709
|
-
this.activeModuleId = activeModule._id;
|
|
1710
|
-
// Always try to load menus when active module changes
|
|
1711
|
-
this.loadMenusForModule(activeModule);
|
|
1712
|
-
}
|
|
1713
|
-
});
|
|
1714
|
-
}
|
|
1715
|
-
ngOnInit() {
|
|
1716
|
-
// Modern ES2022+ pattern: Use Promise-based delays instead of nested setTimeout
|
|
1717
|
-
const initializeAnimations = async () => {
|
|
1718
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1719
|
-
this.animateItems = true;
|
|
1720
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
1721
|
-
this.animateSections = [true, true, true, true, true];
|
|
1722
|
-
};
|
|
1723
|
-
initializeAnimations();
|
|
1724
|
-
// Get sidebar menu data with loading state tracking
|
|
1725
|
-
this.sidebarService?.getSidebarMenues({ syme_type: "module", sort: 'asc', syme_title: "" })?.subscribe({
|
|
1726
|
-
next: (res) => {
|
|
1727
|
-
this.core_system_module = res?.data || [];
|
|
1728
|
-
// Handle module initialization and active module setup
|
|
1729
|
-
if (this.core_system_module.length > 0) {
|
|
1730
|
-
const activeModule = this.appState.activeModule();
|
|
1731
|
-
console.log('🔍 SIDEBAR: Modules loaded. Current active module:', activeModule?.syme_title || 'none');
|
|
1732
|
-
console.log('📊 SIDEBAR: Available modules:', this.core_system_module.map(m => m.syme_title).join(', '));
|
|
1733
|
-
if (activeModule) {
|
|
1734
|
-
// Active module exists, verify it's in the loaded modules
|
|
1735
|
-
const foundModule = this.core_system_module.find(m => m._id === activeModule._id);
|
|
1736
|
-
if (foundModule) {
|
|
1737
|
-
console.log('✅ SIDEBAR: Active module found in loaded modules:', foundModule.syme_title);
|
|
1738
|
-
// The effect should have already triggered, but ensure menus are loaded
|
|
1739
|
-
if (this.core_system_menu.length === 0) {
|
|
1740
|
-
console.log('🔄 SIDEBAR: No menus loaded yet, triggering load for active module');
|
|
1741
|
-
this.loadMenusForModule(activeModule);
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
else {
|
|
1745
|
-
console.log('⚠️ SIDEBAR: Active module not found in loaded modules, setting first as active');
|
|
1746
|
-
this.setActiveMenuByIndex(0);
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
else {
|
|
1750
|
-
console.log('📌 SIDEBAR: No active module, setting first module as active');
|
|
1751
|
-
this.setActiveMenuByIndex(0);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
// Modern ES2022+ pattern: Use Promise-based delay
|
|
1755
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
1756
|
-
delay(300).then(() => {
|
|
1757
|
-
this.moduleLoadComplete = true;
|
|
1758
|
-
});
|
|
1759
|
-
},
|
|
1760
|
-
error: (err) => {
|
|
1761
|
-
console.error('Error loading sidebar modules:', err);
|
|
1762
|
-
this.moduleLoadComplete = true; // Set to true even on error to ensure UI is responsive
|
|
1763
|
-
}
|
|
1764
|
-
});
|
|
1765
|
-
// collecte the width of the sidebar by using the style property
|
|
1766
|
-
const cide_lyt_stack_wrapper_width = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--cide-lyt-stack-wrapper-width'));
|
|
1767
|
-
const cide_lyt_sidebar_menu_width = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--cide-lyt-sidebar-menu-width'));
|
|
1768
|
-
this.sidebarSetupData.cide_lyt_sidebar_width = (cide_lyt_stack_wrapper_width + cide_lyt_sidebar_menu_width);
|
|
1769
|
-
}
|
|
1770
|
-
/**
|
|
1771
|
-
* Load menus for a specific module
|
|
1772
|
-
* @param module The module to load menus for
|
|
1773
|
-
*/
|
|
1774
|
-
loadMenusForModule(module) {
|
|
1775
|
-
if (!module?._id) {
|
|
1776
|
-
console.warn('🚫 SIDEBAR: Cannot load menus - no module ID provided');
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1779
|
-
console.log('📂 SIDEBAR: Loading menus for module:', module.syme_title, 'ID:', module._id);
|
|
1780
|
-
this.loadingMenus = true;
|
|
1781
|
-
this.menuLoadComplete = false;
|
|
1782
|
-
// Fetch menus for the selected module
|
|
1783
|
-
this.sidebarService?.getSidebarMenues({
|
|
1784
|
-
syme_id: module._id,
|
|
1785
|
-
sort: 'asc'
|
|
1786
|
-
})?.subscribe({
|
|
1787
|
-
next: (res) => {
|
|
1788
|
-
const menus = res?.data || [];
|
|
1789
|
-
this.core_system_menu = menus;
|
|
1790
|
-
this.loadingMenus = false;
|
|
1791
|
-
console.log('✅ SIDEBAR: Loaded', menus, 'menus for module:', module.syme_title);
|
|
1792
|
-
console.log('📋 SIDEBAR: Menu titles:', menus.map(m => m.syme_title).join(', '));
|
|
1793
|
-
// Modern ES2022+ pattern: Use Promise-based delay
|
|
1794
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
1795
|
-
delay(200).then(() => {
|
|
1796
|
-
this.menuLoadComplete = true;
|
|
1797
|
-
console.log('🎯 SIDEBAR: Menu loading complete for:', module.syme_title);
|
|
1798
|
-
});
|
|
1799
|
-
},
|
|
1800
|
-
error: (err) => {
|
|
1801
|
-
console.error('❌ SIDEBAR: Error loading menus for module:', module.syme_title, err);
|
|
1802
|
-
this.core_system_menu = [];
|
|
1803
|
-
this.loadingMenus = false;
|
|
1804
|
-
this.menuLoadComplete = true;
|
|
1805
|
-
}
|
|
1806
|
-
});
|
|
1807
|
-
}
|
|
1808
|
-
toggleSidebar() {
|
|
1809
|
-
this.isCollapsed = !this.isCollapsed;
|
|
1810
|
-
// Add animation class
|
|
1811
|
-
const sidebarElement = this.elementRef.nativeElement.querySelector('.cide-lyt-sidebar');
|
|
1812
|
-
sidebarElement.classList.add('animating');
|
|
1813
|
-
// Remove animation class after animation completes
|
|
1814
|
-
// Modern ES2022+ pattern: Use Promise-based delay
|
|
1815
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
1816
|
-
delay(300).then(() => {
|
|
1817
|
-
sidebarElement.classList.remove('animating');
|
|
1818
|
-
});
|
|
1819
|
-
// this.sidebarService.toggleSidebar();
|
|
1820
|
-
}
|
|
1821
|
-
toggleUserMenu() {
|
|
1822
|
-
this.showUserMenu = !this.showUserMenu;
|
|
1823
|
-
// Close other menus if open
|
|
1824
|
-
if (this.showUserMenu) {
|
|
1825
|
-
this.showOptions = false;
|
|
1826
|
-
this.showNotifications = false;
|
|
1827
|
-
}
|
|
1828
|
-
// Add subtle animation when opening menu
|
|
1829
|
-
if (this.showUserMenu) {
|
|
1830
|
-
this.animateMenuOpen('user-menu');
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
/**
|
|
1834
|
-
* Sets the active menu item with animation
|
|
1835
|
-
* @param moduleId The ID of the module to set as active
|
|
1836
|
-
*/
|
|
1837
|
-
setActiveMenu(moduleId) {
|
|
1838
|
-
if (this.activeModuleId === moduleId)
|
|
1839
|
-
return;
|
|
1840
|
-
// Set the active module in app state - the effect will handle local state updates
|
|
1841
|
-
const selectedModule = this.core_system_module.find(m => m._id === moduleId);
|
|
1842
|
-
if (selectedModule) {
|
|
1843
|
-
this.appState.setActiveModule(selectedModule);
|
|
1844
|
-
// The effect will automatically update this.activeModuleId and load menus
|
|
1845
|
-
}
|
|
1846
|
-
// You could trigger custom animations here
|
|
1847
|
-
}
|
|
1848
|
-
/**
|
|
1849
|
-
* Sets the active menu item by index with animation
|
|
1850
|
-
* @param index The index of the menu item
|
|
1851
|
-
*/
|
|
1852
|
-
setActiveMenuByIndex(index) {
|
|
1853
|
-
const selectedModule = this.core_system_module[index];
|
|
1854
|
-
if (selectedModule) {
|
|
1855
|
-
this.appState.setActiveModule(selectedModule);
|
|
1856
|
-
// The effect will automatically update this.activeModuleId and load menus
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
/**
|
|
1860
|
-
* Public method to set active module by ID from external components
|
|
1861
|
-
* This is useful when you want to programmatically set the active module
|
|
1862
|
-
* @param moduleId The ID of the module to set as active
|
|
1863
|
-
* @returns boolean indicating if the module was found and set as active
|
|
1864
|
-
*/
|
|
1865
|
-
setActiveModuleById(moduleId) {
|
|
1866
|
-
const module = this.core_system_module.find(m => m._id === moduleId);
|
|
1867
|
-
if (module) {
|
|
1868
|
-
this.setActiveMenu(moduleId);
|
|
1869
|
-
return true;
|
|
1870
|
-
}
|
|
1871
|
-
console.warn(`Module with ID "${moduleId}" not found in sidebar modules`);
|
|
1872
|
-
return false;
|
|
1873
|
-
}
|
|
1874
|
-
/**
|
|
1875
|
-
* Public method to get the currently active module ID
|
|
1876
|
-
* @returns The ID of the currently active module, or empty string if none
|
|
1877
|
-
*/
|
|
1878
|
-
getActiveModuleId() {
|
|
1879
|
-
return this.activeModuleId;
|
|
1880
|
-
}
|
|
1881
|
-
/**
|
|
1882
|
-
* Tracks hover state for enhanced visual feedback
|
|
1883
|
-
* @param moduleId The ID of the hovered module, or empty string for none
|
|
1884
|
-
*/
|
|
1885
|
-
onItemHover(moduleId) {
|
|
1886
|
-
this.hoverModuleId = moduleId;
|
|
1887
|
-
}
|
|
1888
|
-
/**
|
|
1889
|
-
* Adds subtle animations when opening dropdown menus
|
|
1890
|
-
* @param menuId The ID of the menu element
|
|
1891
|
-
*/
|
|
1892
|
-
animateMenuOpen(menuId) {
|
|
1893
|
-
const menuElement = this.elementRef.nativeElement.querySelector(`.${menuId}`);
|
|
1894
|
-
if (!menuElement)
|
|
1895
|
-
return;
|
|
1896
|
-
// First set initial state
|
|
1897
|
-
menuElement.style.opacity = '0';
|
|
1898
|
-
menuElement.style.transform = 'translateY(-8px) scale(0.95)';
|
|
1899
|
-
// Then animate to final state
|
|
1900
|
-
setTimeout(() => {
|
|
1901
|
-
menuElement.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
|
|
1902
|
-
menuElement.style.opacity = '1';
|
|
1903
|
-
menuElement.style.transform = 'translateY(0) scale(1)';
|
|
1904
|
-
}, 10);
|
|
1905
|
-
}
|
|
1906
|
-
/**
|
|
1907
|
-
* Updates tooltip position for more accurate placement
|
|
1908
|
-
* @param event MouseEvent from hovering
|
|
1909
|
-
* @param tooltip The tooltip element
|
|
1910
|
-
*/
|
|
1911
|
-
updateTooltipPosition(event, tooltipIndex) {
|
|
1912
|
-
const tooltip = this.elementRef.nativeElement.querySelectorAll('.nav-tooltip')[tooltipIndex];
|
|
1913
|
-
if (!tooltip)
|
|
1914
|
-
return;
|
|
1915
|
-
// Get the vertical position of the cursor
|
|
1916
|
-
const cursorY = event.clientY;
|
|
1917
|
-
// Set the tooltip position to match the cursor Y position
|
|
1918
|
-
tooltip.style.top = `${cursorY}px`;
|
|
1919
|
-
// Add a slight animation
|
|
1920
|
-
tooltip.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
|
|
1921
|
-
tooltip.style.transform = 'translateY(-50%)';
|
|
1922
|
-
}
|
|
1923
|
-
toggleNotificationsPanel() {
|
|
1924
|
-
this.showNotificationsPanel = !this.showNotificationsPanel;
|
|
1925
|
-
this.showNotifications = this.showNotificationsPanel;
|
|
1926
|
-
// Close other menus if open
|
|
1927
|
-
if (this.showNotificationsPanel) {
|
|
1928
|
-
this.showUserMenu = false;
|
|
1929
|
-
this.showOptions = false;
|
|
1930
|
-
}
|
|
1931
|
-
// Add animation when opening panel
|
|
1932
|
-
if (this.showNotificationsPanel) {
|
|
1933
|
-
this.animateMenuOpen('notifications-panel');
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
toggleTheme() {
|
|
1937
|
-
this.darkMode = !this.darkMode;
|
|
1938
|
-
// Add dark mode class to sidebar element
|
|
1939
|
-
const sidebarElement = this.elementRef.nativeElement.querySelector('.cide-lyt-sidebar');
|
|
1940
|
-
if (this.darkMode) {
|
|
1941
|
-
sidebarElement.classList.add('dark-mode');
|
|
1942
|
-
}
|
|
1943
|
-
else {
|
|
1944
|
-
sidebarElement.classList.remove('dark-mode');
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
toggleOptions() {
|
|
1948
|
-
this.showOptions = !this.showOptions;
|
|
1949
|
-
// Close other menus if open
|
|
1950
|
-
if (this.showOptions) {
|
|
1951
|
-
this.showUserMenu = false;
|
|
1952
|
-
this.showNotifications = false;
|
|
1953
|
-
}
|
|
1954
|
-
// Add subtle animation when opening menu
|
|
1955
|
-
if (this.showOptions) {
|
|
1956
|
-
this.animateMenuOpen('options-menu');
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
/**
|
|
1960
|
-
* Toggle old-style collapsed sections (for backwards compatibility)
|
|
1961
|
-
*/
|
|
1962
|
-
toggleCollapsedSection(section) {
|
|
1963
|
-
this.collapsedSections[section] = !this.collapsedSections[section];
|
|
1964
|
-
}
|
|
1965
|
-
isSectionCollapsed(section) {
|
|
1966
|
-
return this.collapsedSections[section];
|
|
1967
|
-
}
|
|
1968
|
-
onSearch() {
|
|
1969
|
-
// Reset animation trigger when searching
|
|
1970
|
-
this.resetAnimations();
|
|
1971
|
-
// Mock search functionality - would be integrated with actual search service
|
|
1972
|
-
if (this.searchText.trim() === '') {
|
|
1973
|
-
this.searchResults = [];
|
|
1974
|
-
return;
|
|
1975
|
-
}
|
|
1976
|
-
// Example search results - would come from actual data source
|
|
1977
|
-
const allItems = [
|
|
1978
|
-
{ icon: 'settings', title: 'General Settings' },
|
|
1979
|
-
{ icon: 'payments', title: 'Billing & Payments' },
|
|
1980
|
-
{ icon: 'shield', title: 'Security Settings' },
|
|
1981
|
-
{ icon: 'person', title: 'Profile Settings' },
|
|
1982
|
-
{ icon: 'notifications', title: 'Notification Preferences' },
|
|
1983
|
-
{ icon: 'integration_instructions', title: 'API Integrations' },
|
|
1984
|
-
{ icon: 'key', title: 'API Keys' },
|
|
1985
|
-
{ icon: 'groups', title: 'Team Members' },
|
|
1986
|
-
{ icon: 'devices', title: 'Connected Devices' },
|
|
1987
|
-
{ icon: 'language', title: 'Language Settings' },
|
|
1988
|
-
];
|
|
1989
|
-
// Simple text search on title
|
|
1990
|
-
this.searchResults = allItems.filter(item => item.title.toLowerCase().includes(this.searchText.toLowerCase()));
|
|
1991
|
-
}
|
|
1992
|
-
onScroll(event) {
|
|
1993
|
-
const scrollTop = event.target.scrollTop;
|
|
1994
|
-
const sections = this.elementRef.nativeElement.querySelectorAll('.sidebar-section');
|
|
1995
|
-
// Reset animations if scrolling back to top
|
|
1996
|
-
if (scrollTop < 10) {
|
|
1997
|
-
this.resetAnimations();
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
|
-
// Animate sections as they come into view
|
|
2001
|
-
sections.forEach((section, index) => {
|
|
2002
|
-
if (index >= this.animateSections.length)
|
|
2003
|
-
return;
|
|
2004
|
-
const sectionTop = section.offsetTop;
|
|
2005
|
-
const scrollPosition = scrollTop + 300; // Offset to trigger animation before fully visible
|
|
2006
|
-
// When scrolling down and section comes into view
|
|
2007
|
-
if (scrollPosition > sectionTop && !this.scrolledPast[index] && scrollTop > this.lastScrollTop) {
|
|
2008
|
-
this.animateSections[index] = true;
|
|
2009
|
-
this.scrolledPast[index] = true;
|
|
2010
|
-
}
|
|
2011
|
-
// When scrolling up to top, reset animation
|
|
2012
|
-
if (scrollTop < 50 && this.lastScrollTop > scrollTop) {
|
|
2013
|
-
this.resetAnimations();
|
|
2014
|
-
}
|
|
2015
|
-
});
|
|
2016
|
-
this.lastScrollTop = scrollTop;
|
|
2017
|
-
}
|
|
2018
|
-
resetAnimations() {
|
|
2019
|
-
this.animateSections = [false, false, false, false, false];
|
|
2020
|
-
this.scrolledPast = [false, false, false, false, false];
|
|
2021
|
-
// Re-trigger animations
|
|
2022
|
-
setTimeout(() => {
|
|
2023
|
-
this.animateSections = [true, true, true, true, true];
|
|
2024
|
-
}, 50);
|
|
2025
|
-
}
|
|
2026
|
-
onDocumentClick(event) {
|
|
2027
|
-
// Close dropdown menus when clicking outside
|
|
2028
|
-
const clickedInside = this.elementRef.nativeElement.contains(event.target);
|
|
2029
|
-
if (!clickedInside) {
|
|
2030
|
-
this.showUserMenu = false;
|
|
2031
|
-
this.showOptions = false;
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidebarWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2035
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideLytSidebarWrapperComponent, isStandalone: true, selector: "cide-lyt-sidebar-wrapper", host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "<nav class=\"cide-lyt-sidebar tw-flex tw-h-full tw-select-none\" [class.collapsed]=\"isCollapsed\">\n <!-- First tier sidebar (Icon only) -->\n <div class=\"cide-lyt-stack tw-h-full tw-flex tw-flex-col tw-items-center tw-border-r tw-border-gray-100\">\n <!-- Scrollable content -->\n <div class=\"sidebar-scroll-content\">\n <!-- Collapse/Expand toggle -->\n <div class=\"nav-item collapse-toggle\" tabindex=\"0\" (click)=\"toggleSidebar()\"\n (keydown.enter)=\"toggleSidebar()\">\n <div class=\"nav-tooltip\">{{isCollapsed ? 'Expand' : 'Collapse'}} Sidebar</div>\n <cide-ele-icon size=\"xs\" type=\"box\" class=\"tw-text-gray-500 hover:tw-text-gray-700\">\n {{isCollapsed ? 'chevron_right' : 'chevron_left'}}\n </cide-ele-icon>\n </div> <!-- Main navigation icons - Enhanced Design -->\n <div class=\"tw-flex tw-flex-col tw-gap-4\">\n @for (core_system_module_item of core_system_module; track $index) {\n <div class=\"nav-item tw-relative tw-group\" (click)=\"setActiveMenu(core_system_module_item._id)\"\n [cideEleTooltip]=\"core_system_module_item.syme_title\" [tooltipShowArrow]=\"true\"\n tooltipPlacement=\"right\"\n [ngClass]=\"{'nav-item-active': activeModuleId === core_system_module_item._id}\"\n (mouseenter)=\"onItemHover(core_system_module_item._id)\" (mouseleave)=\"onItemHover('')\"\n [tabindex]=\"$index\" (keydown.enter)=\"setActiveMenu(core_system_module_item._id)\">\n\n <!-- Notification Badge with enhanced design -->\n @if (core_system_module_item?.syme_ping) {\n <div class=\"nav-badge tw-absolute -tw-top-1 -tw-right-1 tw-w-1.5 tw-h-1.5 tw-rounded-full tw-animate-ping\"\n style=\"background-color: var(--cide-theme-error-color);\">\n </div>\n }\n\n <!-- Enhanced icon with better visual effects -->\n <div\n class=\"tw-p-2 tw-rounded-xl tw-transition-all tw-duration-200 tw-ease-in-out\n group-hover:tw-bg-blue-50 group-hover:tw-shadow-md group-hover:tw-scale-105\n tw-border-2 tw-border-transparent\n {{activeModuleId === core_system_module_item._id ? 'tw-bg-blue-100 tw-border-blue-200 tw-shadow-sm' : 'tw-bg-gray-50'}}\">\n <cide-ele-icon type=\"box\" size=\"xs\"\n class=\"tw-transition-all tw-duration-200 tw-ease-in-out\n {{activeModuleId === core_system_module_item._id ? 'tw-text-blue-600' : 'tw-text-gray-600 group-hover:tw-text-blue-500'}}\">\n {{core_system_module_item?.syme_icon || 'dashboard'}}\n </cide-ele-icon>\n </div>\n\n <!-- Modern active indicator -->\n @if (activeModuleId === core_system_module_item._id) {\n <div\n class=\"nav-indicator tw-absolute tw-left-0 tw-top-1/2 tw-transform -tw-translate-y-1/2 tw-w-1 tw-h-8 tw-bg-blue-500 tw-rounded-r-full tw-shadow-lg tw-transition-all tw-duration-300\">\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Bottom section with enhanced theme toggle and user avatar -->\n <div class=\"tw-flex tw-flex-col tw-items-center tw-gap-3 tw-mt-auto tw-pb-4\">\n <!-- Theme toggle with modern design -->\n <div class=\"nav-item tw-group\">\n <div class=\"nav-tooltip\">Toggle Theme</div>\n <button class=\"theme-toggle\" (click)=\"toggleTheme()\" (keydown.enter)=\"toggleTheme()\"\n (keydown.space)=\"toggleTheme()\" tabindex=\"0\" role=\"button\" aria-label=\"Toggle theme\">\n <cide-ele-icon type=\"box\" size=\"sm\">\n {{darkMode ? 'light_mode' : 'dark_mode'}}\n </cide-ele-icon>\n </button>\n </div>\n\n <!-- User avatar with dropdown -->\n <div class=\"tw-relative user-dropdown\" (click)=\"toggleUserMenu()\" (keydown.enter)=\"toggleUserMenu()\"\n (keydown.space)=\"toggleUserMenu()\" tabindex=\"0\" role=\"button\" aria-label=\"Toggle user menu\"\n [attr.aria-expanded]=\"showUserMenu\">\n <div class=\"nav-tooltip\">Your Profile</div>\n <div class=\"user-avatar tw-w-10 tw-h-10 tw-rounded-full tw-overflow-hidden tw-bg-gray-200 tw-border-2 tw-shadow-sm tw-cursor-pointer hover:tw-shadow\"\n style=\"border-color: var(--cide-theme-sidebar-color);\">\n <img src=\"data:image/png;base64,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\"\n alt=\"User\" class=\"tw-w-full tw-h-full tw-object-cover\"\n onerror=\"this.src='data:image/png;base64,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'\">\n <!-- Modern Status Indicator Circle -->\n <div class=\"tw-absolute -tw-bottom-0.5 -tw-right-0.5 tw-w-3.5 tw-h-3.5 tw-rounded-full tw-border-2 tw-border-white tw-shadow-sm tw-transition-all tw-duration-200\n {{getUserStatusClass()}}\" [cideEleTooltip]=\"getUserStatusText()\"\n tooltipPlacement=\"left\">\n <!-- Pulse animation for active status -->\n <div class=\"tw-absolute tw-inset-0 tw-rounded-full tw-animate-pulse tw-opacity-50\n {{getUserStatusClass()}}\"></div>\n </div>\n </div>\n\n <!-- Enhanced User dropdown menu with modern status options -->\n <div *ngIf=\"showUserMenu\"\n class=\"user-menu tw-absolute tw-left-full tw-bottom-0 tw-mt-2 tw-ml-2 tw-w-72 tw-rounded-xl tw-shadow-2xl tw-bg-white tw-ring-1 tw-ring-black/5 tw-divide-y tw-divide-gray-100 tw-z-50 tw-backdrop-blur-sm\">\n\n <!-- Enhanced User Info Section -->\n <div class=\"tw-p-5 tw-bg-gradient-to-br tw-from-blue-50 tw-to-indigo-50\">\n <div class=\"tw-flex tw-items-center tw-gap-4\">\n <div class=\"tw-relative\">\n <div\n class=\"tw-w-12 tw-h-12 tw-rounded-xl tw-bg-gradient-to-br tw-from-blue-500 tw-via-purple-500 tw-to-indigo-600 tw-flex tw-items-center tw-justify-center tw-text-white tw-font-semibold tw-text-sm tw-shadow-lg\">\n JD\n </div>\n <!-- Enhanced status indicator -->\n <div class=\"tw-absolute -tw-bottom-1 -tw-right-1 tw-w-4 tw-h-4 tw-rounded-full tw-border-2 tw-border-white tw-shadow-md tw-transition-all tw-duration-300\"\n [class]=\"getUserStatusClass()\" [title]=\"getUserStatusText()\">\n <div class=\"tw-absolute tw-inset-0 tw-rounded-full tw-animate-pulse tw-opacity-50\"\n [class]=\"getUserStatusClass()\"></div>\n </div>\n </div>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <div class=\"tw-text-base tw-font-semibold tw-text-gray-900 tw-truncate\">John Doe</div>\n <div class=\"tw-text-sm tw-text-gray-600 tw-truncate\">john.doe@company.com</div>\n <div class=\"tw-text-xs tw-text-gray-500 tw-mt-1 tw-flex tw-items-center tw-gap-1\">\n <div class=\"tw-w-1.5 tw-h-1.5 tw-rounded-full tw-bg-green-500 tw-animate-pulse\">\n </div>\n <span>Active now</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Enhanced Status Selection Section -->\n <div class=\"tw-p-4\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-mb-3\">\n <h4 class=\"tw-text-sm tw-font-semibold tw-text-gray-800 tw-flex tw-items-center tw-gap-2\">\n <div\n class=\"tw-w-5 tw-h-5 tw-rounded-md tw-bg-blue-100 tw-flex tw-items-center tw-justify-center\">\n <cide-ele-icon type=\"none\" size=\"2xs\"\n class=\"tw-text-blue-600\">radio_button_checked</cide-ele-icon>\n </div>\n Set Status\n </h4>\n <span\n class=\"tw-text-xs tw-text-gray-500 tw-bg-gray-100 tw-px-2 tw-py-1 tw-rounded-full\">Auto-away\n in 5min</span>\n </div>\n\n <div class=\"tw-space-y-1\">\n <button (click)=\"setUserStatus('online')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-green-50 tw-group\"\n [class.tw-bg-green-50]=\"userStatusService.userStatus().status === 'online'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'online'\"\n [class.tw-ring-green-200]=\"userStatusService.userStatus().status === 'online'\">\n <div class=\"tw-relative\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-green-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n <div class=\"tw-absolute tw-inset-0 tw-rounded-full tw-bg-green-500 tw-animate-ping tw-opacity-75\"\n *ngIf=\"userStatusService.userStatus().status === 'online'\"></div>\n </div>\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Online</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'online'\" type=\"none\"\n size=\"xs\" class=\"tw-text-green-600\">check</cide-ele-icon>\n </button>\n\n <button (click)=\"setUserStatus('away')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-yellow-50 tw-group\"\n [class.tw-bg-yellow-50]=\"userStatusService.userStatus().status === 'away'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'away'\"\n [class.tw-ring-yellow-200]=\"userStatusService.userStatus().status === 'away'\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-yellow-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Away</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'away'\" type=\"none\"\n size=\"xs\" class=\"tw-text-yellow-600\">check</cide-ele-icon>\n </button>\n\n <button (click)=\"setUserStatus('busy')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-orange-50 tw-group\"\n [class.tw-bg-orange-50]=\"userStatusService.userStatus().status === 'busy'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'busy'\"\n [class.tw-ring-orange-200]=\"userStatusService.userStatus().status === 'busy'\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-orange-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Busy</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'busy'\" type=\"none\"\n size=\"xs\" class=\"tw-text-orange-600\">check</cide-ele-icon>\n </button>\n\n <button (click)=\"setUserStatus('do-not-disturb')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-red-50 tw-group\"\n [class.tw-bg-red-50]=\"userStatusService.userStatus().status === 'do-not-disturb'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'do-not-disturb'\"\n [class.tw-ring-red-200]=\"userStatusService.userStatus().status === 'do-not-disturb'\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-red-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Do not disturb</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'do-not-disturb'\"\n type=\"none\" size=\"xs\" class=\"tw-text-red-600\">check</cide-ele-icon>\n </button>\n </div>\n </div>\n\n <!-- Enhanced Quick Actions Section -->\n <div class=\"tw-p-4 tw-bg-gray-50/50\">\n <h4\n class=\"tw-text-sm tw-font-semibold tw-text-gray-800 tw-mb-3 tw-flex tw-items-center tw-gap-2\">\n <div\n class=\"tw-w-5 tw-h-5 tw-rounded-md tw-bg-gray-100 tw-flex tw-items-center tw-justify-center\">\n <cide-ele-icon type=\"none\" size=\"2xs\" class=\"tw-text-gray-600\">bolt</cide-ele-icon>\n </div>\n Quick Actions\n </h4>\n <div class=\"tw-space-y-1\">\n <a href=\"#\"\n class=\"quick-action tw-group tw-flex tw-items-center tw-px-3 tw-py-2 tw-text-sm tw-text-gray-700 tw-rounded-lg hover:tw-bg-white tw-transition-all tw-duration-200\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-lg tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3 group-hover:tw-bg-blue-200 tw-transition-colors\">\n <cide-ele-icon type=\"none\" size=\"xs\" class=\"tw-text-blue-600\">person</cide-ele-icon>\n </div>\n <span class=\"tw-flex-1\">View Profile</span>\n <cide-ele-icon type=\"none\" size=\"xs\"\n class=\"tw-text-gray-400 group-hover:tw-text-gray-600 tw-transition-colors\">chevron_right</cide-ele-icon>\n </a>\n <a href=\"#\"\n class=\"quick-action tw-group tw-flex tw-items-center tw-px-3 tw-py-2 tw-text-sm tw-text-gray-700 tw-rounded-lg hover:tw-bg-white tw-transition-all tw-duration-200\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-lg tw-bg-gray-100 tw-flex tw-items-center tw-justify-center tw-mr-3 group-hover:tw-bg-gray-200 tw-transition-colors\">\n <cide-ele-icon type=\"none\" size=\"xs\"\n class=\"tw-text-gray-600\">settings</cide-ele-icon>\n </div>\n <span class=\"tw-flex-1\">Preferences</span>\n <cide-ele-icon type=\"none\" size=\"xs\"\n class=\"tw-text-gray-400 group-hover:tw-text-gray-600 tw-transition-colors\">chevron_right</cide-ele-icon>\n </a>\n </div>\n </div>\n\n <!-- Enhanced Sign Out Section -->\n <div class=\"tw-p-4\">\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-3 tw-py-2.5 tw-text-sm tw-text-red-600 tw-rounded-lg hover:tw-bg-red-50 tw-transition-all tw-duration-200 tw-font-medium\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-lg tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3 group-hover:tw-bg-red-200 tw-transition-colors\">\n <cide-ele-icon type=\"none\" size=\"xs\" class=\"tw-text-red-600\">logout</cide-ele-icon>\n </div>\n <span class=\"tw-flex-1\">Sign out</span>\n </a>\n </div>\n </div>\n </div>\n </div>\n </div> <!-- Second tier sidebar (Expanded view) -->\n <div class=\"cide-lyt-sidebar-menu tw-h-full tw-overflow-hidden tw-shadow-sm\">\n <div class=\"tw-flex tw-flex-col tw-h-full\">\n <!-- Header section with search and options -->\n <div class=\"sidebar-header tw-p-4 tw-border-b tw-border-gray-100\">\n <!-- Title with back button - Clean layout without online indicator -->\n <div class=\"tw-flex tw-items-center tw-gap-3 tw-mb-4\">\n <button\n class=\"back-button tw-p-1.5 tw-rounded-lg tw-text-gray-500 hover:tw-bg-gray-100 hover:tw-text-gray-700 tw-transition-colors tw-flex-shrink-0\"\n [cideEleTooltip]=\"'Back to home'\" tooltipPlacement=\"bottom\">\n <cide-ele-icon type=\"none\" size=\"xs\" class=\"tw-transition-transform hover:tw-scale-110\">{{\n getActiveModuleIcon() }}</cide-ele-icon>\n </button>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <h2 class=\"tw-text-sm tw-font-semibold tw-text-gray-900 tw-truncate tw-leading-5\">\n {{ appState.getActiveModuleTitle() || 'Menu' }}\n </h2>\n <p class=\"tw-text-xs tw-text-gray-500 tw-truncate tw-mt-0.5\">\n Module Dashboard\n </p>\n </div>\n </div>\n\n <!-- Search and options -->\n <div class=\"tw-flex tw-items-center tw-justify-between\">\n <div class=\"search-wrapper tw-relative tw-flex-1\">\n <cide-ele-input type=\"text\" leadingIcon=\"search\" size=\"xs\" placeholder=\"Search settings...\"\n [clearInput]=\"true\" [(ngModel)]=\"searchText\" (input)=\"onSearch()\">\n </cide-ele-input>\n <div\n class=\"search-shortcut tw-absolute tw-right-3 tw-top-1/2 -tw-translate-y-1/2 tw-bg-gray-100 tw-text-gray-500 tw-rounded tw-px-1.5 tw-py-0.5 tw-text-xs\">\n \u2318K</div>\n </div>\n <button\n class=\"tw-ml-2 tw-p-1.5 tw-rounded-lg tw-text-gray-500 hover:tw-bg-gray-100 hover:tw-text-gray-700 tw-transition-colors\"\n (click)=\"toggleOptions()\">\n <cide-ele-icon type=\"none\" size=\"xs\">more_vert</cide-ele-icon>\n </button>\n\n <!-- Options dropdown menu -->\n <div *ngIf=\"showOptions\"\n class=\"options-menu tw-absolute tw-right-4 tw-top-16 tw-mt-2 tw-w-48 tw-rounded-md tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5 tw-divide-y tw-divide-gray-100 tw-z-10\">\n <div class=\"tw-py-1\">\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500 tw-group-hover:tw-text-gray-600\"\n type=\"none\" size=\"xs\">refresh</cide-ele-icon>\n Refresh\n </a>\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500 tw-group-hover:tw-text-gray-600\"\n type=\"none\" size=\"xs\">sync</cide-ele-icon>\n Sync settings\n </a>\n </div>\n <div class=\"tw-py-1\">\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500 tw-group-hover:tw-text-gray-600\"\n type=\"none\" size=\"xs\">help_outline</cide-ele-icon>\n Help & support\n </a>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Menu items with categories -->\n <div class=\"sidebar-content tw-overflow-y-auto tw-flex-1\" (scroll)=\"onScroll($event)\">\n <!-- Search results if searching -->\n <div *ngIf=\"searchText\" class=\"sidebar-section tw-p-4\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-mb-2\">\n <h3 class=\"tw-text-xs tw-font-semibold tw-text-gray-500\">SEARCH RESULTS</h3>\n <span class=\"tw-text-xs tw-text-gray-500\">{{searchResults.length}} items</span>\n </div>\n\n <div *ngIf=\"searchResults.length > 0\" class=\"tw-space-y-1\">\n <a *ngFor=\"let item of searchResults\" href=\"#\"\n class=\"menu-item tw-flex tw-items-center tw-px-3 tw-py-2 tw-rounded-md hover:tw-bg-gray-50\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500\" type=\"none\"\n size=\"xs\">{{item.icon}}</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">{{item.title}}</span>\n </a>\n </div>\n\n <div *ngIf=\"searchResults.length === 0\" class=\"tw-p-4 tw-text-center\">\n <div class=\"tw-flex tw-justify-center tw-mb-3\">\n <cide-ele-icon class=\"tw-text-gray-400\" type=\"none\" size=\"md\">search_off</cide-ele-icon>\n </div>\n <p class=\"tw-text-sm tw-text-gray-500\">No results for \"{{searchText}}\"</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Try another search term</p>\n </div>\n </div>\n\n <!-- Only show these sections if not searching -->\n <ng-container *ngIf=\"!searchText\">\n <!-- Loading state -->\n <div *ngIf=\"loadingMenus\" class=\"tw-p-4 tw-text-center\">\n <div class=\"tw-flex tw-justify-center tw-mb-3\">\n <cide-ele-spinner variant=\"circle\" size=\"xs\"></cide-ele-spinner>\n </div>\n <p class=\"tw-text-sm tw-text-gray-500\">Loading menus...</p>\n </div>\n\n <!-- Dynamic menu tree -->\n <div *ngIf=\"!loadingMenus && menuLoadComplete\" class=\"sidebar-section tw-p-4\"\n [class.animate-in]=\"animateSections[0]\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: selectedModuleMenus, level: 0}\"></ng-container>\n </div>\n\n <!-- Empty state -->\n <div *ngIf=\"!loadingMenus && menuLoadComplete && selectedModuleMenus.length === 0\"\n class=\"tw-p-4 tw-text-center\">\n <div class=\"tw-flex tw-justify-center tw-mb-3\">\n <cide-ele-icon class=\"tw-text-gray-400\" type=\"none\" size=\"md\">folder_open</cide-ele-icon>\n </div>\n <p class=\"tw-text-sm tw-text-gray-500\">No menus available</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Select a module to view its menus</p>\n </div>\n </ng-container>\n\n <!-- Recursive menu template -->\n <ng-template #recursiveMenu let-menus let-level=\"level\">\n <div [class.tw-ml-0]=\"level > 0\">\n <div *ngFor=\"let menu of menus\" class=\"tw-mb-2\">\n <!-- Title type items (section headers) -->\n <div *ngIf=\"menu.syme_type === 'title'\" class=\"tw-mb-3\">\n <h3 class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-mb-2\">{{\n menu.syme_title }}</h3>\n <!-- Render children of title -->\n <ng-container *ngIf=\"menu.children && menu.children.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: menu.children, level: level + 1}\"></ng-container>\n </ng-container>\n </div>\n\n <!-- Section type items (collapsible sections) -->\n <div *ngIf=\"menu.syme_type === 'section'\" class=\"tw-mb-2\">\n <!-- Section header (clickable to expand/collapse) -->\n <button (click)=\"toggleSection(menu._id)\" (keydown.enter)=\"toggleSection(menu._id)\"\n (keydown.space)=\"toggleSection(menu._id)\"\n class=\"section-header tw-w-full tw-flex tw-items-center tw-px-3 tw-py-2 tw-rounded-md hover:tw-bg-gray-50 tw-cursor-pointer tw-transition-colors tw-text-left\"\n [class.tw-bg-blue-50]=\"isSectionExpanded(menu._id)\" type=\"button\" tabindex=\"0\"\n role=\"button\" [attr.aria-expanded]=\"isSectionExpanded(menu._id)\"\n [attr.aria-label]=\"'Toggle ' + menu.syme_title + ' section'\">\n\n <!-- Section Icon (left side, like other menu items) -->\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500\" type=\"none\" size=\"xs\">{{\n menu.syme_icon || 'folder' }}</cide-ele-icon>\n\n <!-- Section Title -->\n <span class=\"tw-text-sm tw-font-medium tw-text-gray-800\">{{ menu.syme_title\n }}</span>\n\n <!-- Right side icons container -->\n <div class=\"tw-ml-auto tw-flex tw-items-center tw-space-x-2\">\n <!-- Child Count Badge -->\n <span *ngIf=\"menu.children && menu.children.length > 0\"\n class=\"tw-px-2 tw-py-0.5 tw-bg-gray-200 tw-text-gray-600 tw-rounded-full tw-text-xs tw-font-medium\">\n {{ menu.children.length }}\n </span>\n\n <!-- Expand/Collapse Icon (right side) -->\n <cide-ele-icon class=\"tw-text-gray-400 tw-transition-transform tw-duration-200\"\n [class.tw-rotate-90]=\"isSectionExpanded(menu._id)\" type=\"none\"\n size=\"2xs\">chevron_right</cide-ele-icon>\n </div>\n </button>\n\n <!-- Section Content (collapsible) -->\n <div *ngIf=\"isSectionExpanded(menu._id) && menu.children && menu.children.length > 0\"\n class=\"section-content tw-mt-2 tw-ml-2 tw-border-l tw-border-gray-200 tw-pl-3\"\n [@slideInOut]=\"isSectionExpanded(menu._id) ? 'in' : 'out'\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: menu.children, level: level + 1}\"></ng-container>\n </div>\n </div>\n\n <!-- Menu type items (clickable links) -->\n <a *ngIf=\"menu.syme_type === 'menu'\" (click)=\"onMenuClick(menu)\"\n (keydown.enter)=\"onMenuClick(menu)\" (keydown.space)=\"onMenuClick(menu)\" tabindex=\"0\"\n role=\"button\"\n class=\"menu-item tw-flex tw-items-center tw-px-1 tw-py-1 tw-rounded-md hover:tw-bg-gray-50 tw-cursor-pointer tw-transition-colors\">\n <cide-ele-icon *ngIf=\"menu.syme_icon\" class=\"tw-mr-3 tw-text-gray-500\" type=\"none\"\n size=\"xs\">{{ menu.syme_icon }}</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">{{ menu.syme_title }}</span>\n <cide-ele-icon *ngIf=\"menu.children && menu.children.length > 0\"\n class=\"tw-ml-auto tw-text-gray-400\" type=\"none\"\n size=\"2xs\">chevron_right</cide-ele-icon>\n </a>\n\n <!-- Render nested children for menu items -->\n <ng-container\n *ngIf=\"menu.syme_type === 'menu' && menu.children && menu.children.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: menu.children, level: level + 1}\"></ng-container>\n </ng-container>\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- Notification panel (overlays content when shown) -->\n <div *ngIf=\"showNotifications\" class=\"notification-panel tw-absolute tw-inset-0 tw-bg-white tw-z-20\">\n <div class=\"tw-p-4 tw-border-b tw-border-gray-100 tw-flex tw-justify-between tw-items-center\">\n <h3 class=\"tw-text-sm tw-font-medium\">Notifications</h3>\n <button class=\"tw-p-1.5 tw-rounded-lg tw-text-gray-500 hover:tw-bg-gray-100\"\n (click)=\"toggleNotificationsPanel()\">\n <cide-ele-icon type=\"none\" size=\"xs\">close</cide-ele-icon>\n </button>\n </div>\n\n <div class=\"tw-p-4\">\n <div class=\"tw-flex tw-justify-between tw-items-center tw-mb-4\">\n <div class=\"tw-text-xs tw-text-gray-500\">Today</div>\n <button class=\"tw-text-xs tw-text-blue-500\">Mark all as read</button>\n </div>\n\n <div class=\"tw-space-y-3\">\n <div\n class=\"notification-item tw-p-3 tw-bg-blue-50 tw-rounded-lg tw-border-l-4 tw-border-blue-500\">\n <div class=\"tw-flex tw-gap-3\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-full tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-text-blue-500\">\n <cide-ele-icon type=\"none\" size=\"xs\">notifications</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-900\">New feature available</div>\n <div class=\"tw-text-xs tw-text-gray-500 tw-mt-1\">Try our new analytics dashboard\n with improved visualizations.</div>\n <div class=\"tw-text-xs tw-text-gray-400 tw-mt-2\">Just now</div>\n </div>\n </div>\n </div>\n\n <div class=\"notification-item tw-p-3 tw-rounded-lg tw-border-l-4 tw-border-transparent\">\n <div class=\"tw-flex tw-gap-3\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-full tw-bg-gray-100 tw-flex tw-items-center tw-justify-center tw-text-gray-500\">\n <cide-ele-icon type=\"none\" size=\"xs\">person_add</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-900\">New team member</div>\n <div class=\"tw-text-xs tw-text-gray-500 tw-mt-1\">Jane Smith has joined your team.\n </div>\n <div class=\"tw-text-xs tw-text-gray-400 tw-mt-2\">2 hours ago</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Storage info at bottom -->\n <div class=\"sidebar-footer tw-p-4 tw-border-t tw-border-gray-100\">\n <!-- for info display -->\n </div>\n </div>\n </div>\n\n <!-- Resizer -->\n <div parentElementSelector=\"#cide-lyt-sidebar-page-inner-wrapper\"\n [minPrevSize]=\"sidebarSetupData.cide_lyt_sidebar_width\" prevElementSelector=\"#cide-lyt-sidebar-page\"\n nextElementSelector=\"#cide-lyt-page-wrapper\" cideEleResizer direction=\"horizontal\">\n <div class=\"cide-lyt-devider-track tw-w-full tw-h-full\"></div>\n </div>\n</nav>", styles: [".cide-lyt-sidebar{display:flex;box-shadow:0 4px 12px #0000000d;overflow:hidden;background-color:var(--cide-theme-sidebar-color);--sidebar-tooltip-bg: var(--cide-theme-dark-color);--sidebar-tooltip-color: var(--cide-theme-light-color);--sidebar-shadow-color: rgba(0, 0, 0, .05);transition:width .3s cubic-bezier(.4,0,.2,1);max-height:100%;isolation:isolate;will-change:width;position:relative}.cide-lyt-sidebar.collapsed{animation:collapseEffect .3s forwards}.cide-lyt-sidebar:not(.collapsed){animation:expandEffect .3s forwards}@keyframes collapseEffect{0%{box-shadow:0 4px 12px var(--sidebar-shadow-color)}to{box-shadow:0 2px 8px var(--sidebar-shadow-color)}}@keyframes expandEffect{0%{box-shadow:0 2px 8px var(--sidebar-shadow-color)}to{box-shadow:0 4px 12px var(--sidebar-shadow-color)}}.cide-lyt-sidebar.animating{transition:width .3s cubic-bezier(.25,.46,.45,.94)}.cide-lyt-sidebar.collapsed .cide-lyt-sidebar-menu{width:0;opacity:0;visibility:hidden;transform:translate(-10px)}.cide-lyt-stack{background:linear-gradient(to bottom,var(--cide-theme-sidebar-color),var(--cide-theme-light-color));z-index:10;border-right:1px solid var(--cide-theme-light-color);display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:0 0 1.5rem;transition:all .3s cubic-bezier(.4,0,.2,1);overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:var(--cide-theme-color-brand-primary) transparent;position:relative;will-change:transform;scroll-behavior:smooth;box-shadow:inset -1px 0 0 var(--sidebar-shadow-color)}.cide-lyt-stack:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:30px;background:linear-gradient(to top,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.cide-lyt-stack:before{content:\"\";position:absolute;top:0;left:0;right:0;height:30px;background:linear-gradient(to bottom,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.cide-lyt-stack.scrolled-down:before{opacity:1}.cide-lyt-stack.scrolled-up:after{opacity:1}.cide-lyt-stack{position:relative;z-index:1;-webkit-overflow-scrolling:touch;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:var(--cide-theme-color-brand-primary) transparent}.cide-lyt-stack::-webkit-scrollbar{width:2px}.cide-lyt-stack::-webkit-scrollbar-track{background:transparent;margin:10px 0}.cide-lyt-stack::-webkit-scrollbar-thumb{background:var(--cide-theme-color-brand-primary);border-radius:4px;box-shadow:0 0 6px var(--cide-theme-color-brand-primary)}.cide-lyt-stack:hover::-webkit-scrollbar-thumb{background:var(--cide-theme-color-brand-primary);box-shadow:0 0 10px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.4)}.cide-lyt-stack:hover::-webkit-scrollbar{width:3px}.cide-lyt-stack:before,.cide-lyt-stack:after,.sidebar-content:before,.sidebar-content:after,.notification-panel:before,.notification-panel:after,.user-menu:before,.user-menu:after,.options-menu:before,.options-menu:after{content:\"\";position:absolute;left:0;right:0;height:24px;z-index:5;pointer-events:none;opacity:0;transition:opacity .3s ease}.cide-lyt-stack:before,.sidebar-content:before,.notification-panel:before,.user-menu:before,.options-menu:before{top:0;background:linear-gradient(to bottom,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),#fff0)}.cide-lyt-stack:after,.sidebar-content:after,.notification-panel:after,.user-menu:after,.options-menu:after{bottom:0;background:linear-gradient(to top,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),#fff0)}.cide-lyt-stack.is-scrollable-top:before,.sidebar-content.is-scrollable-top:before,.notification-panel.is-scrollable-top:before,.user-menu.is-scrollable-top:before,.options-menu.is-scrollable-top:before{opacity:1}.cide-lyt-stack.is-scrollable-bottom:after,.sidebar-content.is-scrollable-bottom:after,.notification-panel.is-scrollable-bottom:after,.user-menu.is-scrollable-bottom:after,.options-menu.is-scrollable-bottom:after{opacity:1}.collapse-toggle{margin-bottom:.5rem;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);z-index:5;border-radius:50%;overflow:hidden}.collapse-toggle:before{content:\"\";position:absolute;inset:0;background:var(--cide-theme-label-color);opacity:0;transition:opacity .2s ease;z-index:-1}.collapse-toggle:hover{transform:scale(1.1)}.collapse-toggle:hover:before{opacity:1}.collapse-toggle:active{transform:scale(.95);transition:transform .1s ease}.collapse-toggle:focus-visible{outline:2px solid var(--cide-theme-color-brand-primary);outline-offset:2px}.sidebar-scroll-content{width:100%;padding-top:.75rem;position:relative;z-index:1;overflow-y:auto;overflow-x:hidden;flex-grow:1;display:flex;flex-direction:column;align-items:center;padding:1rem 0;gap:.5rem;max-height:calc(100% - 8rem)}.nav-item{position:relative;width:30px;height:30px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;margin:4px 0;border-radius:10px;box-shadow:0 0 #3b82f600;transform-origin:center;overflow:hidden;color:var(--cide-theme-text-color)}.nav-item:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at center,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.08),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.01));opacity:0;transition:opacity .3s ease;z-index:-1}.nav-item:hover{background-color:color-mix(in srgb,var(--cide-theme-light-color) 90%,transparent);transform:translateY(-1px);box-shadow:0 2px 6px var(--cide-theme-shadow-color)}.nav-item:hover:before{opacity:1}.nav-item:active{transition:all .1s ease;transform:translateY(-1px);box-shadow:0 2px 6px var(--cide-theme-shadow-color)}.nav-item:active{transform:translateY(1px);transition:all .1s ease}.nav-item-active{background:linear-gradient(135deg,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.08),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.12));box-shadow:0 2px 8px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.15);transform:translateZ(0);color:var(--cide-theme-color-brand-primary)}.nav-item-active:after{content:\"\";position:absolute;bottom:-2px;left:30%;right:30%;height:2px;background:linear-gradient(to right,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0),var(--cide-theme-color-brand-primary),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0));border-radius:1px;animation:pulseGlow 2s infinite}@keyframes pulseGlow{0%{opacity:.4}50%{opacity:1}to{opacity:.4}}.nav-item-active:hover{background:linear-gradient(135deg,#3b82f61a,#3b82f626)}.nav-badge{z-index:5;transition:all .3s ease}.nav-item:hover .nav-badge{transform:scale(1.1)}.nav-tooltip{position:fixed;left:64px;top:50%;transform:translateY(-50%) translate(-5px);background-color:var(--sidebar-tooltip-bg);color:var(--sidebar-tooltip-color);padding:.4rem .75rem;border-radius:.25rem;font-size:.75rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 8px var(--cide-theme-shadow-color);letter-spacing:.01em;will-change:transform,opacity;max-width:220px;text-overflow:ellipsis;overflow:hidden;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}.nav-tooltip:before{content:\"\";position:absolute;top:50%;right:100%;transform:translateY(-50%);border-width:6px;border-style:solid;border-color:transparent var(--sidebar-tooltip-bg) transparent transparent;filter:drop-shadow(-2px 0px 1px rgba(0,0,0,.1))}.nav-item:hover .nav-tooltip{opacity:1;transform:translateY(-50%) translate(0);box-shadow:0 3px 12px var(--cide-theme-shadow-color)}.nav-item-active{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 10%,var(--cide-theme-light-color));border-radius:.5rem}.nav-item-active:before{opacity:0}.active-nav-icon{color:var(--cide-lyt-sidebar-nav-item-color-active)!important;filter:drop-shadow(0 0 3px rgba(59,130,246,.3))}.nav-indicator{position:absolute;left:-8px;top:50%;transform:translateY(-50%);width:3px;height:60%;background:var(--cide-theme-color-brand-primary);border-radius:0 4px 4px 0;opacity:0;transition:all .3s ease;box-shadow:0 0 6px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.5)}.nav-item-active .nav-indicator{opacity:1;left:0}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.sidebar-scroll-content .nav-item{opacity:0;animation:fadeSlideIn .5s forwards}.sidebar-scroll-content .nav-item:nth-child(1){animation-delay:.05s}.sidebar-scroll-content .nav-item:nth-child(2){animation-delay:.1s}.sidebar-scroll-content .nav-item:nth-child(3){animation-delay:.15s}.sidebar-scroll-content .nav-item:nth-child(4){animation-delay:.2s}.sidebar-scroll-content .nav-item:nth-child(5){animation-delay:.25s}.sidebar-scroll-content .nav-item:nth-child(6){animation-delay:.3s}.sidebar-scroll-content .nav-item:nth-child(7){animation-delay:.35s}.sidebar-scroll-content .nav-item:nth-child(8){animation-delay:.4s}.sidebar-scroll-content .nav-item:nth-child(9){animation-delay:.45s}.sidebar-scroll-content .nav-item:nth-child(10){animation-delay:.5s}.sidebar-scroll-content .nav-item:nth-child(11){animation-delay:.55s}.sidebar-scroll-content .nav-item:nth-child(12){animation-delay:.6s}.sidebar-scroll-content .nav-item:nth-child(13){animation-delay:.65s}.sidebar-scroll-content .nav-item:nth-child(14){animation-delay:.7s}.sidebar-scroll-content .nav-item:nth-child(15){animation-delay:.75s}cide-ele-icon[type=box]{cursor:pointer;width:40px;height:40px;display:flex;align-items:center;justify-content:center;border-radius:.5rem;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}cide-ele-icon[type=box]:hover{background-color:#f3f4f6;transform:translateY(-1px)}cide-ele-icon[type=box]:active{transform:translateY(0)}.theme-toggle{position:relative;overflow:hidden;padding:.5rem;border-radius:12px;background-color:transparent;border:2px solid transparent;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem}.theme-toggle:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:radial-gradient(circle at center,rgba(59,130,246,.2) 0%,transparent 70%);opacity:0;transition:opacity .3s ease;border-radius:.5rem}.theme-toggle:hover{background-color:var(--cide-theme-hover-bg-color, rgba(243, 244, 246, 1));border-color:var(--cide-theme-color-brand-primary, rgba(59, 130, 246, .3));transform:translateY(-1px);box-shadow:0 4px 12px #0000001a}.theme-toggle:hover:after{opacity:1}.theme-toggle:focus{outline:none;box-shadow:0 0 0 3px #3b82f64d}.theme-toggle cide-ele-icon{color:var(--cide-theme-icon-color, #6b7280);transition:all .2s ease;width:1.25rem;height:1.25rem;display:flex;align-items:center;justify-content:center}.theme-toggle:hover cide-ele-icon{color:var(--cide-theme-color-brand-primary, #3b82f6);transform:scale(1.1) rotate(10deg)}.dark-mode .theme-toggle:after{background:radial-gradient(circle at center,rgba(96,165,250,.2) 0%,transparent 70%)}.dark-mode .theme-toggle:hover{background-color:var(--cide-theme-dark-hover-bg-color, rgba(55, 65, 81, 1));border-color:var(--cide-theme-color-brand-primary, rgba(96, 165, 250, .3))}.notification-badge{font-size:.65rem;min-width:20px;height:20px;box-shadow:0 2px 4px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.25);transform-origin:center;position:relative;animation:pulseNotification 2s infinite;overflow:hidden;background-color:var(--cide-theme-color-brand-primary);color:var(--cide-theme-light-color)}.notification-badge:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,#ffffff4d,#fff0);opacity:0;transition:opacity .3s ease}.notification-badge:hover:before{opacity:1}@keyframes pulseNotification{0%{box-shadow:0 0 rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.4);transform:scale(1)}40%{transform:translateY(-5px)}50%{box-shadow:0 0 0 4px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0);transform:scale(1.05)}60%{transform:translateY(-2px)}to{box-shadow:0 0 rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0);transform:scale(1)}}.user-avatar{transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden;box-shadow:0 2px 5px #00000014;z-index:2}.user-avatar:hover{transform:scale(1.08);box-shadow:0 4px 12px #00000026}.user-avatar:before{content:\"\";position:absolute;inset:-3px;background:radial-gradient(circle,rgba(59,130,246,.3),transparent 70%);opacity:0;transition:opacity .3s ease;z-index:-1;border-radius:50%}.user-avatar:hover:before{opacity:1}.user-status{position:absolute;bottom:0;right:0;width:10px;height:10px;border-radius:50%;border:2px solid white;transition:all .3s ease}.user-status.online{background-color:#10b981;animation:statusPulse 2s infinite}.user-status.busy{background-color:#f59e0b}.user-status.offline{background-color:#6b7280}.user-status.dnd{background-color:#ef4444}@keyframes statusPulse{0%{box-shadow:0 0 #10b98166}70%{box-shadow:0 0 0 6px #10b98100}to{box-shadow:0 0 #10b98100}}.dark-mode .user-avatar{box-shadow:0 2px 5px #0003}.dark-mode .user-avatar:before{background:radial-gradient(circle,rgba(96,165,250,.4),transparent 70%)}.dark-mode .user-status{border-color:#0f172a}.user-dropdown{position:relative}.user-menu{animation:fadeInUp .2s cubic-bezier(.4,0,.2,1);z-index:30;margin-left:10px;margin-bottom:5px;max-height:80vh;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;box-shadow:0 10px 25px #0000001a;border-radius:.75rem;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);position:absolute;left:100%;bottom:0;margin-top:.5rem;margin-left:.5rem;width:14rem;border-radius:.375rem;box-shadow:0 10px 15px -3px var(--cide-theme-shadow-color),0 4px 6px -2px var(--cide-theme-shadow-color);background-color:var(--cide-theme-sidebar-color);border:1px solid var(--cide-theme-border-color);z-index:50;overflow:hidden}.user-menu a{color:var(--cide-theme-text-color);transition:background-color .2s ease}.user-menu a:hover{background-color:var(--cide-theme-hover-bg-color)}.user-menu .tw-text-red-700{color:var(--cide-theme-error-color)}.user-menu::-webkit-scrollbar{width:3px}.user-menu::-webkit-scrollbar-track{background:transparent}.user-menu::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.user-status{transition:all .3s ease;box-shadow:0 0 0 2px var(--cide-theme-sidebar-color)}.user-status.online{animation:pulseOnline 2s infinite}@keyframes pulseOnline{0%{box-shadow:0 0 #22c55e66,0 0 0 2px var(--cide-theme-sidebar-color)}70%{box-shadow:0 0 0 4px #22c55e00,0 0 0 2px var(--cide-theme-sidebar-color)}to{box-shadow:0 0 #22c55e00,0 0 0 2px var(--cide-theme-sidebar-color)}}.cide-lyt-sidebar-menu{width:auto;background-color:var(--cide-theme-sidebar-color);border-left:1px solid rgba(243,244,246,1);transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;overflow:hidden;max-height:100%;position:relative;will-change:width,opacity,transform}.sidebar-header{background-color:var(--cide-theme-sidebar-color);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);position:sticky;top:0;z-index:10;border-bottom:1px solid rgba(243,244,246,.8);padding:1rem .75rem;transition:all .3s ease}.sidebar-header.scrolled{box-shadow:0 4px 10px -8px #0000001a}.back-button{transition:all .2s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%}.back-button:hover{transform:translate(-2px);background-color:#f3f4f6}.back-button:active{transform:translate(-1px) scale(.95)}.search-wrapper{position:relative;transition:all .3s ease}.search-wrapper:focus-within{transform:translateY(-1px)}.search-shortcut{font-size:.65rem;opacity:.7;letter-spacing:.02em;pointer-events:none;padding:.1rem .3rem;background-color:#f3f4f6cc;border-radius:.25rem;border:1px solid rgba(229,231,235,.8);transition:all .3s ease}.search-wrapper:focus-within .search-shortcut{opacity:.5}.options-menu{animation:fadeInUp .2s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 12px #0000001a;max-height:70vh;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;border-radius:.75rem;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.options-menu::-webkit-scrollbar{width:3px}.options-menu::-webkit-scrollbar-track{background:transparent}.options-menu::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.sidebar-section{position:relative;padding:.5rem 0}.sidebar-section:not(:first-child):before{content:\"\";height:1px;background:linear-gradient(to right,#e5e7eb00,#e5e7eb80,#e5e7eb00);position:absolute;top:0;left:10%;right:10%}.tw-text-xs.tw-font-semibold.tw-text-gray-500{font-size:.7rem;letter-spacing:.05em;text-transform:uppercase;position:relative;display:inline-block;padding:0 .5rem;color:var(--cide-theme-label-color)}.tw-text-xs.tw-font-semibold.tw-text-gray-500:after{content:\"\";position:absolute;height:3px;width:2rem;background:linear-gradient(to right,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.5),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0));bottom:-4px;left:.5rem;border-radius:3px;transition:width .3s ease}.sidebar-section:hover .tw-text-xs.tw-font-semibold.tw-text-gray-500:after{width:3rem}.menu-item{text-decoration:none;transition:all .25s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden;margin:3px 0;border-radius:.5rem;transform:translateZ(0);color:var(--cide-theme-text-color)}.menu-item:hover{transform:translate(3px);background-color:var(--cide-theme-light-color);box-shadow:0 1px 3px var(--sidebar-shadow-color)}.active-menu-item{background-color:rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.08);position:relative;box-shadow:0 2px 5px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.1)}.active-menu-item:after{content:\"\";position:absolute;left:0;top:0;height:100%;width:3px;background:linear-gradient(to bottom,var(--cide-theme-color-brand-primary),var(--cide-theme-secondary-color));border-radius:0 2px 2px 0;box-shadow:0 0 6px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.3);animation:pulseLeftBorder 2s infinite}@keyframes pulseLeftBorder{0%{opacity:.7}50%{opacity:1}to{opacity:.7}}.dark-mode .active-menu-item{background-color:#1e293b80;box-shadow:0 2px 5px #0f172a33}.dark-mode .active-menu-item:after{background:linear-gradient(to bottom,#60a5fa,#3b82f6);box-shadow:0 0 8px #60a5fa66}.quick-actions{transition:all .3s ease;opacity:0;transform:translate(5px);z-index:2}.menu-item:hover .quick-actions{opacity:1;transform:translate(0)}.badge{font-size:.7rem;padding:1px 5px;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;z-index:1}.menu-item:hover .badge{background-color:#d1d5db}.new-badge{animation:pulse 2s infinite;z-index:1}@keyframes pulse{0%{box-shadow:0 0 #3b82f666}70%{box-shadow:0 0 0 4px #3b82f600}to{box-shadow:0 0 #3b82f600}}.toggle-switch{display:inline-flex;align-items:center;transition:all .2s ease}.toggle-switch:hover{transform:scale(1.05)}.toggle-track{position:relative;cursor:pointer;transition:background-color .3s ease;overflow:hidden}.toggle-track:after{content:\"\";position:absolute;top:50%;left:50%;width:5px;height:5px;background:#ffffffb3;opacity:0;border-radius:100%;transform:scale(1) translate(-50%,-50%);transform-origin:50% 50%}.toggle-track.clicked:after{animation:ripple .6s ease-out}@keyframes ripple{0%{opacity:1;transform:scale(0) translate(-50%,-50%)}to{opacity:0;transform:scale(20) translate(-50%,-50%)}}.alert-box{animation:fadeIn .5s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 8px #fde04726;border-radius:.5rem;position:relative;overflow:hidden}.alert-box:before{content:\"\";position:absolute;inset:0;background-image:url(\"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23fef3c7' fill-opacity='0.4' fill-rule='evenodd'%3E%3Ccircle cx='3' cy='3' r='3'/%3E%3Ccircle cx='13' cy='13' r='3'/%3E%3C/g%3E%3C/svg%3E\");opacity:.3;pointer-events:none}@keyframes fadeIn{0%{opacity:0;transform:translateY(5px)}to{opacity:1;transform:translateY(0)}}.notification-panel{animation:fadeIn .3s cubic-bezier(.4,0,.2,1);overflow-y:auto;max-height:100vh;scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;padding:.75rem;border-radius:.75rem}.notification-panel::-webkit-scrollbar{width:3px}.notification-panel::-webkit-scrollbar-track{background:transparent}.notification-panel::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.notification-panel:hover::-webkit-scrollbar-thumb{background-color:#9ca3af80}.notification-item{transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;border-radius:.5rem;margin-bottom:.5rem;position:relative;overflow:hidden}.notification-item:hover{background-color:#f3f4f6;transform:translate(1px)}.notification-item:active{transform:scale(.99)}.notification-item.unread:before{content:\"\";position:absolute;left:0;top:0;height:100%;width:3px;background-color:var(--cide-theme-color-brand-primary);border-radius:0 2px 2px 0}.sidebar-footer{background-color:var(--cide-theme-light-color);position:sticky;bottom:0;z-index:10;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-top:1px solid rgba(243,244,246,.8);padding:.75rem;transition:all .3s ease}.upgrade-button{transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden;border-radius:.5rem;color:var(--cide-theme-text-color)}.upgrade-button:hover{background-color:rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.1);transform:translateY(-1px)}.upgrade-button:active{transform:translateY(0) scale(.98)}.upgrade-button:after{content:\"\";position:absolute;width:12px;height:12px;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%233b82f6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'%3E%3C/polygon%3E%3C/svg%3E\");top:5px;right:10px;opacity:.5;transform:scale(0);transition:all .3s ease}.upgrade-button:hover:after{transform:scale(1) rotate(20deg);animation:float 3s ease-in-out infinite}@keyframes float{0%,to{transform:scale(1) rotate(5deg) translate(0)}50%{transform:scale(1.1) rotate(-5deg) translate(-2px,-2px)}}.storage-bar{background-color:#e5e7eb80;overflow:hidden;border-radius:4px;height:6px;position:relative}.storage-fill{background:linear-gradient(to right,var(--cide-theme-color-brand-primary),var(--cide-theme-secondary-color));transition:width 1s cubic-bezier(.4,0,.2,1);position:relative;height:100%;border-radius:4px}.storage-fill:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,#fff0,#fff3,#fff0);animation:shimmer 2s infinite}@keyframes shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.plan-features{transition:all .3s ease;padding:.5rem;border-radius:.5rem}.plan-features:hover{background-color:#f3f4f680}.feature-item{display:flex;align-items:center;margin:.25rem 0;position:relative;padding-left:1.25rem;color:var(--cide-theme-text-color)}.feature-item:before{content:\"\\2713\";position:absolute;left:0;color:var(--cide-theme-color-brand-primary);font-size:.7rem;top:50%;transform:translateY(-50%)}.sidebar-content{scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;flex:1;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;padding:0 .75rem;transition:all .3s ease;position:relative;scroll-behavior:smooth;scroll-padding:1rem;overscroll-behavior:contain}.sidebar-content:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:30px;background:linear-gradient(to top,rgba(255,255,255,.9),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.sidebar-content:before{content:\"\";position:absolute;top:0;left:0;right:0;height:30px;background:linear-gradient(to bottom,rgba(255,255,255,.9),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.sidebar-content.scrolled-down:before{opacity:1}.sidebar-content.scrolled-up:after{opacity:1}.sidebar-content::-webkit-scrollbar{width:3px}.sidebar-content::-webkit-scrollbar-track{background:transparent}.sidebar-content::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.sidebar-content:hover::-webkit-scrollbar-thumb{background-color:#9ca3af80}.cide-lyt-sidebar *::-webkit-scrollbar{width:3px}.cide-lyt-sidebar *::-webkit-scrollbar-track{background:transparent}.cide-lyt-sidebar *::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.cide-lyt-sidebar *:hover::-webkit-scrollbar-thumb{background-color:#9ca3af80}.sidebar-section{transition:all .3s ease-out;transform:translateY(0);opacity:1}.sidebar-section.animate-in{animation:slideInUp .4s cubic-bezier(.4,0,.2,1) forwards}@keyframes slideInUp{0%{opacity:.5;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}.sidebar-section:nth-child(1).animate-in{animation-delay:.1s}.sidebar-section:nth-child(2).animate-in{animation-delay:.2s}.sidebar-section:nth-child(3).animate-in{animation-delay:.3s}.sidebar-section:nth-child(4).animate-in{animation-delay:.4s}.sidebar-section:nth-child(5).animate-in{animation-delay:.5s}:host ::ng-deep cide-ele-input{width:100%}:host ::ng-deep .cide-input-field{border-radius:8px;background-color:#f9fafb;border:1px solid transparent;transition:all .2s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .cide-input-field:focus-within{border-color:#d1d5db;background-color:#fff;box-shadow:0 2px 8px #0000000d;transform:translateY(-1px)}.cide-lyt-sidebar *:focus{outline:none;box-shadow:0 0 0 2px #3b82f64d}.dark-mode{--sidebar-tooltip-bg: var(--cide-theme-dark-color);--sidebar-tooltip-color: var(--cide-theme-light-color);background-color:#1e293b;color:var(--cide-theme-light-color)}.dark-mode .cide-lyt-stack{background-color:#0f172a;border-right-color:#1e293b}.dark-mode .cide-lyt-stack:before,.dark-mode .cide-lyt-stack:after,.dark-mode .sidebar-content:before,.dark-mode .sidebar-content:after{background:linear-gradient(to bottom,rgba(15,23,42,.9),transparent)}.dark-mode .sidebar-content:after,.dark-mode .cide-lyt-stack:after{background:linear-gradient(to top,rgba(15,23,42,.9),transparent)}.dark-mode .cide-lyt-sidebar-menu{background-color:#0f172a;border-left-color:#1e293b}.dark-mode .sidebar-header,.dark-mode .sidebar-footer{background-color:#0f172af2;border-color:#1e293b}.dark-mode .tw-text-gray-700{color:#e2e8f0}.dark-mode .tw-text-gray-500{color:#94a3b8}.dark-mode .tw-bg-gray-50,.dark-mode .menu-item:hover{background-color:#1e293b80}.dark-mode .tw-bg-gray-100{background-color:#1e293b}.dark-mode .tw-bg-gray-200{background-color:#334155}.dark-mode .nav-item:hover:before{background-color:#60a5fa1a}.dark-mode .back-button:hover{background-color:#1e293bb3}.dark-mode .sidebar-section:not(:first-child):before{background:linear-gradient(to right,#33415500,#33415580,#33415500)}.dark-mode .toggle-track:after{background:#00000080}.dark-mode .alert-box:before{opacity:.2}.dark-mode :host ::ng-deep .cide-input-field{background-color:#1e293b;color:#e2e8f0}.dark-mode :host ::ng-deep .cide-input-field:focus-within{border-color:#475569;background-color:#0f172a}.dark-mode *::-webkit-scrollbar-thumb{background-color:#47556980}.dark-mode *:hover::-webkit-scrollbar-thumb{background-color:#64748b99}.nav-item-active .nav-indicator{animation:pulseIndicator 2s infinite}@keyframes pulseIndicator{0%{box-shadow:0 0 #3b82f666}70%{box-shadow:0 0 0 3px #3b82f600}to{box-shadow:0 0 #3b82f600}}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}@media (prefers-reduced-motion: reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}@media screen and (max-width: 1024px){.cide-lyt-sidebar-menu{width:220px}.sidebar-content{padding:0 .5rem}.tw-text-xs.tw-font-semibold.tw-text-gray-500{font-size:.65rem}}@media screen and (max-width: 768px){.cide-lyt-sidebar-menu{position:fixed;width:100%;max-width:280px;left:72px;z-index:40;box-shadow:0 10px 25px #00000026;border-radius:0 1rem 1rem 0}.cide-lyt-sidebar.collapsed .cide-lyt-sidebar-menu{left:-100%}.sidebar-overlay{position:fixed;inset:0;background-color:#0000004d;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;pointer-events:none;transition:opacity .3s ease;z-index:35}.sidebar-overlay.active{opacity:1;pointer-events:auto}}.cide-lyt-sidebar,.cide-lyt-stack,.cide-lyt-sidebar-menu,.sidebar-logo,.nav-item,.menu-item,.user-avatar,.upgrade-button,.storage-fill,.notification-item,.sidebar-section.animate-in{transform:translateZ(0);backface-visibility:hidden;perspective:1000px;will-change:transform,opacity}.dark-mode .nav-item-active{background:linear-gradient(135deg,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.15),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.1));box-shadow:0 2px 8px #1e40af40}.dark-mode .nav-item-active:after{background:linear-gradient(to right,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0),rgba(var(--cide-theme-secondary-color-rgb, 74, 222, 128),.6),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0));box-shadow:0 0 5px rgba(var(--cide-theme-secondary-color-rgb, 74, 222, 128),.5)}.dark-mode .nav-item:hover{background-color:#33415580;box-shadow:0 2px 8px #0f172a4d}.dark-mode .nav-tooltip{background-color:#0f172ae6;box-shadow:0 3px 10px #0006;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.dark-mode .nav-tooltip:before{border-color:transparent rgba(15,23,42,.9) transparent transparent}.text-theme-primary{color:var(--cide-theme-color-brand-primary)}.text-theme-secondary{color:var(--cide-theme-secondary-color)}.text-theme-text{color:var(--cide-theme-text-color)}.text-theme-label{color:var(--cide-theme-label-color)}.bg-theme-primary{background-color:var(--cide-theme-color-brand-primary)}.bg-theme-secondary{background-color:var(--cide-theme-secondary-color)}.bg-theme-light{background-color:var(--cide-theme-light-color)}.bg-theme-dark{background-color:var(--cide-theme-dark-color)}.bg-theme-sidebar{background-color:var(--cide-theme-sidebar-color)}.border-theme-light{border-color:var(--cide-theme-light-color)}.border-theme-primary{border-color:var(--cide-theme-color-brand-primary)}.hover-bg-theme-light:hover{background-color:var(--cide-theme-light-color)}.hover-text-theme-primary:hover{color:var(--cide-theme-color-brand-primary)}.tw-text-gray-500{color:var(--cide-theme-icon-color)}.hover\\:tw-text-gray-700:hover{color:var(--cide-theme-icon-hover-color)}.tw-text-gray-700{color:var(--cide-theme-text-color)}.tw-text-red-500,.tw-text-red-600,.tw-text-red-700{color:var(--cide-theme-error-color)}.tw-bg-gray-200{background-color:var(--cide-theme-border-color)}.hover\\:tw-bg-gray-100:hover{background-color:var(--cide-theme-hover-bg-color)}.tw-bg-white{background-color:var(--cide-theme-sidebar-color)}.tw-bg-green-500{background-color:var(--cide-theme-success-color)}.tw-border-gray-100,.tw-divide-gray-100{border-color:var(--cide-theme-border-color)}.tw-shadow-lg{box-shadow:0 10px 15px -3px var(--cide-theme-shadow-color),0 4px 6px -2px var(--cide-theme-shadow-color)}.tw-shadow-sm,.hover\\:tw-shadow:hover{box-shadow:0 1px 2px 0 var(--cide-theme-shadow-color)}.active-nav-icon{color:var(--cide-theme-color-brand-primary)!important}.nav-item{display:flex;align-items:center;justify-content:center;position:relative;border-radius:.375rem}.nav-item:hover{background-color:var(--cide-theme-hover-bg-color)}.nav-item:hover cide-ele-icon{color:var(--cide-theme-icon-hover-color)}.nav-item-active{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 10%,var(--cide-theme-light-color))}.nav-item-active cide-ele-icon{color:var(--cide-theme-color-brand-primary)}.nav-indicator{opacity:0;transition:opacity .2s ease}.nav-item-active .nav-indicator{opacity:1}.nav-tooltip{position:absolute;left:100%;top:50%;transform:translateY(-50%);background-color:var(--sidebar-tooltip-bg);color:var(--sidebar-tooltip-color);padding:.25rem .5rem;border-radius:.25rem;font-size:.75rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s ease;margin-left:.5rem;z-index:30;box-shadow:0 2px 8px var(--cide-theme-shadow-color)}.nav-item:hover .nav-tooltip{opacity:1}.nav-badge{position:absolute;top:-.25rem;right:-.25rem;width:.375rem;height:.375rem;border-radius:9999px;background-color:var(--cide-theme-error-color);animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes ping{75%,to{transform:scale(2);opacity:0}}.user-avatar{width:2.5rem;height:2.5rem;border-radius:9999px;overflow:hidden;background-color:var(--cide-theme-border-color);border:2px solid var(--cide-theme-sidebar-color);box-shadow:0 1px 2px var(--cide-theme-shadow-color);cursor:pointer;transition:box-shadow .2s ease}.user-avatar:hover{box-shadow:0 2px 4px var(--cide-theme-shadow-color)}.user-status{position:absolute;bottom:0;right:0;width:.75rem;height:.75rem;border-radius:9999px;background-color:var(--cide-theme-success-color);border:2px solid var(--cide-theme-sidebar-color)}.dark-mode .nav-item:hover{background-color:#ffffff1a}.dark-mode .nav-item-active{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 30%,var(--cide-theme-dark-color))}.dark-mode .user-avatar{border-color:var(--cide-theme-dark-color);background-color:#ffffff1a}.dark-mode .user-status{border-color:var(--cide-theme-dark-color)}.dark-mode .user-menu{background-color:var(--cide-theme-dark-color);border-color:#ffffff1a}.dark-mode .user-menu a{color:#fffc}.dark-mode .user-menu a:hover{background-color:#ffffff1a}.section-header{transition:all .2s ease;border:1px solid transparent}.section-header:hover{background-color:#f8fafc!important;border-color:#e2e8f0}.section-header:focus{outline:none;box-shadow:0 0 0 2px #3b82f6;border-color:#3b82f6}.section-content{overflow:hidden;transition:all .3s ease}.section-header .tw-rotate-90{transform:rotate(90deg)}.section-content .tw-border-l{border-left-color:#d1d5db}.section-header:hover .tw-text-gray-500{color:#6b7280}.section-header:hover .tw-text-gray-800{color:#374151}\n"], dependencies: [{ kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "size"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CideEleResizerDirective, selector: "[cideEleResizer]", inputs: ["direction", "to", "prevElementSelector", "nextElementSelector", "parentElementSelector", "minPrevSize", "minNextSize", "usePercentage"], outputs: ["resizeStart", "resizing", "resizeEnd"] }, { kind: "directive", type: TooltipDirective, selector: "[cideEleTooltip]", inputs: ["cideEleTooltip", "tooltipColor", "tooltipBg", "tooltipPlacement", "tooltipType", "tooltipDelay", "tooltipDir", "tooltipShowArrow", "tooltipMultiline", "tooltipMaxWidth", "tooltipInteractive", "tooltipClass"] }, { kind: "component", type: CideSpinnerComponent, selector: "cide-ele-spinner", inputs: ["size", "type"] }], animations: [
|
|
2036
|
-
trigger('slideInOut', [
|
|
2037
|
-
state('in', style({
|
|
2038
|
-
opacity: 1,
|
|
2039
|
-
transform: 'translateY(0)',
|
|
2040
|
-
height: '*'
|
|
2041
|
-
})),
|
|
2042
|
-
state('out', style({
|
|
2043
|
-
opacity: 0,
|
|
2044
|
-
transform: 'translateY(-10px)',
|
|
2045
|
-
height: '0px'
|
|
2046
|
-
})),
|
|
2047
|
-
transition('in => out', animate('200ms ease-in')),
|
|
2048
|
-
transition('out => in', animate('200ms ease-out'))
|
|
2049
|
-
])
|
|
2050
|
-
] });
|
|
2051
|
-
}
|
|
2052
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidebarWrapperComponent, decorators: [{
|
|
2053
|
-
type: Component,
|
|
2054
|
-
args: [{ selector: 'cide-lyt-sidebar-wrapper', standalone: true, imports: [CideIconComponent, CideInputComponent, CommonModule, CideEleResizerDirective, TooltipDirective, CideSpinnerComponent], animations: [
|
|
2055
|
-
trigger('slideInOut', [
|
|
2056
|
-
state('in', style({
|
|
2057
|
-
opacity: 1,
|
|
2058
|
-
transform: 'translateY(0)',
|
|
2059
|
-
height: '*'
|
|
2060
|
-
})),
|
|
2061
|
-
state('out', style({
|
|
2062
|
-
opacity: 0,
|
|
2063
|
-
transform: 'translateY(-10px)',
|
|
2064
|
-
height: '0px'
|
|
2065
|
-
})),
|
|
2066
|
-
transition('in => out', animate('200ms ease-in')),
|
|
2067
|
-
transition('out => in', animate('200ms ease-out'))
|
|
2068
|
-
])
|
|
2069
|
-
], template: "<nav class=\"cide-lyt-sidebar tw-flex tw-h-full tw-select-none\" [class.collapsed]=\"isCollapsed\">\n <!-- First tier sidebar (Icon only) -->\n <div class=\"cide-lyt-stack tw-h-full tw-flex tw-flex-col tw-items-center tw-border-r tw-border-gray-100\">\n <!-- Scrollable content -->\n <div class=\"sidebar-scroll-content\">\n <!-- Collapse/Expand toggle -->\n <div class=\"nav-item collapse-toggle\" tabindex=\"0\" (click)=\"toggleSidebar()\"\n (keydown.enter)=\"toggleSidebar()\">\n <div class=\"nav-tooltip\">{{isCollapsed ? 'Expand' : 'Collapse'}} Sidebar</div>\n <cide-ele-icon size=\"xs\" type=\"box\" class=\"tw-text-gray-500 hover:tw-text-gray-700\">\n {{isCollapsed ? 'chevron_right' : 'chevron_left'}}\n </cide-ele-icon>\n </div> <!-- Main navigation icons - Enhanced Design -->\n <div class=\"tw-flex tw-flex-col tw-gap-4\">\n @for (core_system_module_item of core_system_module; track $index) {\n <div class=\"nav-item tw-relative tw-group\" (click)=\"setActiveMenu(core_system_module_item._id)\"\n [cideEleTooltip]=\"core_system_module_item.syme_title\" [tooltipShowArrow]=\"true\"\n tooltipPlacement=\"right\"\n [ngClass]=\"{'nav-item-active': activeModuleId === core_system_module_item._id}\"\n (mouseenter)=\"onItemHover(core_system_module_item._id)\" (mouseleave)=\"onItemHover('')\"\n [tabindex]=\"$index\" (keydown.enter)=\"setActiveMenu(core_system_module_item._id)\">\n\n <!-- Notification Badge with enhanced design -->\n @if (core_system_module_item?.syme_ping) {\n <div class=\"nav-badge tw-absolute -tw-top-1 -tw-right-1 tw-w-1.5 tw-h-1.5 tw-rounded-full tw-animate-ping\"\n style=\"background-color: var(--cide-theme-error-color);\">\n </div>\n }\n\n <!-- Enhanced icon with better visual effects -->\n <div\n class=\"tw-p-2 tw-rounded-xl tw-transition-all tw-duration-200 tw-ease-in-out\n group-hover:tw-bg-blue-50 group-hover:tw-shadow-md group-hover:tw-scale-105\n tw-border-2 tw-border-transparent\n {{activeModuleId === core_system_module_item._id ? 'tw-bg-blue-100 tw-border-blue-200 tw-shadow-sm' : 'tw-bg-gray-50'}}\">\n <cide-ele-icon type=\"box\" size=\"xs\"\n class=\"tw-transition-all tw-duration-200 tw-ease-in-out\n {{activeModuleId === core_system_module_item._id ? 'tw-text-blue-600' : 'tw-text-gray-600 group-hover:tw-text-blue-500'}}\">\n {{core_system_module_item?.syme_icon || 'dashboard'}}\n </cide-ele-icon>\n </div>\n\n <!-- Modern active indicator -->\n @if (activeModuleId === core_system_module_item._id) {\n <div\n class=\"nav-indicator tw-absolute tw-left-0 tw-top-1/2 tw-transform -tw-translate-y-1/2 tw-w-1 tw-h-8 tw-bg-blue-500 tw-rounded-r-full tw-shadow-lg tw-transition-all tw-duration-300\">\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Bottom section with enhanced theme toggle and user avatar -->\n <div class=\"tw-flex tw-flex-col tw-items-center tw-gap-3 tw-mt-auto tw-pb-4\">\n <!-- Theme toggle with modern design -->\n <div class=\"nav-item tw-group\">\n <div class=\"nav-tooltip\">Toggle Theme</div>\n <button class=\"theme-toggle\" (click)=\"toggleTheme()\" (keydown.enter)=\"toggleTheme()\"\n (keydown.space)=\"toggleTheme()\" tabindex=\"0\" role=\"button\" aria-label=\"Toggle theme\">\n <cide-ele-icon type=\"box\" size=\"sm\">\n {{darkMode ? 'light_mode' : 'dark_mode'}}\n </cide-ele-icon>\n </button>\n </div>\n\n <!-- User avatar with dropdown -->\n <div class=\"tw-relative user-dropdown\" (click)=\"toggleUserMenu()\" (keydown.enter)=\"toggleUserMenu()\"\n (keydown.space)=\"toggleUserMenu()\" tabindex=\"0\" role=\"button\" aria-label=\"Toggle user menu\"\n [attr.aria-expanded]=\"showUserMenu\">\n <div class=\"nav-tooltip\">Your Profile</div>\n <div class=\"user-avatar tw-w-10 tw-h-10 tw-rounded-full tw-overflow-hidden tw-bg-gray-200 tw-border-2 tw-shadow-sm tw-cursor-pointer hover:tw-shadow\"\n style=\"border-color: var(--cide-theme-sidebar-color);\">\n <img src=\"data:image/png;base64,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\"\n alt=\"User\" class=\"tw-w-full tw-h-full tw-object-cover\"\n onerror=\"this.src='data:image/png;base64,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'\">\n <!-- Modern Status Indicator Circle -->\n <div class=\"tw-absolute -tw-bottom-0.5 -tw-right-0.5 tw-w-3.5 tw-h-3.5 tw-rounded-full tw-border-2 tw-border-white tw-shadow-sm tw-transition-all tw-duration-200\n {{getUserStatusClass()}}\" [cideEleTooltip]=\"getUserStatusText()\"\n tooltipPlacement=\"left\">\n <!-- Pulse animation for active status -->\n <div class=\"tw-absolute tw-inset-0 tw-rounded-full tw-animate-pulse tw-opacity-50\n {{getUserStatusClass()}}\"></div>\n </div>\n </div>\n\n <!-- Enhanced User dropdown menu with modern status options -->\n <div *ngIf=\"showUserMenu\"\n class=\"user-menu tw-absolute tw-left-full tw-bottom-0 tw-mt-2 tw-ml-2 tw-w-72 tw-rounded-xl tw-shadow-2xl tw-bg-white tw-ring-1 tw-ring-black/5 tw-divide-y tw-divide-gray-100 tw-z-50 tw-backdrop-blur-sm\">\n\n <!-- Enhanced User Info Section -->\n <div class=\"tw-p-5 tw-bg-gradient-to-br tw-from-blue-50 tw-to-indigo-50\">\n <div class=\"tw-flex tw-items-center tw-gap-4\">\n <div class=\"tw-relative\">\n <div\n class=\"tw-w-12 tw-h-12 tw-rounded-xl tw-bg-gradient-to-br tw-from-blue-500 tw-via-purple-500 tw-to-indigo-600 tw-flex tw-items-center tw-justify-center tw-text-white tw-font-semibold tw-text-sm tw-shadow-lg\">\n JD\n </div>\n <!-- Enhanced status indicator -->\n <div class=\"tw-absolute -tw-bottom-1 -tw-right-1 tw-w-4 tw-h-4 tw-rounded-full tw-border-2 tw-border-white tw-shadow-md tw-transition-all tw-duration-300\"\n [class]=\"getUserStatusClass()\" [title]=\"getUserStatusText()\">\n <div class=\"tw-absolute tw-inset-0 tw-rounded-full tw-animate-pulse tw-opacity-50\"\n [class]=\"getUserStatusClass()\"></div>\n </div>\n </div>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <div class=\"tw-text-base tw-font-semibold tw-text-gray-900 tw-truncate\">John Doe</div>\n <div class=\"tw-text-sm tw-text-gray-600 tw-truncate\">john.doe@company.com</div>\n <div class=\"tw-text-xs tw-text-gray-500 tw-mt-1 tw-flex tw-items-center tw-gap-1\">\n <div class=\"tw-w-1.5 tw-h-1.5 tw-rounded-full tw-bg-green-500 tw-animate-pulse\">\n </div>\n <span>Active now</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Enhanced Status Selection Section -->\n <div class=\"tw-p-4\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-mb-3\">\n <h4 class=\"tw-text-sm tw-font-semibold tw-text-gray-800 tw-flex tw-items-center tw-gap-2\">\n <div\n class=\"tw-w-5 tw-h-5 tw-rounded-md tw-bg-blue-100 tw-flex tw-items-center tw-justify-center\">\n <cide-ele-icon type=\"none\" size=\"2xs\"\n class=\"tw-text-blue-600\">radio_button_checked</cide-ele-icon>\n </div>\n Set Status\n </h4>\n <span\n class=\"tw-text-xs tw-text-gray-500 tw-bg-gray-100 tw-px-2 tw-py-1 tw-rounded-full\">Auto-away\n in 5min</span>\n </div>\n\n <div class=\"tw-space-y-1\">\n <button (click)=\"setUserStatus('online')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-green-50 tw-group\"\n [class.tw-bg-green-50]=\"userStatusService.userStatus().status === 'online'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'online'\"\n [class.tw-ring-green-200]=\"userStatusService.userStatus().status === 'online'\">\n <div class=\"tw-relative\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-green-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n <div class=\"tw-absolute tw-inset-0 tw-rounded-full tw-bg-green-500 tw-animate-ping tw-opacity-75\"\n *ngIf=\"userStatusService.userStatus().status === 'online'\"></div>\n </div>\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Online</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'online'\" type=\"none\"\n size=\"xs\" class=\"tw-text-green-600\">check</cide-ele-icon>\n </button>\n\n <button (click)=\"setUserStatus('away')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-yellow-50 tw-group\"\n [class.tw-bg-yellow-50]=\"userStatusService.userStatus().status === 'away'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'away'\"\n [class.tw-ring-yellow-200]=\"userStatusService.userStatus().status === 'away'\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-yellow-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Away</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'away'\" type=\"none\"\n size=\"xs\" class=\"tw-text-yellow-600\">check</cide-ele-icon>\n </button>\n\n <button (click)=\"setUserStatus('busy')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-orange-50 tw-group\"\n [class.tw-bg-orange-50]=\"userStatusService.userStatus().status === 'busy'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'busy'\"\n [class.tw-ring-orange-200]=\"userStatusService.userStatus().status === 'busy'\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-orange-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Busy</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'busy'\" type=\"none\"\n size=\"xs\" class=\"tw-text-orange-600\">check</cide-ele-icon>\n </button>\n\n <button (click)=\"setUserStatus('do-not-disturb')\"\n class=\"status-option tw-w-full tw-flex tw-items-center tw-gap-3 tw-px-3 tw-py-2.5 tw-text-sm tw-rounded-lg tw-transition-all tw-duration-200 hover:tw-bg-red-50 tw-group\"\n [class.tw-bg-red-50]=\"userStatusService.userStatus().status === 'do-not-disturb'\"\n [class.tw-ring-2]=\"userStatusService.userStatus().status === 'do-not-disturb'\"\n [class.tw-ring-red-200]=\"userStatusService.userStatus().status === 'do-not-disturb'\">\n <div\n class=\"tw-w-3 tw-h-3 tw-rounded-full tw-bg-red-500 tw-shadow-sm group-hover:tw-scale-110 tw-transition-transform\">\n </div>\n <span class=\"tw-text-gray-700 tw-font-medium tw-flex-1\">Do not disturb</span>\n <cide-ele-icon *ngIf=\"userStatusService.userStatus().status === 'do-not-disturb'\"\n type=\"none\" size=\"xs\" class=\"tw-text-red-600\">check</cide-ele-icon>\n </button>\n </div>\n </div>\n\n <!-- Enhanced Quick Actions Section -->\n <div class=\"tw-p-4 tw-bg-gray-50/50\">\n <h4\n class=\"tw-text-sm tw-font-semibold tw-text-gray-800 tw-mb-3 tw-flex tw-items-center tw-gap-2\">\n <div\n class=\"tw-w-5 tw-h-5 tw-rounded-md tw-bg-gray-100 tw-flex tw-items-center tw-justify-center\">\n <cide-ele-icon type=\"none\" size=\"2xs\" class=\"tw-text-gray-600\">bolt</cide-ele-icon>\n </div>\n Quick Actions\n </h4>\n <div class=\"tw-space-y-1\">\n <a href=\"#\"\n class=\"quick-action tw-group tw-flex tw-items-center tw-px-3 tw-py-2 tw-text-sm tw-text-gray-700 tw-rounded-lg hover:tw-bg-white tw-transition-all tw-duration-200\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-lg tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3 group-hover:tw-bg-blue-200 tw-transition-colors\">\n <cide-ele-icon type=\"none\" size=\"xs\" class=\"tw-text-blue-600\">person</cide-ele-icon>\n </div>\n <span class=\"tw-flex-1\">View Profile</span>\n <cide-ele-icon type=\"none\" size=\"xs\"\n class=\"tw-text-gray-400 group-hover:tw-text-gray-600 tw-transition-colors\">chevron_right</cide-ele-icon>\n </a>\n <a href=\"#\"\n class=\"quick-action tw-group tw-flex tw-items-center tw-px-3 tw-py-2 tw-text-sm tw-text-gray-700 tw-rounded-lg hover:tw-bg-white tw-transition-all tw-duration-200\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-lg tw-bg-gray-100 tw-flex tw-items-center tw-justify-center tw-mr-3 group-hover:tw-bg-gray-200 tw-transition-colors\">\n <cide-ele-icon type=\"none\" size=\"xs\"\n class=\"tw-text-gray-600\">settings</cide-ele-icon>\n </div>\n <span class=\"tw-flex-1\">Preferences</span>\n <cide-ele-icon type=\"none\" size=\"xs\"\n class=\"tw-text-gray-400 group-hover:tw-text-gray-600 tw-transition-colors\">chevron_right</cide-ele-icon>\n </a>\n </div>\n </div>\n\n <!-- Enhanced Sign Out Section -->\n <div class=\"tw-p-4\">\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-3 tw-py-2.5 tw-text-sm tw-text-red-600 tw-rounded-lg hover:tw-bg-red-50 tw-transition-all tw-duration-200 tw-font-medium\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-lg tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3 group-hover:tw-bg-red-200 tw-transition-colors\">\n <cide-ele-icon type=\"none\" size=\"xs\" class=\"tw-text-red-600\">logout</cide-ele-icon>\n </div>\n <span class=\"tw-flex-1\">Sign out</span>\n </a>\n </div>\n </div>\n </div>\n </div>\n </div> <!-- Second tier sidebar (Expanded view) -->\n <div class=\"cide-lyt-sidebar-menu tw-h-full tw-overflow-hidden tw-shadow-sm\">\n <div class=\"tw-flex tw-flex-col tw-h-full\">\n <!-- Header section with search and options -->\n <div class=\"sidebar-header tw-p-4 tw-border-b tw-border-gray-100\">\n <!-- Title with back button - Clean layout without online indicator -->\n <div class=\"tw-flex tw-items-center tw-gap-3 tw-mb-4\">\n <button\n class=\"back-button tw-p-1.5 tw-rounded-lg tw-text-gray-500 hover:tw-bg-gray-100 hover:tw-text-gray-700 tw-transition-colors tw-flex-shrink-0\"\n [cideEleTooltip]=\"'Back to home'\" tooltipPlacement=\"bottom\">\n <cide-ele-icon type=\"none\" size=\"xs\" class=\"tw-transition-transform hover:tw-scale-110\">{{\n getActiveModuleIcon() }}</cide-ele-icon>\n </button>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <h2 class=\"tw-text-sm tw-font-semibold tw-text-gray-900 tw-truncate tw-leading-5\">\n {{ appState.getActiveModuleTitle() || 'Menu' }}\n </h2>\n <p class=\"tw-text-xs tw-text-gray-500 tw-truncate tw-mt-0.5\">\n Module Dashboard\n </p>\n </div>\n </div>\n\n <!-- Search and options -->\n <div class=\"tw-flex tw-items-center tw-justify-between\">\n <div class=\"search-wrapper tw-relative tw-flex-1\">\n <cide-ele-input type=\"text\" leadingIcon=\"search\" size=\"xs\" placeholder=\"Search settings...\"\n [clearInput]=\"true\" [(ngModel)]=\"searchText\" (input)=\"onSearch()\">\n </cide-ele-input>\n <div\n class=\"search-shortcut tw-absolute tw-right-3 tw-top-1/2 -tw-translate-y-1/2 tw-bg-gray-100 tw-text-gray-500 tw-rounded tw-px-1.5 tw-py-0.5 tw-text-xs\">\n \u2318K</div>\n </div>\n <button\n class=\"tw-ml-2 tw-p-1.5 tw-rounded-lg tw-text-gray-500 hover:tw-bg-gray-100 hover:tw-text-gray-700 tw-transition-colors\"\n (click)=\"toggleOptions()\">\n <cide-ele-icon type=\"none\" size=\"xs\">more_vert</cide-ele-icon>\n </button>\n\n <!-- Options dropdown menu -->\n <div *ngIf=\"showOptions\"\n class=\"options-menu tw-absolute tw-right-4 tw-top-16 tw-mt-2 tw-w-48 tw-rounded-md tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5 tw-divide-y tw-divide-gray-100 tw-z-10\">\n <div class=\"tw-py-1\">\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500 tw-group-hover:tw-text-gray-600\"\n type=\"none\" size=\"xs\">refresh</cide-ele-icon>\n Refresh\n </a>\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500 tw-group-hover:tw-text-gray-600\"\n type=\"none\" size=\"xs\">sync</cide-ele-icon>\n Sync settings\n </a>\n </div>\n <div class=\"tw-py-1\">\n <a href=\"#\"\n class=\"tw-group tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500 tw-group-hover:tw-text-gray-600\"\n type=\"none\" size=\"xs\">help_outline</cide-ele-icon>\n Help & support\n </a>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Menu items with categories -->\n <div class=\"sidebar-content tw-overflow-y-auto tw-flex-1\" (scroll)=\"onScroll($event)\">\n <!-- Search results if searching -->\n <div *ngIf=\"searchText\" class=\"sidebar-section tw-p-4\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-mb-2\">\n <h3 class=\"tw-text-xs tw-font-semibold tw-text-gray-500\">SEARCH RESULTS</h3>\n <span class=\"tw-text-xs tw-text-gray-500\">{{searchResults.length}} items</span>\n </div>\n\n <div *ngIf=\"searchResults.length > 0\" class=\"tw-space-y-1\">\n <a *ngFor=\"let item of searchResults\" href=\"#\"\n class=\"menu-item tw-flex tw-items-center tw-px-3 tw-py-2 tw-rounded-md hover:tw-bg-gray-50\">\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500\" type=\"none\"\n size=\"xs\">{{item.icon}}</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">{{item.title}}</span>\n </a>\n </div>\n\n <div *ngIf=\"searchResults.length === 0\" class=\"tw-p-4 tw-text-center\">\n <div class=\"tw-flex tw-justify-center tw-mb-3\">\n <cide-ele-icon class=\"tw-text-gray-400\" type=\"none\" size=\"md\">search_off</cide-ele-icon>\n </div>\n <p class=\"tw-text-sm tw-text-gray-500\">No results for \"{{searchText}}\"</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Try another search term</p>\n </div>\n </div>\n\n <!-- Only show these sections if not searching -->\n <ng-container *ngIf=\"!searchText\">\n <!-- Loading state -->\n <div *ngIf=\"loadingMenus\" class=\"tw-p-4 tw-text-center\">\n <div class=\"tw-flex tw-justify-center tw-mb-3\">\n <cide-ele-spinner variant=\"circle\" size=\"xs\"></cide-ele-spinner>\n </div>\n <p class=\"tw-text-sm tw-text-gray-500\">Loading menus...</p>\n </div>\n\n <!-- Dynamic menu tree -->\n <div *ngIf=\"!loadingMenus && menuLoadComplete\" class=\"sidebar-section tw-p-4\"\n [class.animate-in]=\"animateSections[0]\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: selectedModuleMenus, level: 0}\"></ng-container>\n </div>\n\n <!-- Empty state -->\n <div *ngIf=\"!loadingMenus && menuLoadComplete && selectedModuleMenus.length === 0\"\n class=\"tw-p-4 tw-text-center\">\n <div class=\"tw-flex tw-justify-center tw-mb-3\">\n <cide-ele-icon class=\"tw-text-gray-400\" type=\"none\" size=\"md\">folder_open</cide-ele-icon>\n </div>\n <p class=\"tw-text-sm tw-text-gray-500\">No menus available</p>\n <p class=\"tw-text-xs tw-text-gray-400 tw-mt-1\">Select a module to view its menus</p>\n </div>\n </ng-container>\n\n <!-- Recursive menu template -->\n <ng-template #recursiveMenu let-menus let-level=\"level\">\n <div [class.tw-ml-0]=\"level > 0\">\n <div *ngFor=\"let menu of menus\" class=\"tw-mb-2\">\n <!-- Title type items (section headers) -->\n <div *ngIf=\"menu.syme_type === 'title'\" class=\"tw-mb-3\">\n <h3 class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-mb-2\">{{\n menu.syme_title }}</h3>\n <!-- Render children of title -->\n <ng-container *ngIf=\"menu.children && menu.children.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: menu.children, level: level + 1}\"></ng-container>\n </ng-container>\n </div>\n\n <!-- Section type items (collapsible sections) -->\n <div *ngIf=\"menu.syme_type === 'section'\" class=\"tw-mb-2\">\n <!-- Section header (clickable to expand/collapse) -->\n <button (click)=\"toggleSection(menu._id)\" (keydown.enter)=\"toggleSection(menu._id)\"\n (keydown.space)=\"toggleSection(menu._id)\"\n class=\"section-header tw-w-full tw-flex tw-items-center tw-px-3 tw-py-2 tw-rounded-md hover:tw-bg-gray-50 tw-cursor-pointer tw-transition-colors tw-text-left\"\n [class.tw-bg-blue-50]=\"isSectionExpanded(menu._id)\" type=\"button\" tabindex=\"0\"\n role=\"button\" [attr.aria-expanded]=\"isSectionExpanded(menu._id)\"\n [attr.aria-label]=\"'Toggle ' + menu.syme_title + ' section'\">\n\n <!-- Section Icon (left side, like other menu items) -->\n <cide-ele-icon class=\"tw-mr-3 tw-text-gray-500\" type=\"none\" size=\"xs\">{{\n menu.syme_icon || 'folder' }}</cide-ele-icon>\n\n <!-- Section Title -->\n <span class=\"tw-text-sm tw-font-medium tw-text-gray-800\">{{ menu.syme_title\n }}</span>\n\n <!-- Right side icons container -->\n <div class=\"tw-ml-auto tw-flex tw-items-center tw-space-x-2\">\n <!-- Child Count Badge -->\n <span *ngIf=\"menu.children && menu.children.length > 0\"\n class=\"tw-px-2 tw-py-0.5 tw-bg-gray-200 tw-text-gray-600 tw-rounded-full tw-text-xs tw-font-medium\">\n {{ menu.children.length }}\n </span>\n\n <!-- Expand/Collapse Icon (right side) -->\n <cide-ele-icon class=\"tw-text-gray-400 tw-transition-transform tw-duration-200\"\n [class.tw-rotate-90]=\"isSectionExpanded(menu._id)\" type=\"none\"\n size=\"2xs\">chevron_right</cide-ele-icon>\n </div>\n </button>\n\n <!-- Section Content (collapsible) -->\n <div *ngIf=\"isSectionExpanded(menu._id) && menu.children && menu.children.length > 0\"\n class=\"section-content tw-mt-2 tw-ml-2 tw-border-l tw-border-gray-200 tw-pl-3\"\n [@slideInOut]=\"isSectionExpanded(menu._id) ? 'in' : 'out'\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: menu.children, level: level + 1}\"></ng-container>\n </div>\n </div>\n\n <!-- Menu type items (clickable links) -->\n <a *ngIf=\"menu.syme_type === 'menu'\" (click)=\"onMenuClick(menu)\"\n (keydown.enter)=\"onMenuClick(menu)\" (keydown.space)=\"onMenuClick(menu)\" tabindex=\"0\"\n role=\"button\"\n class=\"menu-item tw-flex tw-items-center tw-px-1 tw-py-1 tw-rounded-md hover:tw-bg-gray-50 tw-cursor-pointer tw-transition-colors\">\n <cide-ele-icon *ngIf=\"menu.syme_icon\" class=\"tw-mr-3 tw-text-gray-500\" type=\"none\"\n size=\"xs\">{{ menu.syme_icon }}</cide-ele-icon>\n <span class=\"tw-text-sm tw-text-gray-700\">{{ menu.syme_title }}</span>\n <cide-ele-icon *ngIf=\"menu.children && menu.children.length > 0\"\n class=\"tw-ml-auto tw-text-gray-400\" type=\"none\"\n size=\"2xs\">chevron_right</cide-ele-icon>\n </a>\n\n <!-- Render nested children for menu items -->\n <ng-container\n *ngIf=\"menu.syme_type === 'menu' && menu.children && menu.children.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"recursiveMenu; context: {$implicit: menu.children, level: level + 1}\"></ng-container>\n </ng-container>\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- Notification panel (overlays content when shown) -->\n <div *ngIf=\"showNotifications\" class=\"notification-panel tw-absolute tw-inset-0 tw-bg-white tw-z-20\">\n <div class=\"tw-p-4 tw-border-b tw-border-gray-100 tw-flex tw-justify-between tw-items-center\">\n <h3 class=\"tw-text-sm tw-font-medium\">Notifications</h3>\n <button class=\"tw-p-1.5 tw-rounded-lg tw-text-gray-500 hover:tw-bg-gray-100\"\n (click)=\"toggleNotificationsPanel()\">\n <cide-ele-icon type=\"none\" size=\"xs\">close</cide-ele-icon>\n </button>\n </div>\n\n <div class=\"tw-p-4\">\n <div class=\"tw-flex tw-justify-between tw-items-center tw-mb-4\">\n <div class=\"tw-text-xs tw-text-gray-500\">Today</div>\n <button class=\"tw-text-xs tw-text-blue-500\">Mark all as read</button>\n </div>\n\n <div class=\"tw-space-y-3\">\n <div\n class=\"notification-item tw-p-3 tw-bg-blue-50 tw-rounded-lg tw-border-l-4 tw-border-blue-500\">\n <div class=\"tw-flex tw-gap-3\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-full tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-text-blue-500\">\n <cide-ele-icon type=\"none\" size=\"xs\">notifications</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-900\">New feature available</div>\n <div class=\"tw-text-xs tw-text-gray-500 tw-mt-1\">Try our new analytics dashboard\n with improved visualizations.</div>\n <div class=\"tw-text-xs tw-text-gray-400 tw-mt-2\">Just now</div>\n </div>\n </div>\n </div>\n\n <div class=\"notification-item tw-p-3 tw-rounded-lg tw-border-l-4 tw-border-transparent\">\n <div class=\"tw-flex tw-gap-3\">\n <div\n class=\"tw-w-8 tw-h-8 tw-rounded-full tw-bg-gray-100 tw-flex tw-items-center tw-justify-center tw-text-gray-500\">\n <cide-ele-icon type=\"none\" size=\"xs\">person_add</cide-ele-icon>\n </div>\n <div class=\"tw-flex-1\">\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-900\">New team member</div>\n <div class=\"tw-text-xs tw-text-gray-500 tw-mt-1\">Jane Smith has joined your team.\n </div>\n <div class=\"tw-text-xs tw-text-gray-400 tw-mt-2\">2 hours ago</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Storage info at bottom -->\n <div class=\"sidebar-footer tw-p-4 tw-border-t tw-border-gray-100\">\n <!-- for info display -->\n </div>\n </div>\n </div>\n\n <!-- Resizer -->\n <div parentElementSelector=\"#cide-lyt-sidebar-page-inner-wrapper\"\n [minPrevSize]=\"sidebarSetupData.cide_lyt_sidebar_width\" prevElementSelector=\"#cide-lyt-sidebar-page\"\n nextElementSelector=\"#cide-lyt-page-wrapper\" cideEleResizer direction=\"horizontal\">\n <div class=\"cide-lyt-devider-track tw-w-full tw-h-full\"></div>\n </div>\n</nav>", styles: [".cide-lyt-sidebar{display:flex;box-shadow:0 4px 12px #0000000d;overflow:hidden;background-color:var(--cide-theme-sidebar-color);--sidebar-tooltip-bg: var(--cide-theme-dark-color);--sidebar-tooltip-color: var(--cide-theme-light-color);--sidebar-shadow-color: rgba(0, 0, 0, .05);transition:width .3s cubic-bezier(.4,0,.2,1);max-height:100%;isolation:isolate;will-change:width;position:relative}.cide-lyt-sidebar.collapsed{animation:collapseEffect .3s forwards}.cide-lyt-sidebar:not(.collapsed){animation:expandEffect .3s forwards}@keyframes collapseEffect{0%{box-shadow:0 4px 12px var(--sidebar-shadow-color)}to{box-shadow:0 2px 8px var(--sidebar-shadow-color)}}@keyframes expandEffect{0%{box-shadow:0 2px 8px var(--sidebar-shadow-color)}to{box-shadow:0 4px 12px var(--sidebar-shadow-color)}}.cide-lyt-sidebar.animating{transition:width .3s cubic-bezier(.25,.46,.45,.94)}.cide-lyt-sidebar.collapsed .cide-lyt-sidebar-menu{width:0;opacity:0;visibility:hidden;transform:translate(-10px)}.cide-lyt-stack{background:linear-gradient(to bottom,var(--cide-theme-sidebar-color),var(--cide-theme-light-color));z-index:10;border-right:1px solid var(--cide-theme-light-color);display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:0 0 1.5rem;transition:all .3s cubic-bezier(.4,0,.2,1);overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:var(--cide-theme-color-brand-primary) transparent;position:relative;will-change:transform;scroll-behavior:smooth;box-shadow:inset -1px 0 0 var(--sidebar-shadow-color)}.cide-lyt-stack:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:30px;background:linear-gradient(to top,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.cide-lyt-stack:before{content:\"\";position:absolute;top:0;left:0;right:0;height:30px;background:linear-gradient(to bottom,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.cide-lyt-stack.scrolled-down:before{opacity:1}.cide-lyt-stack.scrolled-up:after{opacity:1}.cide-lyt-stack{position:relative;z-index:1;-webkit-overflow-scrolling:touch;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:var(--cide-theme-color-brand-primary) transparent}.cide-lyt-stack::-webkit-scrollbar{width:2px}.cide-lyt-stack::-webkit-scrollbar-track{background:transparent;margin:10px 0}.cide-lyt-stack::-webkit-scrollbar-thumb{background:var(--cide-theme-color-brand-primary);border-radius:4px;box-shadow:0 0 6px var(--cide-theme-color-brand-primary)}.cide-lyt-stack:hover::-webkit-scrollbar-thumb{background:var(--cide-theme-color-brand-primary);box-shadow:0 0 10px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.4)}.cide-lyt-stack:hover::-webkit-scrollbar{width:3px}.cide-lyt-stack:before,.cide-lyt-stack:after,.sidebar-content:before,.sidebar-content:after,.notification-panel:before,.notification-panel:after,.user-menu:before,.user-menu:after,.options-menu:before,.options-menu:after{content:\"\";position:absolute;left:0;right:0;height:24px;z-index:5;pointer-events:none;opacity:0;transition:opacity .3s ease}.cide-lyt-stack:before,.sidebar-content:before,.notification-panel:before,.user-menu:before,.options-menu:before{top:0;background:linear-gradient(to bottom,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),#fff0)}.cide-lyt-stack:after,.sidebar-content:after,.notification-panel:after,.user-menu:after,.options-menu:after{bottom:0;background:linear-gradient(to top,color-mix(in srgb,var(--cide-theme-sidebar-color) 90%,transparent),#fff0)}.cide-lyt-stack.is-scrollable-top:before,.sidebar-content.is-scrollable-top:before,.notification-panel.is-scrollable-top:before,.user-menu.is-scrollable-top:before,.options-menu.is-scrollable-top:before{opacity:1}.cide-lyt-stack.is-scrollable-bottom:after,.sidebar-content.is-scrollable-bottom:after,.notification-panel.is-scrollable-bottom:after,.user-menu.is-scrollable-bottom:after,.options-menu.is-scrollable-bottom:after{opacity:1}.collapse-toggle{margin-bottom:.5rem;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);z-index:5;border-radius:50%;overflow:hidden}.collapse-toggle:before{content:\"\";position:absolute;inset:0;background:var(--cide-theme-label-color);opacity:0;transition:opacity .2s ease;z-index:-1}.collapse-toggle:hover{transform:scale(1.1)}.collapse-toggle:hover:before{opacity:1}.collapse-toggle:active{transform:scale(.95);transition:transform .1s ease}.collapse-toggle:focus-visible{outline:2px solid var(--cide-theme-color-brand-primary);outline-offset:2px}.sidebar-scroll-content{width:100%;padding-top:.75rem;position:relative;z-index:1;overflow-y:auto;overflow-x:hidden;flex-grow:1;display:flex;flex-direction:column;align-items:center;padding:1rem 0;gap:.5rem;max-height:calc(100% - 8rem)}.nav-item{position:relative;width:30px;height:30px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;margin:4px 0;border-radius:10px;box-shadow:0 0 #3b82f600;transform-origin:center;overflow:hidden;color:var(--cide-theme-text-color)}.nav-item:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at center,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.08),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.01));opacity:0;transition:opacity .3s ease;z-index:-1}.nav-item:hover{background-color:color-mix(in srgb,var(--cide-theme-light-color) 90%,transparent);transform:translateY(-1px);box-shadow:0 2px 6px var(--cide-theme-shadow-color)}.nav-item:hover:before{opacity:1}.nav-item:active{transition:all .1s ease;transform:translateY(-1px);box-shadow:0 2px 6px var(--cide-theme-shadow-color)}.nav-item:active{transform:translateY(1px);transition:all .1s ease}.nav-item-active{background:linear-gradient(135deg,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.08),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.12));box-shadow:0 2px 8px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.15);transform:translateZ(0);color:var(--cide-theme-color-brand-primary)}.nav-item-active:after{content:\"\";position:absolute;bottom:-2px;left:30%;right:30%;height:2px;background:linear-gradient(to right,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0),var(--cide-theme-color-brand-primary),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0));border-radius:1px;animation:pulseGlow 2s infinite}@keyframes pulseGlow{0%{opacity:.4}50%{opacity:1}to{opacity:.4}}.nav-item-active:hover{background:linear-gradient(135deg,#3b82f61a,#3b82f626)}.nav-badge{z-index:5;transition:all .3s ease}.nav-item:hover .nav-badge{transform:scale(1.1)}.nav-tooltip{position:fixed;left:64px;top:50%;transform:translateY(-50%) translate(-5px);background-color:var(--sidebar-tooltip-bg);color:var(--sidebar-tooltip-color);padding:.4rem .75rem;border-radius:.25rem;font-size:.75rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 8px var(--cide-theme-shadow-color);letter-spacing:.01em;will-change:transform,opacity;max-width:220px;text-overflow:ellipsis;overflow:hidden;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}.nav-tooltip:before{content:\"\";position:absolute;top:50%;right:100%;transform:translateY(-50%);border-width:6px;border-style:solid;border-color:transparent var(--sidebar-tooltip-bg) transparent transparent;filter:drop-shadow(-2px 0px 1px rgba(0,0,0,.1))}.nav-item:hover .nav-tooltip{opacity:1;transform:translateY(-50%) translate(0);box-shadow:0 3px 12px var(--cide-theme-shadow-color)}.nav-item-active{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 10%,var(--cide-theme-light-color));border-radius:.5rem}.nav-item-active:before{opacity:0}.active-nav-icon{color:var(--cide-lyt-sidebar-nav-item-color-active)!important;filter:drop-shadow(0 0 3px rgba(59,130,246,.3))}.nav-indicator{position:absolute;left:-8px;top:50%;transform:translateY(-50%);width:3px;height:60%;background:var(--cide-theme-color-brand-primary);border-radius:0 4px 4px 0;opacity:0;transition:all .3s ease;box-shadow:0 0 6px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.5)}.nav-item-active .nav-indicator{opacity:1;left:0}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.sidebar-scroll-content .nav-item{opacity:0;animation:fadeSlideIn .5s forwards}.sidebar-scroll-content .nav-item:nth-child(1){animation-delay:.05s}.sidebar-scroll-content .nav-item:nth-child(2){animation-delay:.1s}.sidebar-scroll-content .nav-item:nth-child(3){animation-delay:.15s}.sidebar-scroll-content .nav-item:nth-child(4){animation-delay:.2s}.sidebar-scroll-content .nav-item:nth-child(5){animation-delay:.25s}.sidebar-scroll-content .nav-item:nth-child(6){animation-delay:.3s}.sidebar-scroll-content .nav-item:nth-child(7){animation-delay:.35s}.sidebar-scroll-content .nav-item:nth-child(8){animation-delay:.4s}.sidebar-scroll-content .nav-item:nth-child(9){animation-delay:.45s}.sidebar-scroll-content .nav-item:nth-child(10){animation-delay:.5s}.sidebar-scroll-content .nav-item:nth-child(11){animation-delay:.55s}.sidebar-scroll-content .nav-item:nth-child(12){animation-delay:.6s}.sidebar-scroll-content .nav-item:nth-child(13){animation-delay:.65s}.sidebar-scroll-content .nav-item:nth-child(14){animation-delay:.7s}.sidebar-scroll-content .nav-item:nth-child(15){animation-delay:.75s}cide-ele-icon[type=box]{cursor:pointer;width:40px;height:40px;display:flex;align-items:center;justify-content:center;border-radius:.5rem;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}cide-ele-icon[type=box]:hover{background-color:#f3f4f6;transform:translateY(-1px)}cide-ele-icon[type=box]:active{transform:translateY(0)}.theme-toggle{position:relative;overflow:hidden;padding:.5rem;border-radius:12px;background-color:transparent;border:2px solid transparent;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem}.theme-toggle:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:radial-gradient(circle at center,rgba(59,130,246,.2) 0%,transparent 70%);opacity:0;transition:opacity .3s ease;border-radius:.5rem}.theme-toggle:hover{background-color:var(--cide-theme-hover-bg-color, rgba(243, 244, 246, 1));border-color:var(--cide-theme-color-brand-primary, rgba(59, 130, 246, .3));transform:translateY(-1px);box-shadow:0 4px 12px #0000001a}.theme-toggle:hover:after{opacity:1}.theme-toggle:focus{outline:none;box-shadow:0 0 0 3px #3b82f64d}.theme-toggle cide-ele-icon{color:var(--cide-theme-icon-color, #6b7280);transition:all .2s ease;width:1.25rem;height:1.25rem;display:flex;align-items:center;justify-content:center}.theme-toggle:hover cide-ele-icon{color:var(--cide-theme-color-brand-primary, #3b82f6);transform:scale(1.1) rotate(10deg)}.dark-mode .theme-toggle:after{background:radial-gradient(circle at center,rgba(96,165,250,.2) 0%,transparent 70%)}.dark-mode .theme-toggle:hover{background-color:var(--cide-theme-dark-hover-bg-color, rgba(55, 65, 81, 1));border-color:var(--cide-theme-color-brand-primary, rgba(96, 165, 250, .3))}.notification-badge{font-size:.65rem;min-width:20px;height:20px;box-shadow:0 2px 4px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.25);transform-origin:center;position:relative;animation:pulseNotification 2s infinite;overflow:hidden;background-color:var(--cide-theme-color-brand-primary);color:var(--cide-theme-light-color)}.notification-badge:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,#ffffff4d,#fff0);opacity:0;transition:opacity .3s ease}.notification-badge:hover:before{opacity:1}@keyframes pulseNotification{0%{box-shadow:0 0 rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.4);transform:scale(1)}40%{transform:translateY(-5px)}50%{box-shadow:0 0 0 4px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0);transform:scale(1.05)}60%{transform:translateY(-2px)}to{box-shadow:0 0 rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0);transform:scale(1)}}.user-avatar{transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden;box-shadow:0 2px 5px #00000014;z-index:2}.user-avatar:hover{transform:scale(1.08);box-shadow:0 4px 12px #00000026}.user-avatar:before{content:\"\";position:absolute;inset:-3px;background:radial-gradient(circle,rgba(59,130,246,.3),transparent 70%);opacity:0;transition:opacity .3s ease;z-index:-1;border-radius:50%}.user-avatar:hover:before{opacity:1}.user-status{position:absolute;bottom:0;right:0;width:10px;height:10px;border-radius:50%;border:2px solid white;transition:all .3s ease}.user-status.online{background-color:#10b981;animation:statusPulse 2s infinite}.user-status.busy{background-color:#f59e0b}.user-status.offline{background-color:#6b7280}.user-status.dnd{background-color:#ef4444}@keyframes statusPulse{0%{box-shadow:0 0 #10b98166}70%{box-shadow:0 0 0 6px #10b98100}to{box-shadow:0 0 #10b98100}}.dark-mode .user-avatar{box-shadow:0 2px 5px #0003}.dark-mode .user-avatar:before{background:radial-gradient(circle,rgba(96,165,250,.4),transparent 70%)}.dark-mode .user-status{border-color:#0f172a}.user-dropdown{position:relative}.user-menu{animation:fadeInUp .2s cubic-bezier(.4,0,.2,1);z-index:30;margin-left:10px;margin-bottom:5px;max-height:80vh;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;box-shadow:0 10px 25px #0000001a;border-radius:.75rem;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);position:absolute;left:100%;bottom:0;margin-top:.5rem;margin-left:.5rem;width:14rem;border-radius:.375rem;box-shadow:0 10px 15px -3px var(--cide-theme-shadow-color),0 4px 6px -2px var(--cide-theme-shadow-color);background-color:var(--cide-theme-sidebar-color);border:1px solid var(--cide-theme-border-color);z-index:50;overflow:hidden}.user-menu a{color:var(--cide-theme-text-color);transition:background-color .2s ease}.user-menu a:hover{background-color:var(--cide-theme-hover-bg-color)}.user-menu .tw-text-red-700{color:var(--cide-theme-error-color)}.user-menu::-webkit-scrollbar{width:3px}.user-menu::-webkit-scrollbar-track{background:transparent}.user-menu::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.user-status{transition:all .3s ease;box-shadow:0 0 0 2px var(--cide-theme-sidebar-color)}.user-status.online{animation:pulseOnline 2s infinite}@keyframes pulseOnline{0%{box-shadow:0 0 #22c55e66,0 0 0 2px var(--cide-theme-sidebar-color)}70%{box-shadow:0 0 0 4px #22c55e00,0 0 0 2px var(--cide-theme-sidebar-color)}to{box-shadow:0 0 #22c55e00,0 0 0 2px var(--cide-theme-sidebar-color)}}.cide-lyt-sidebar-menu{width:auto;background-color:var(--cide-theme-sidebar-color);border-left:1px solid rgba(243,244,246,1);transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;overflow:hidden;max-height:100%;position:relative;will-change:width,opacity,transform}.sidebar-header{background-color:var(--cide-theme-sidebar-color);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);position:sticky;top:0;z-index:10;border-bottom:1px solid rgba(243,244,246,.8);padding:1rem .75rem;transition:all .3s ease}.sidebar-header.scrolled{box-shadow:0 4px 10px -8px #0000001a}.back-button{transition:all .2s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%}.back-button:hover{transform:translate(-2px);background-color:#f3f4f6}.back-button:active{transform:translate(-1px) scale(.95)}.search-wrapper{position:relative;transition:all .3s ease}.search-wrapper:focus-within{transform:translateY(-1px)}.search-shortcut{font-size:.65rem;opacity:.7;letter-spacing:.02em;pointer-events:none;padding:.1rem .3rem;background-color:#f3f4f6cc;border-radius:.25rem;border:1px solid rgba(229,231,235,.8);transition:all .3s ease}.search-wrapper:focus-within .search-shortcut{opacity:.5}.options-menu{animation:fadeInUp .2s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 12px #0000001a;max-height:70vh;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;border-radius:.75rem;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.options-menu::-webkit-scrollbar{width:3px}.options-menu::-webkit-scrollbar-track{background:transparent}.options-menu::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.sidebar-section{position:relative;padding:.5rem 0}.sidebar-section:not(:first-child):before{content:\"\";height:1px;background:linear-gradient(to right,#e5e7eb00,#e5e7eb80,#e5e7eb00);position:absolute;top:0;left:10%;right:10%}.tw-text-xs.tw-font-semibold.tw-text-gray-500{font-size:.7rem;letter-spacing:.05em;text-transform:uppercase;position:relative;display:inline-block;padding:0 .5rem;color:var(--cide-theme-label-color)}.tw-text-xs.tw-font-semibold.tw-text-gray-500:after{content:\"\";position:absolute;height:3px;width:2rem;background:linear-gradient(to right,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.5),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0));bottom:-4px;left:.5rem;border-radius:3px;transition:width .3s ease}.sidebar-section:hover .tw-text-xs.tw-font-semibold.tw-text-gray-500:after{width:3rem}.menu-item{text-decoration:none;transition:all .25s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden;margin:3px 0;border-radius:.5rem;transform:translateZ(0);color:var(--cide-theme-text-color)}.menu-item:hover{transform:translate(3px);background-color:var(--cide-theme-light-color);box-shadow:0 1px 3px var(--sidebar-shadow-color)}.active-menu-item{background-color:rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.08);position:relative;box-shadow:0 2px 5px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.1)}.active-menu-item:after{content:\"\";position:absolute;left:0;top:0;height:100%;width:3px;background:linear-gradient(to bottom,var(--cide-theme-color-brand-primary),var(--cide-theme-secondary-color));border-radius:0 2px 2px 0;box-shadow:0 0 6px rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.3);animation:pulseLeftBorder 2s infinite}@keyframes pulseLeftBorder{0%{opacity:.7}50%{opacity:1}to{opacity:.7}}.dark-mode .active-menu-item{background-color:#1e293b80;box-shadow:0 2px 5px #0f172a33}.dark-mode .active-menu-item:after{background:linear-gradient(to bottom,#60a5fa,#3b82f6);box-shadow:0 0 8px #60a5fa66}.quick-actions{transition:all .3s ease;opacity:0;transform:translate(5px);z-index:2}.menu-item:hover .quick-actions{opacity:1;transform:translate(0)}.badge{font-size:.7rem;padding:1px 5px;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;z-index:1}.menu-item:hover .badge{background-color:#d1d5db}.new-badge{animation:pulse 2s infinite;z-index:1}@keyframes pulse{0%{box-shadow:0 0 #3b82f666}70%{box-shadow:0 0 0 4px #3b82f600}to{box-shadow:0 0 #3b82f600}}.toggle-switch{display:inline-flex;align-items:center;transition:all .2s ease}.toggle-switch:hover{transform:scale(1.05)}.toggle-track{position:relative;cursor:pointer;transition:background-color .3s ease;overflow:hidden}.toggle-track:after{content:\"\";position:absolute;top:50%;left:50%;width:5px;height:5px;background:#ffffffb3;opacity:0;border-radius:100%;transform:scale(1) translate(-50%,-50%);transform-origin:50% 50%}.toggle-track.clicked:after{animation:ripple .6s ease-out}@keyframes ripple{0%{opacity:1;transform:scale(0) translate(-50%,-50%)}to{opacity:0;transform:scale(20) translate(-50%,-50%)}}.alert-box{animation:fadeIn .5s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 8px #fde04726;border-radius:.5rem;position:relative;overflow:hidden}.alert-box:before{content:\"\";position:absolute;inset:0;background-image:url(\"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23fef3c7' fill-opacity='0.4' fill-rule='evenodd'%3E%3Ccircle cx='3' cy='3' r='3'/%3E%3Ccircle cx='13' cy='13' r='3'/%3E%3C/g%3E%3C/svg%3E\");opacity:.3;pointer-events:none}@keyframes fadeIn{0%{opacity:0;transform:translateY(5px)}to{opacity:1;transform:translateY(0)}}.notification-panel{animation:fadeIn .3s cubic-bezier(.4,0,.2,1);overflow-y:auto;max-height:100vh;scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;padding:.75rem;border-radius:.75rem}.notification-panel::-webkit-scrollbar{width:3px}.notification-panel::-webkit-scrollbar-track{background:transparent}.notification-panel::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.notification-panel:hover::-webkit-scrollbar-thumb{background-color:#9ca3af80}.notification-item{transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;border-radius:.5rem;margin-bottom:.5rem;position:relative;overflow:hidden}.notification-item:hover{background-color:#f3f4f6;transform:translate(1px)}.notification-item:active{transform:scale(.99)}.notification-item.unread:before{content:\"\";position:absolute;left:0;top:0;height:100%;width:3px;background-color:var(--cide-theme-color-brand-primary);border-radius:0 2px 2px 0}.sidebar-footer{background-color:var(--cide-theme-light-color);position:sticky;bottom:0;z-index:10;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-top:1px solid rgba(243,244,246,.8);padding:.75rem;transition:all .3s ease}.upgrade-button{transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden;border-radius:.5rem;color:var(--cide-theme-text-color)}.upgrade-button:hover{background-color:rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.1);transform:translateY(-1px)}.upgrade-button:active{transform:translateY(0) scale(.98)}.upgrade-button:after{content:\"\";position:absolute;width:12px;height:12px;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%233b82f6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'%3E%3C/polygon%3E%3C/svg%3E\");top:5px;right:10px;opacity:.5;transform:scale(0);transition:all .3s ease}.upgrade-button:hover:after{transform:scale(1) rotate(20deg);animation:float 3s ease-in-out infinite}@keyframes float{0%,to{transform:scale(1) rotate(5deg) translate(0)}50%{transform:scale(1.1) rotate(-5deg) translate(-2px,-2px)}}.storage-bar{background-color:#e5e7eb80;overflow:hidden;border-radius:4px;height:6px;position:relative}.storage-fill{background:linear-gradient(to right,var(--cide-theme-color-brand-primary),var(--cide-theme-secondary-color));transition:width 1s cubic-bezier(.4,0,.2,1);position:relative;height:100%;border-radius:4px}.storage-fill:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,#fff0,#fff3,#fff0);animation:shimmer 2s infinite}@keyframes shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.plan-features{transition:all .3s ease;padding:.5rem;border-radius:.5rem}.plan-features:hover{background-color:#f3f4f680}.feature-item{display:flex;align-items:center;margin:.25rem 0;position:relative;padding-left:1.25rem;color:var(--cide-theme-text-color)}.feature-item:before{content:\"\\2713\";position:absolute;left:0;color:var(--cide-theme-color-brand-primary);font-size:.7rem;top:50%;transform:translateY(-50%)}.sidebar-content{scrollbar-width:thin;scrollbar-color:rgba(209,213,219,.5) transparent;flex:1;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;padding:0 .75rem;transition:all .3s ease;position:relative;scroll-behavior:smooth;scroll-padding:1rem;overscroll-behavior:contain}.sidebar-content:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:30px;background:linear-gradient(to top,rgba(255,255,255,.9),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.sidebar-content:before{content:\"\";position:absolute;top:0;left:0;right:0;height:30px;background:linear-gradient(to bottom,rgba(255,255,255,.9),transparent);pointer-events:none;opacity:0;transition:opacity .3s ease;z-index:2}.sidebar-content.scrolled-down:before{opacity:1}.sidebar-content.scrolled-up:after{opacity:1}.sidebar-content::-webkit-scrollbar{width:3px}.sidebar-content::-webkit-scrollbar-track{background:transparent}.sidebar-content::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.sidebar-content:hover::-webkit-scrollbar-thumb{background-color:#9ca3af80}.cide-lyt-sidebar *::-webkit-scrollbar{width:3px}.cide-lyt-sidebar *::-webkit-scrollbar-track{background:transparent}.cide-lyt-sidebar *::-webkit-scrollbar-thumb{background-color:#d1d5db80;border-radius:20px}.cide-lyt-sidebar *:hover::-webkit-scrollbar-thumb{background-color:#9ca3af80}.sidebar-section{transition:all .3s ease-out;transform:translateY(0);opacity:1}.sidebar-section.animate-in{animation:slideInUp .4s cubic-bezier(.4,0,.2,1) forwards}@keyframes slideInUp{0%{opacity:.5;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}.sidebar-section:nth-child(1).animate-in{animation-delay:.1s}.sidebar-section:nth-child(2).animate-in{animation-delay:.2s}.sidebar-section:nth-child(3).animate-in{animation-delay:.3s}.sidebar-section:nth-child(4).animate-in{animation-delay:.4s}.sidebar-section:nth-child(5).animate-in{animation-delay:.5s}:host ::ng-deep cide-ele-input{width:100%}:host ::ng-deep .cide-input-field{border-radius:8px;background-color:#f9fafb;border:1px solid transparent;transition:all .2s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .cide-input-field:focus-within{border-color:#d1d5db;background-color:#fff;box-shadow:0 2px 8px #0000000d;transform:translateY(-1px)}.cide-lyt-sidebar *:focus{outline:none;box-shadow:0 0 0 2px #3b82f64d}.dark-mode{--sidebar-tooltip-bg: var(--cide-theme-dark-color);--sidebar-tooltip-color: var(--cide-theme-light-color);background-color:#1e293b;color:var(--cide-theme-light-color)}.dark-mode .cide-lyt-stack{background-color:#0f172a;border-right-color:#1e293b}.dark-mode .cide-lyt-stack:before,.dark-mode .cide-lyt-stack:after,.dark-mode .sidebar-content:before,.dark-mode .sidebar-content:after{background:linear-gradient(to bottom,rgba(15,23,42,.9),transparent)}.dark-mode .sidebar-content:after,.dark-mode .cide-lyt-stack:after{background:linear-gradient(to top,rgba(15,23,42,.9),transparent)}.dark-mode .cide-lyt-sidebar-menu{background-color:#0f172a;border-left-color:#1e293b}.dark-mode .sidebar-header,.dark-mode .sidebar-footer{background-color:#0f172af2;border-color:#1e293b}.dark-mode .tw-text-gray-700{color:#e2e8f0}.dark-mode .tw-text-gray-500{color:#94a3b8}.dark-mode .tw-bg-gray-50,.dark-mode .menu-item:hover{background-color:#1e293b80}.dark-mode .tw-bg-gray-100{background-color:#1e293b}.dark-mode .tw-bg-gray-200{background-color:#334155}.dark-mode .nav-item:hover:before{background-color:#60a5fa1a}.dark-mode .back-button:hover{background-color:#1e293bb3}.dark-mode .sidebar-section:not(:first-child):before{background:linear-gradient(to right,#33415500,#33415580,#33415500)}.dark-mode .toggle-track:after{background:#00000080}.dark-mode .alert-box:before{opacity:.2}.dark-mode :host ::ng-deep .cide-input-field{background-color:#1e293b;color:#e2e8f0}.dark-mode :host ::ng-deep .cide-input-field:focus-within{border-color:#475569;background-color:#0f172a}.dark-mode *::-webkit-scrollbar-thumb{background-color:#47556980}.dark-mode *:hover::-webkit-scrollbar-thumb{background-color:#64748b99}.nav-item-active .nav-indicator{animation:pulseIndicator 2s infinite}@keyframes pulseIndicator{0%{box-shadow:0 0 #3b82f666}70%{box-shadow:0 0 0 3px #3b82f600}to{box-shadow:0 0 #3b82f600}}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}@media (prefers-reduced-motion: reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}@media screen and (max-width: 1024px){.cide-lyt-sidebar-menu{width:220px}.sidebar-content{padding:0 .5rem}.tw-text-xs.tw-font-semibold.tw-text-gray-500{font-size:.65rem}}@media screen and (max-width: 768px){.cide-lyt-sidebar-menu{position:fixed;width:100%;max-width:280px;left:72px;z-index:40;box-shadow:0 10px 25px #00000026;border-radius:0 1rem 1rem 0}.cide-lyt-sidebar.collapsed .cide-lyt-sidebar-menu{left:-100%}.sidebar-overlay{position:fixed;inset:0;background-color:#0000004d;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;pointer-events:none;transition:opacity .3s ease;z-index:35}.sidebar-overlay.active{opacity:1;pointer-events:auto}}.cide-lyt-sidebar,.cide-lyt-stack,.cide-lyt-sidebar-menu,.sidebar-logo,.nav-item,.menu-item,.user-avatar,.upgrade-button,.storage-fill,.notification-item,.sidebar-section.animate-in{transform:translateZ(0);backface-visibility:hidden;perspective:1000px;will-change:transform,opacity}.dark-mode .nav-item-active{background:linear-gradient(135deg,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.15),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),.1));box-shadow:0 2px 8px #1e40af40}.dark-mode .nav-item-active:after{background:linear-gradient(to right,rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0),rgba(var(--cide-theme-secondary-color-rgb, 74, 222, 128),.6),rgba(var(--cide-theme-color-brand-primary-rgb, 59, 130, 246),0));box-shadow:0 0 5px rgba(var(--cide-theme-secondary-color-rgb, 74, 222, 128),.5)}.dark-mode .nav-item:hover{background-color:#33415580;box-shadow:0 2px 8px #0f172a4d}.dark-mode .nav-tooltip{background-color:#0f172ae6;box-shadow:0 3px 10px #0006;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.dark-mode .nav-tooltip:before{border-color:transparent rgba(15,23,42,.9) transparent transparent}.text-theme-primary{color:var(--cide-theme-color-brand-primary)}.text-theme-secondary{color:var(--cide-theme-secondary-color)}.text-theme-text{color:var(--cide-theme-text-color)}.text-theme-label{color:var(--cide-theme-label-color)}.bg-theme-primary{background-color:var(--cide-theme-color-brand-primary)}.bg-theme-secondary{background-color:var(--cide-theme-secondary-color)}.bg-theme-light{background-color:var(--cide-theme-light-color)}.bg-theme-dark{background-color:var(--cide-theme-dark-color)}.bg-theme-sidebar{background-color:var(--cide-theme-sidebar-color)}.border-theme-light{border-color:var(--cide-theme-light-color)}.border-theme-primary{border-color:var(--cide-theme-color-brand-primary)}.hover-bg-theme-light:hover{background-color:var(--cide-theme-light-color)}.hover-text-theme-primary:hover{color:var(--cide-theme-color-brand-primary)}.tw-text-gray-500{color:var(--cide-theme-icon-color)}.hover\\:tw-text-gray-700:hover{color:var(--cide-theme-icon-hover-color)}.tw-text-gray-700{color:var(--cide-theme-text-color)}.tw-text-red-500,.tw-text-red-600,.tw-text-red-700{color:var(--cide-theme-error-color)}.tw-bg-gray-200{background-color:var(--cide-theme-border-color)}.hover\\:tw-bg-gray-100:hover{background-color:var(--cide-theme-hover-bg-color)}.tw-bg-white{background-color:var(--cide-theme-sidebar-color)}.tw-bg-green-500{background-color:var(--cide-theme-success-color)}.tw-border-gray-100,.tw-divide-gray-100{border-color:var(--cide-theme-border-color)}.tw-shadow-lg{box-shadow:0 10px 15px -3px var(--cide-theme-shadow-color),0 4px 6px -2px var(--cide-theme-shadow-color)}.tw-shadow-sm,.hover\\:tw-shadow:hover{box-shadow:0 1px 2px 0 var(--cide-theme-shadow-color)}.active-nav-icon{color:var(--cide-theme-color-brand-primary)!important}.nav-item{display:flex;align-items:center;justify-content:center;position:relative;border-radius:.375rem}.nav-item:hover{background-color:var(--cide-theme-hover-bg-color)}.nav-item:hover cide-ele-icon{color:var(--cide-theme-icon-hover-color)}.nav-item-active{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 10%,var(--cide-theme-light-color))}.nav-item-active cide-ele-icon{color:var(--cide-theme-color-brand-primary)}.nav-indicator{opacity:0;transition:opacity .2s ease}.nav-item-active .nav-indicator{opacity:1}.nav-tooltip{position:absolute;left:100%;top:50%;transform:translateY(-50%);background-color:var(--sidebar-tooltip-bg);color:var(--sidebar-tooltip-color);padding:.25rem .5rem;border-radius:.25rem;font-size:.75rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s ease;margin-left:.5rem;z-index:30;box-shadow:0 2px 8px var(--cide-theme-shadow-color)}.nav-item:hover .nav-tooltip{opacity:1}.nav-badge{position:absolute;top:-.25rem;right:-.25rem;width:.375rem;height:.375rem;border-radius:9999px;background-color:var(--cide-theme-error-color);animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes ping{75%,to{transform:scale(2);opacity:0}}.user-avatar{width:2.5rem;height:2.5rem;border-radius:9999px;overflow:hidden;background-color:var(--cide-theme-border-color);border:2px solid var(--cide-theme-sidebar-color);box-shadow:0 1px 2px var(--cide-theme-shadow-color);cursor:pointer;transition:box-shadow .2s ease}.user-avatar:hover{box-shadow:0 2px 4px var(--cide-theme-shadow-color)}.user-status{position:absolute;bottom:0;right:0;width:.75rem;height:.75rem;border-radius:9999px;background-color:var(--cide-theme-success-color);border:2px solid var(--cide-theme-sidebar-color)}.dark-mode .nav-item:hover{background-color:#ffffff1a}.dark-mode .nav-item-active{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 30%,var(--cide-theme-dark-color))}.dark-mode .user-avatar{border-color:var(--cide-theme-dark-color);background-color:#ffffff1a}.dark-mode .user-status{border-color:var(--cide-theme-dark-color)}.dark-mode .user-menu{background-color:var(--cide-theme-dark-color);border-color:#ffffff1a}.dark-mode .user-menu a{color:#fffc}.dark-mode .user-menu a:hover{background-color:#ffffff1a}.section-header{transition:all .2s ease;border:1px solid transparent}.section-header:hover{background-color:#f8fafc!important;border-color:#e2e8f0}.section-header:focus{outline:none;box-shadow:0 0 0 2px #3b82f6;border-color:#3b82f6}.section-content{overflow:hidden;transition:all .3s ease}.section-header .tw-rotate-90{transform:rotate(90deg)}.section-content .tw-border-l{border-left-color:#d1d5db}.section-header:hover .tw-text-gray-500{color:#6b7280}.section-header:hover .tw-text-gray-800{color:#374151}\n"] }]
|
|
2070
|
-
}], ctorParameters: () => [], propDecorators: { onDocumentClick: [{
|
|
2071
|
-
type: HostListener,
|
|
2072
|
-
args: ['document:click', ['$event']]
|
|
2073
|
-
}] } });
|
|
2074
|
-
|
|
2075
|
-
/**
|
|
2076
|
-
* A custom route reuse strategy that allows storing and retrieving routes
|
|
2077
|
-
* to persist component state, especially for tabbed navigation.
|
|
2078
|
-
*/
|
|
2079
|
-
class CustomRouteReuseStrategy {
|
|
2080
|
-
storedRoutes = {};
|
|
2081
|
-
// Generates a unique key for a route.
|
|
2082
|
-
// For routes marked with 'reuseTab', this key includes sorted query parameters
|
|
2083
|
-
// to ensure distinct states for tabs differing by query params.
|
|
2084
|
-
getPathKey(route) {
|
|
2085
|
-
let path = route.pathFromRoot
|
|
2086
|
-
.map(r => r.url.map(segment => segment.path).join('/'))
|
|
2087
|
-
.filter(p => p.length > 0)
|
|
2088
|
-
.join('/');
|
|
2089
|
-
// If the route is marked for tab reuse and has query parameters,
|
|
2090
|
-
// append them to the key in a sorted, consistent manner.
|
|
2091
|
-
if (route.data && route.data['reuseTab'] && route.queryParamMap.keys.length > 0) {
|
|
2092
|
-
const queryParams = route.queryParamMap.keys
|
|
2093
|
-
.sort() // Ensure consistent order of query parameters
|
|
2094
|
-
.map(key => `${key}=${route.queryParamMap.get(key)}`)
|
|
2095
|
-
.join('&');
|
|
2096
|
-
path += '?' + queryParams;
|
|
2097
|
-
}
|
|
2098
|
-
return path;
|
|
2099
|
-
}
|
|
2100
|
-
// Determines if this route (and its subtree) should be detached to be reused later.
|
|
2101
|
-
shouldDetach(route) {
|
|
2102
|
-
return !!(route.data && route.data['reuseTab']);
|
|
2103
|
-
}
|
|
2104
|
-
// Stores the detached route.
|
|
2105
|
-
store(route, handle) {
|
|
2106
|
-
const pathKey = this.getPathKey(route);
|
|
2107
|
-
if (handle && route.data && route.data['reuseTab'] && pathKey) {
|
|
2108
|
-
this.storedRoutes[pathKey] = handle;
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
// Determines if this route (and its subtree) should be reattached.
|
|
2112
|
-
shouldAttach(route) {
|
|
2113
|
-
const pathKey = this.getPathKey(route);
|
|
2114
|
-
return !!pathKey && !!this.storedRoutes[pathKey];
|
|
2115
|
-
}
|
|
2116
|
-
// Retrieves the previously stored route.
|
|
2117
|
-
retrieve(route) {
|
|
2118
|
-
const pathKey = this.getPathKey(route);
|
|
2119
|
-
if (!pathKey || !this.storedRoutes[pathKey]) {
|
|
2120
|
-
return null;
|
|
2121
|
-
}
|
|
2122
|
-
return this.storedRoutes[pathKey];
|
|
2123
|
-
}
|
|
2124
|
-
// Determines if a route should be reused.
|
|
2125
|
-
shouldReuseRoute(future, curr) {
|
|
2126
|
-
return future.routeConfig === curr.routeConfig;
|
|
2127
|
-
}
|
|
2128
|
-
// Clears a stored route, typically when a tab is closed.
|
|
2129
|
-
// This method now properly destroys the component instance to prevent memory leaks
|
|
2130
|
-
// and ensure old data doesn't persist when reopening the same route.
|
|
2131
|
-
clearStoredRoute(pathKey) {
|
|
2132
|
-
const handle = this.storedRoutes[pathKey];
|
|
2133
|
-
if (handle) {
|
|
2134
|
-
// Properly destroy the component instance to prevent memory leaks
|
|
2135
|
-
// and ensure clean state when the route is reopened
|
|
2136
|
-
try {
|
|
2137
|
-
// The handle contains the component instance and view container
|
|
2138
|
-
// We need to destroy it properly to clean up resources
|
|
2139
|
-
if (handle && typeof handle === 'object' && 'componentRef' in handle) {
|
|
2140
|
-
const componentRef = handle.componentRef;
|
|
2141
|
-
if (componentRef && typeof componentRef.destroy === 'function') {
|
|
2142
|
-
componentRef.destroy();
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
// Also destroy the view container if it exists
|
|
2146
|
-
if (handle && typeof handle === 'object' && 'viewContainerRef' in handle) {
|
|
2147
|
-
const viewContainerRef = handle.viewContainerRef;
|
|
2148
|
-
if (viewContainerRef && typeof viewContainerRef.clear === 'function') {
|
|
2149
|
-
viewContainerRef.clear();
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
catch (error) {
|
|
2154
|
-
console.warn('Error destroying route handle:', error);
|
|
2155
|
-
}
|
|
2156
|
-
// Remove from stored routes
|
|
2157
|
-
delete this.storedRoutes[pathKey];
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
// Clears all stored routes (useful for cleanup)
|
|
2161
|
-
clearAllStoredRoutes() {
|
|
2162
|
-
Object.keys(this.storedRoutes).forEach(pathKey => {
|
|
2163
|
-
this.clearStoredRoute(pathKey);
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
// Gets the count of stored routes (useful for debugging)
|
|
2167
|
-
getStoredRoutesCount() {
|
|
2168
|
-
return Object.keys(this.storedRoutes).length;
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
/**
|
|
2173
|
-
* Creates the default state for a new tab.
|
|
2174
|
-
*/
|
|
2175
|
-
const createDefaultComponentState = () => ({
|
|
2176
|
-
sideDrawer: { isOpen: false, activeComponent: null, componentState: {} },
|
|
2177
|
-
footer: { someData: '' },
|
|
2178
|
-
console: { history: [] },
|
|
2179
|
-
});
|
|
2180
|
-
class TabStateService {
|
|
2181
|
-
// Holds the state for all open tabs, keyed by tab ID.
|
|
2182
|
-
_tabsState = new BehaviorSubject(new Map());
|
|
2183
|
-
// Holds the ID of the currently active tab.
|
|
2184
|
-
_activeTabId = new BehaviorSubject(null);
|
|
2185
|
-
/** An observable of all open tabs. */
|
|
2186
|
-
tabs$ = this._tabsState.pipe(map(tabsMap => Array.from(tabsMap.values())));
|
|
2187
|
-
/** An observable of the currently active tab's ID. */
|
|
2188
|
-
activeTabId$ = this._activeTabId.asObservable().pipe(distinctUntilChanged());
|
|
2189
|
-
/** An observable of the state of the currently active tab. */
|
|
2190
|
-
activeTabState$ = this.activeTabId$.pipe(map(activeId => (activeId ? this._tabsState.getValue().get(activeId) ?? null : null)),
|
|
2191
|
-
// Use a deep-ish comparison to prevent emissions when only the object reference changes
|
|
2192
|
-
distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)));
|
|
2193
|
-
/** An observable of just the component-specific state for the active tab. */
|
|
2194
|
-
activeComponentState$ = this.activeTabState$.pipe(map(activeTab => activeTab?.componentState ?? null));
|
|
2195
|
-
/**
|
|
2196
|
-
* Adds a new tab to the state.
|
|
2197
|
-
* @param title The title for the new tab.
|
|
2198
|
-
* @param id An optional unique ID. If not provided, one will be generated.
|
|
2199
|
-
* @returns The ID of the newly created tab.
|
|
2200
|
-
*/
|
|
2201
|
-
addTab(title, id) {
|
|
2202
|
-
const newTabId = id || `tab_${Date.now()}_${Math.random()}`;
|
|
2203
|
-
const newTab = {
|
|
2204
|
-
id: newTabId,
|
|
2205
|
-
title,
|
|
2206
|
-
componentState: createDefaultComponentState(),
|
|
2207
|
-
};
|
|
2208
|
-
const currentTabs = this._tabsState.getValue();
|
|
2209
|
-
const newTabs = new Map(currentTabs).set(newTabId, newTab);
|
|
2210
|
-
this._tabsState.next(newTabs);
|
|
2211
|
-
this.setActiveTab(newTabId);
|
|
2212
|
-
return newTabId;
|
|
2213
|
-
}
|
|
2214
|
-
/**
|
|
2215
|
-
* Removes a tab from the state.
|
|
2216
|
-
* @param tabIdToRemove The ID of the tab to remove.
|
|
2217
|
-
*/
|
|
2218
|
-
removeTab(tabIdToRemove) {
|
|
2219
|
-
const currentTabs = this._tabsState.getValue();
|
|
2220
|
-
if (!currentTabs.has(tabIdToRemove)) {
|
|
2221
|
-
return;
|
|
2222
|
-
}
|
|
2223
|
-
const newTabs = new Map(currentTabs);
|
|
2224
|
-
newTabs.delete(tabIdToRemove);
|
|
2225
|
-
this._tabsState.next(newTabs);
|
|
2226
|
-
}
|
|
2227
|
-
constructor() {
|
|
2228
|
-
// You can initialize with default tabs if needed
|
|
2229
|
-
// For example:
|
|
2230
|
-
// this.addTab('Welcome Tab');
|
|
2231
|
-
}
|
|
2232
|
-
/**
|
|
2233
|
-
* Sets the currently active tab.
|
|
2234
|
-
* @param tabId The unique identifier of the tab to activate.
|
|
2235
|
-
*/
|
|
2236
|
-
setActiveTab(tabId) {
|
|
2237
|
-
if (this._activeTabId.getValue() !== tabId) {
|
|
2238
|
-
this._activeTabId.next(tabId);
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
/**
|
|
2242
|
-
* Updates the state for the currently active tab.
|
|
2243
|
-
* This performs a deep merge to avoid overwriting nested state.
|
|
2244
|
-
* @param partialState The partial state to update for the active tab.
|
|
2245
|
-
*/
|
|
2246
|
-
updateActiveTabState(partialState) {
|
|
2247
|
-
const activeId = this._activeTabId.getValue();
|
|
2248
|
-
if (!activeId) {
|
|
2249
|
-
console.warn('Cannot update state, no active tab.');
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
const tabs = this._tabsState.getValue();
|
|
2253
|
-
const activeTabState = tabs.get(activeId);
|
|
2254
|
-
if (activeTabState) {
|
|
2255
|
-
// Use lodash merge for a deep merge
|
|
2256
|
-
const updatedState = merge({}, activeTabState, { componentState: partialState });
|
|
2257
|
-
const newTabsState = new Map(tabs);
|
|
2258
|
-
newTabsState.set(activeId, updatedState);
|
|
2259
|
-
this._tabsState.next(newTabsState);
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
/**
|
|
2263
|
-
* Gets an observable for a specific tab's state.
|
|
2264
|
-
* @param tabId The ID of the tab to observe.
|
|
2265
|
-
*/
|
|
2266
|
-
getTabState(tabId) {
|
|
2267
|
-
return this._tabsState.pipe(map(tabs => tabs.get(tabId)));
|
|
2268
|
-
}
|
|
2269
|
-
/**
|
|
2270
|
-
* Determines the ID of the next tab to activate when a tab is closed.
|
|
2271
|
-
* @param idToRemove The ID of the tab being removed.
|
|
2272
|
-
* @returns The ID of the next tab to activate, or null if no tabs are left.
|
|
2273
|
-
*/
|
|
2274
|
-
getNextActiveTabId(idToRemove) {
|
|
2275
|
-
const tabsMap = this._tabsState.getValue();
|
|
2276
|
-
if (tabsMap.size <= 1) {
|
|
2277
|
-
return null;
|
|
2278
|
-
}
|
|
2279
|
-
const tabIds = Array.from(tabsMap.keys());
|
|
2280
|
-
const removedIndex = tabIds.indexOf(idToRemove);
|
|
2281
|
-
// If it's the last tab in the list, activate the one before it. Otherwise, activate the next one.
|
|
2282
|
-
const nextIndex = removedIndex === tabIds.length - 1 ? removedIndex - 1 : removedIndex + 1;
|
|
2283
|
-
return tabIds[nextIndex];
|
|
2284
|
-
}
|
|
2285
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2286
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabStateService, providedIn: 'root' });
|
|
2287
|
-
}
|
|
2288
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabStateService, decorators: [{
|
|
2289
|
-
type: Injectable,
|
|
2290
|
-
args: [{
|
|
2291
|
-
providedIn: 'root',
|
|
2292
|
-
}]
|
|
2293
|
-
}], ctorParameters: () => [] });
|
|
2294
|
-
|
|
2295
|
-
class CideLytRequestService {
|
|
2296
|
-
requestVisible = false;
|
|
2297
|
-
// Modern Angular v20+ pattern: Use Signals instead of BehaviorSubject
|
|
2298
|
-
tabsSignal = signal([], ...(ngDevMode ? [{ debugName: "tabsSignal" }] : []));
|
|
2299
|
-
activeTabIdSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeTabIdSignal" }] : []));
|
|
2300
|
-
// Computed signals for derived state and public readonly access
|
|
2301
|
-
tabs = computed(() => this.tabsSignal(), ...(ngDevMode ? [{ debugName: "tabs" }] : []));
|
|
2302
|
-
activeTabId = computed(() => this.activeTabIdSignal(), ...(ngDevMode ? [{ debugName: "activeTabId" }] : []));
|
|
2303
|
-
activeTab = computed(() => this.tabsSignal().find(tab => tab.active), ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
2304
|
-
// Use inject() for all dependencies
|
|
2305
|
-
router = inject(Router);
|
|
2306
|
-
routeReuseStrategy = inject(RouteReuseStrategy);
|
|
2307
|
-
tabStateService = inject(TabStateService);
|
|
2308
|
-
sidedrawerService = inject(CideLytSidedrawerService);
|
|
2309
|
-
sharedService = inject(CideLytSharedService);
|
|
2310
|
-
constructor() {
|
|
2311
|
-
// Initialize router
|
|
2312
|
-
this.router = inject(Router);
|
|
2313
|
-
this.router.routeReuseStrategy = new CustomRouteReuseStrategy();
|
|
2314
|
-
// Register tab management callback with shared service to avoid circular dependency
|
|
2315
|
-
this.sharedService.registerTabManagement(this.handleRouteBasedTabManagement.bind(this));
|
|
2316
|
-
// Register request visibility callback
|
|
2317
|
-
this.sharedService.registerRequestVisibility(this.handleRequestVisibility.bind(this));
|
|
2318
|
-
}
|
|
2319
|
-
/**
|
|
2320
|
-
* Handle request visibility changes from shared service
|
|
2321
|
-
*/
|
|
2322
|
-
handleRequestVisibility(show) {
|
|
2323
|
-
if (show) {
|
|
2324
|
-
this.showRequest();
|
|
2325
|
-
}
|
|
2326
|
-
else {
|
|
2327
|
-
this.hideRequest();
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
/**
|
|
2331
|
-
* Handle sidebar sync when tab becomes active
|
|
2332
|
-
*/
|
|
2333
|
-
handleSidebarSync(tabInfo) {
|
|
2334
|
-
// The sidebar sync will be handled automatically by the shared service
|
|
2335
|
-
// when page data is loaded, so we don't need additional logic here
|
|
2336
|
-
console.log('🔗 REQUEST SERVICE: Tab activated, sidebar sync handled by shared service:', tabInfo.title);
|
|
2337
|
-
}
|
|
2338
|
-
/**
|
|
2339
|
-
* Handle route-based tab management - called by shared service
|
|
2340
|
-
* This method checks for existing tabs and creates/activates as needed
|
|
2341
|
-
*/
|
|
2342
|
-
handleRouteBasedTabManagement(currentRoutePath, queryParams, layout, pageData, pageCode) {
|
|
2343
|
-
console.log('handleRouteBasedTabManagement', currentRoutePath, queryParams, layout, pageData, pageCode);
|
|
2344
|
-
const currentTabs = this.tabsSignal();
|
|
2345
|
-
const tabAlreadyExists = currentTabs.find((t) => {
|
|
2346
|
-
const tRoutePath = t.route.startsWith('/') ? t.route : '/' + t.route;
|
|
2347
|
-
const tParamsMatch = JSON.stringify(t.params || {}) === JSON.stringify(queryParams || {});
|
|
2348
|
-
return tRoutePath === currentRoutePath && tParamsMatch;
|
|
2349
|
-
});
|
|
2350
|
-
console.log('tabAlreadyExists', tabAlreadyExists);
|
|
2351
|
-
if (!tabAlreadyExists) {
|
|
2352
|
-
const title = pageData.data?.page?.sypg_title || pageCode.toString() || 'Untitled Tab';
|
|
2353
|
-
console.log(`🆕 Adding new tab: ${title}`);
|
|
2354
|
-
this.addTab(title, currentRoutePath, queryParams, layout);
|
|
2355
|
-
}
|
|
2356
|
-
else if (tabAlreadyExists && !tabAlreadyExists.active) {
|
|
2357
|
-
console.log(`🔄 Activating existing tab: ${tabAlreadyExists.title}`);
|
|
2358
|
-
this.activateTab(tabAlreadyExists.id);
|
|
2359
|
-
}
|
|
2360
|
-
else if (tabAlreadyExists && tabAlreadyExists.active) {
|
|
2361
|
-
console.log(`🔄 Activating existing tab: ${tabAlreadyExists.title}`);
|
|
2362
|
-
this.activateTab(tabAlreadyExists.id);
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
addTab(title, route, params, layout) {
|
|
2366
|
-
console.log('addTab', title, route, params, layout);
|
|
2367
|
-
const id = this.generateId();
|
|
2368
|
-
const currentTabs = this.tabsSignal();
|
|
2369
|
-
console.log('currentTabs', currentTabs, route);
|
|
2370
|
-
// Check if a tab with the same route and params already exists
|
|
2371
|
-
const existingTab = currentTabs.find((tab) => {
|
|
2372
|
-
const paramsMatch = JSON.stringify(tab.params || {}) === JSON.stringify(params || {});
|
|
2373
|
-
return tab.route === route && paramsMatch;
|
|
2374
|
-
});
|
|
2375
|
-
console.log('existingTab', existingTab);
|
|
2376
|
-
if (existingTab) {
|
|
2377
|
-
this.activateTab(existingTab.id);
|
|
2378
|
-
return existingTab.id;
|
|
2379
|
-
}
|
|
2380
|
-
// Create new tab
|
|
2381
|
-
const newTab = {
|
|
2382
|
-
id,
|
|
2383
|
-
title,
|
|
2384
|
-
route,
|
|
2385
|
-
params,
|
|
2386
|
-
active: true,
|
|
2387
|
-
layout,
|
|
2388
|
-
sytm_page_id_sypg: '',
|
|
2389
|
-
themeId: ''
|
|
2390
|
-
};
|
|
2391
|
-
// Deactivate all other tabs
|
|
2392
|
-
currentTabs.forEach((tab) => tab.active = false);
|
|
2393
|
-
console.log('currentTabs after deactivate', currentTabs, 'activateTab', id);
|
|
2394
|
-
// Add new tab
|
|
2395
|
-
this.tabsSignal.set([...currentTabs, newTab]);
|
|
2396
|
-
this.activeTabIdSignal.set(id);
|
|
2397
|
-
// Sync with TabStateService
|
|
2398
|
-
this.tabStateService.addTab(title, id);
|
|
2399
|
-
// Request visibility is now controlled by layout configuration in setPageData
|
|
2400
|
-
// No need to automatically show request wrapper here
|
|
2401
|
-
// Navigate to the new route
|
|
2402
|
-
if (params) {
|
|
2403
|
-
this.router.navigate([route], { queryParams: params });
|
|
2404
|
-
}
|
|
2405
|
-
else {
|
|
2406
|
-
this.router.navigate([route]);
|
|
2407
|
-
}
|
|
2408
|
-
return id;
|
|
2409
|
-
}
|
|
2410
|
-
activateTab(tabId) {
|
|
2411
|
-
console.log('🔍 REQUEST SERVICE: Activating tab:', tabId);
|
|
2412
|
-
const tabs = this.tabsSignal();
|
|
2413
|
-
const tabToActivate = tabs.find(tab => tab.id === tabId);
|
|
2414
|
-
if (!tabToActivate) {
|
|
2415
|
-
console.warn('⚠️ Tab not found:', tabId);
|
|
2416
|
-
return;
|
|
2417
|
-
}
|
|
2418
|
-
// Before navigating, ensure the route is fresh by clearing any cached state
|
|
2419
|
-
// This prevents old component data from persisting when reopening a tab
|
|
2420
|
-
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
2421
|
-
let pathKey = tabToActivate.route.startsWith('/') ? tabToActivate.route.substring(1) : tabToActivate.route;
|
|
2422
|
-
if (tabToActivate.params && Object.keys(tabToActivate.params).length > 0) {
|
|
2423
|
-
const queryParamsString = Object.keys(tabToActivate.params)
|
|
2424
|
-
.sort()
|
|
2425
|
-
.map(key => `${key}=${tabToActivate.params[key]}`)
|
|
2426
|
-
.join('&');
|
|
2427
|
-
pathKey += '?' + queryParamsString;
|
|
2428
|
-
}
|
|
2429
|
-
// Clear any cached route to ensure fresh component state
|
|
2430
|
-
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
2431
|
-
console.log(`🔄 REQUEST SERVICE: Cleared cached route ${pathKey} before activating tab`);
|
|
2432
|
-
}
|
|
2433
|
-
// Update tabs: deactivate all, then activate the target
|
|
2434
|
-
const updatedTabs = tabs.map(tab => ({
|
|
2435
|
-
...tab,
|
|
2436
|
-
active: tab.id === tabId
|
|
2437
|
-
}));
|
|
2438
|
-
this.tabsSignal.set(updatedTabs);
|
|
2439
|
-
this.activeTabIdSignal.set(tabId);
|
|
2440
|
-
console.log('✅ REQUEST SERVICE: Tab activated:', tabToActivate.title);
|
|
2441
|
-
// Navigate to the tab's route
|
|
2442
|
-
this.router.navigate([tabToActivate.route], {
|
|
2443
|
-
queryParams: tabToActivate.params || {},
|
|
2444
|
-
replaceUrl: false
|
|
2445
|
-
});
|
|
2446
|
-
// Trigger sidebar sync for the activated tab by calling shared service
|
|
2447
|
-
// The shared service will handle the sidebar sync when the route loads
|
|
2448
|
-
console.log('🔗 REQUEST SERVICE: Sidebar sync will be handled by route change');
|
|
2449
|
-
}
|
|
2450
|
-
closeTab(id) {
|
|
2451
|
-
const currentTabs = this.tabsSignal();
|
|
2452
|
-
const tabIndex = currentTabs.findIndex((tab) => tab.id === id);
|
|
2453
|
-
if (tabIndex === -1)
|
|
2454
|
-
return;
|
|
2455
|
-
const tabToClose = currentTabs[tabIndex];
|
|
2456
|
-
const wasActive = tabToClose.active;
|
|
2457
|
-
// Sync with TabStateService by removing the tab state
|
|
2458
|
-
this.tabStateService.removeTab(id);
|
|
2459
|
-
// Clear stored route from strategy and ensure proper component cleanup
|
|
2460
|
-
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
2461
|
-
let pathKey = tabToClose.route.startsWith('/') ? tabToClose.route.substring(1) : tabToClose.route;
|
|
2462
|
-
if (tabToClose.params && Object.keys(tabToClose.params).length > 0) {
|
|
2463
|
-
const queryParamsString = Object.keys(tabToClose.params)
|
|
2464
|
-
.sort()
|
|
2465
|
-
.map(key => `${key}=${tabToClose.params[key]}`)
|
|
2466
|
-
.join('&');
|
|
2467
|
-
pathKey += '?' + queryParamsString;
|
|
2468
|
-
}
|
|
2469
|
-
// Clear the stored route and destroy the component instance
|
|
2470
|
-
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
2471
|
-
// Force a route refresh to ensure clean component state
|
|
2472
|
-
// This prevents old data from persisting when the same route is reopened
|
|
2473
|
-
console.log(`🧹 REQUEST SERVICE: Cleared stored route for ${pathKey} and destroyed component instance`);
|
|
2474
|
-
}
|
|
2475
|
-
// Remove the tab from this service's list
|
|
2476
|
-
const newTabs = currentTabs.filter((tab) => tab.id !== id);
|
|
2477
|
-
// If we're closing the active tab, activate the next available tab
|
|
2478
|
-
if (wasActive && newTabs.length > 0) {
|
|
2479
|
-
const newActiveIndex = Math.max(0, Math.min(tabIndex, newTabs.length - 1));
|
|
2480
|
-
const newActiveTab = newTabs[newActiveIndex];
|
|
2481
|
-
// Update sidedrawer for the new active tab
|
|
2482
|
-
this.sidedrawerService.updateDrawerItems(newActiveTab.layout);
|
|
2483
|
-
newTabs[newActiveIndex].active = true;
|
|
2484
|
-
this.activeTabIdSignal.set(newActiveTab.id);
|
|
2485
|
-
this.tabStateService.setActiveTab(newActiveTab.id);
|
|
2486
|
-
// Navigate to the new active tab's route
|
|
2487
|
-
if (newActiveTab.params) {
|
|
2488
|
-
this.router.navigate([newActiveTab.route], { queryParams: newActiveTab.params });
|
|
2489
|
-
}
|
|
2490
|
-
else {
|
|
2491
|
-
this.router.navigate([newActiveTab.route]);
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
else if (newTabs.length === 0) {
|
|
2495
|
-
this.activeTabIdSignal.set(null);
|
|
2496
|
-
this.tabStateService.setActiveTab(null);
|
|
2497
|
-
// Clear sidedrawer as no tabs are open
|
|
2498
|
-
this.sidedrawerService.updateDrawerItems(undefined);
|
|
2499
|
-
}
|
|
2500
|
-
this.tabsSignal.set(newTabs);
|
|
2501
|
-
// Request wrapper visibility is now controlled by layout configuration
|
|
2502
|
-
// The wrapper will be hidden/shown based on sytm_layout_request.status in setPageData
|
|
2503
|
-
}
|
|
2504
|
-
// Hide Request
|
|
2505
|
-
hideRequest() {
|
|
2506
|
-
console.log('🚫 REQUEST SERVICE - Hiding request wrapper');
|
|
2507
|
-
this.requestVisible = false;
|
|
2508
|
-
document.querySelector(`#cide-lyt-request-wrapper`)?.classList.add('cide-lyt-request-wrapper-hide');
|
|
2509
|
-
document.querySelector(`body`)?.classList.remove('cide-lyt-request-exist');
|
|
2510
|
-
}
|
|
2511
|
-
// Show Request
|
|
2512
|
-
showRequest() {
|
|
2513
|
-
console.log('✅ REQUEST SERVICE - Showing request wrapper');
|
|
2514
|
-
this.requestVisible = true;
|
|
2515
|
-
document.querySelector(`#cide-lyt-request-wrapper`)?.classList.remove('cide-lyt-request-wrapper-hide');
|
|
2516
|
-
document.querySelector(`body`)?.classList.add('cide-lyt-request-exist');
|
|
2517
|
-
}
|
|
2518
|
-
generateId() {
|
|
2519
|
-
return 'tab-' + Math.random().toString(36).substr(2, 9);
|
|
2520
|
-
}
|
|
2521
|
-
updateTabScrollPosition(tabId, scrollTop, scrollLeft) {
|
|
2522
|
-
const currentTabs = this.tabsSignal();
|
|
2523
|
-
const tabIndex = currentTabs.findIndex((t) => t.id === tabId);
|
|
2524
|
-
if (tabIndex !== -1) {
|
|
2525
|
-
const tabToUpdate = currentTabs[tabIndex];
|
|
2526
|
-
tabToUpdate.scrollTop = scrollTop;
|
|
2527
|
-
tabToUpdate.scrollLeft = scrollLeft;
|
|
2528
|
-
// Create a new array reference to trigger change detection
|
|
2529
|
-
this.tabsSignal.set([...currentTabs]);
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
/**
|
|
2533
|
-
* Force refresh a route by clearing its cached state
|
|
2534
|
-
* This is useful when you want to ensure a clean component state
|
|
2535
|
-
* @param route The route path to refresh
|
|
2536
|
-
* @param params Optional query parameters
|
|
2537
|
-
*/
|
|
2538
|
-
forceRefreshRoute(route, params) {
|
|
2539
|
-
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
2540
|
-
let pathKey = route.startsWith('/') ? route.substring(1) : route;
|
|
2541
|
-
if (params && Object.keys(params).length > 0) {
|
|
2542
|
-
const queryParamsString = Object.keys(params)
|
|
2543
|
-
.sort()
|
|
2544
|
-
.map(key => `${key}=${params[key]}`)
|
|
2545
|
-
.join('&');
|
|
2546
|
-
pathKey += '?' + queryParamsString;
|
|
2547
|
-
}
|
|
2548
|
-
// Clear the stored route to force a fresh component instance
|
|
2549
|
-
this.routeReuseStrategy.clearStoredRoute(pathKey);
|
|
2550
|
-
console.log(`🔄 REQUEST SERVICE: Force refreshed route ${pathKey}`);
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
/**
|
|
2554
|
-
* Get information about cached routes (useful for debugging)
|
|
2555
|
-
*/
|
|
2556
|
-
getCachedRoutesInfo() {
|
|
2557
|
-
if (this.routeReuseStrategy instanceof CustomRouteReuseStrategy) {
|
|
2558
|
-
const count = this.routeReuseStrategy.getStoredRoutesCount();
|
|
2559
|
-
return { count, routes: [] }; // Could be enhanced to return actual route paths
|
|
2560
|
-
}
|
|
2561
|
-
return { count: 0, routes: [] };
|
|
2562
|
-
}
|
|
2563
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2564
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestService, providedIn: 'root' });
|
|
2565
|
-
}
|
|
2566
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestService, decorators: [{
|
|
2567
|
-
type: Injectable,
|
|
2568
|
-
args: [{
|
|
2569
|
-
providedIn: 'root'
|
|
2570
|
-
}]
|
|
2571
|
-
}], ctorParameters: () => [] });
|
|
2572
|
-
|
|
2573
|
-
// Type guard for setState/getState
|
|
2574
|
-
function hasStateMethods(instance) {
|
|
2575
|
-
return !!instance && typeof instance.getState === 'function' && typeof instance.setState === 'function';
|
|
2576
|
-
}
|
|
2577
|
-
// Type guard for NotesComponentState
|
|
2578
|
-
function isNotesComponentState(state) {
|
|
2579
|
-
return !!state && typeof state === 'object' &&
|
|
2580
|
-
'newNoteModel' in state &&
|
|
2581
|
-
'isFormVisible' in state &&
|
|
2582
|
-
'scrollTop' in state;
|
|
2583
|
-
}
|
|
2584
|
-
class CideLytSidedrawerWrapperComponent {
|
|
2585
|
-
sidedrawerSetupData = {
|
|
2586
|
-
cide_lyt_sidedrawer_width: 0
|
|
2587
|
-
};
|
|
2588
|
-
// Modern Angular v20 pattern: Use Signals directly
|
|
2589
|
-
get drawerItems() { return this.sidedrawerService.drawerItems; }
|
|
2590
|
-
get notesVisible() { return this.sidedrawerService.notesVisible; }
|
|
2591
|
-
isExpanded = false; // New property to track expansion state
|
|
2592
|
-
// Computed signal to determine if sidedrawer should be visible
|
|
2593
|
-
get shouldShowSidedrawer() {
|
|
2594
|
-
return computed(() => {
|
|
2595
|
-
const items = this.drawerItems();
|
|
2596
|
-
return items && items.length > 0;
|
|
2597
|
-
});
|
|
2598
|
-
}
|
|
2599
|
-
notesContainer;
|
|
2600
|
-
currentComponentRef = null;
|
|
2601
|
-
componentMap = {}; // Map configFor to component
|
|
2602
|
-
currentTabId = null;
|
|
2603
|
-
currentActiveComponent = null;
|
|
2604
|
-
sidedrawerService = inject(CideLytSidedrawerService);
|
|
2605
|
-
requestService = inject(CideLytRequestService);
|
|
2606
|
-
tabStateService = inject(TabStateService);
|
|
2607
|
-
constructor() {
|
|
2608
|
-
// Modern Angular v20 pattern: Use Signals directly with effect
|
|
2609
|
-
effect(() => {
|
|
2610
|
-
const configFor = this.sidedrawerService.activeComponent();
|
|
2611
|
-
this.currentActiveComponent = configFor;
|
|
2612
|
-
this.loadComponent(configFor);
|
|
2613
|
-
}, { allowSignalWrites: true });
|
|
2614
|
-
// Effect to track active tab changes
|
|
2615
|
-
effect(() => {
|
|
2616
|
-
const activeTabId = this.requestService.activeTabId();
|
|
2617
|
-
console.log('🔍 SIDEDRAWER - Active tab changed:', activeTabId);
|
|
2618
|
-
this.currentTabId = activeTabId;
|
|
2619
|
-
if (activeTabId) {
|
|
2620
|
-
console.log('🔍 SIDEDRAWER - Loading tab state for:', activeTabId);
|
|
2621
|
-
this.tabStateService.getTabState(activeTabId).pipe(take$1(1)).subscribe(tabState => {
|
|
2622
|
-
console.log('🔍 SIDEDRAWER - Tab state loaded:', tabState);
|
|
2623
|
-
const sideDrawer = tabState?.componentState.sideDrawer;
|
|
2624
|
-
if (sideDrawer) {
|
|
2625
|
-
console.log('🔍 SIDEDRAWER - Restoring drawer state:', sideDrawer);
|
|
2626
|
-
// Restore activeComponent
|
|
2627
|
-
this.sidedrawerService.setActiveComponent(sideDrawer.activeComponent);
|
|
2628
|
-
// Modern ES2022+ pattern: Use Promise-based delay with microtask
|
|
2629
|
-
Promise.resolve().then(() => {
|
|
2630
|
-
if (sideDrawer.activeComponent && this.currentComponentRef && hasStateMethods(this.currentComponentRef.instance)) {
|
|
2631
|
-
const compState = sideDrawer.componentState?.[sideDrawer.activeComponent];
|
|
2632
|
-
// Add type guards for each component as needed
|
|
2633
|
-
if (sideDrawer.activeComponent === 'drowar_notes' && isNotesComponentState(compState)) {
|
|
2634
|
-
this.currentComponentRef.instance.setState(compState);
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
});
|
|
2638
|
-
}
|
|
2639
|
-
else {
|
|
2640
|
-
console.log('🔍 SIDEDRAWER - No drawer state found, clearing active component');
|
|
2641
|
-
this.sidedrawerService.setActiveComponent(null);
|
|
2642
|
-
}
|
|
2643
|
-
});
|
|
2644
|
-
}
|
|
2645
|
-
else {
|
|
2646
|
-
console.log('🔍 SIDEDRAWER - No active tab, clearing drawer state');
|
|
2647
|
-
this.sidedrawerService.setActiveComponent(null);
|
|
2648
|
-
}
|
|
2649
|
-
}, { allowSignalWrites: true });
|
|
2650
|
-
// collecte the width of the sidebar by using the style property
|
|
2651
|
-
const cide_lyt_sidedrawer_wrapper_width = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--cide-lyt-sidedrawer-wrapper-width'));
|
|
2652
|
-
this.sidedrawerSetupData.cide_lyt_sidedrawer_width = (cide_lyt_sidedrawer_wrapper_width);
|
|
2653
|
-
}
|
|
2654
|
-
ngOnInit() {
|
|
2655
|
-
// Initialize the component map (You'd likely populate this from a config or service)
|
|
2656
|
-
this.componentMap['drowar_notes'] = () => import('./cloud-ide-layout-sidedrawer-notes.component-OKwCnhEf.mjs').then(m => m.CideLytSidedrawerNotesComponent);
|
|
2657
|
-
this.componentMap['drawer_theme'] = () => import('./cloud-ide-layout-drawer-theme.component-gmPE1a4W.mjs').then(m => m.CideLytDrawerThemeComponent);
|
|
2658
|
-
}
|
|
2659
|
-
async loadComponent(configFor) {
|
|
2660
|
-
console.log('🔍 SIDEDRAWER - Loading component:', configFor, 'Current tab:', this.currentTabId);
|
|
2661
|
-
// Always clear the container and destroy the previous component
|
|
2662
|
-
this.notesContainer?.clear();
|
|
2663
|
-
if (this.currentComponentRef) {
|
|
2664
|
-
this.currentComponentRef.destroy();
|
|
2665
|
-
this.currentComponentRef = null;
|
|
2666
|
-
}
|
|
2667
|
-
if (!configFor) {
|
|
2668
|
-
console.log('🔍 SIDEDRAWER - No component to load, collapsing');
|
|
2669
|
-
this.isExpanded = false;
|
|
2670
|
-
this.saveSidedrawerState(null);
|
|
2671
|
-
return;
|
|
2672
|
-
}
|
|
2673
|
-
console.log('🔍 SIDEDRAWER - Expanding and loading:', configFor);
|
|
2674
|
-
this.isExpanded = true;
|
|
2675
|
-
const componentLoader = this.componentMap[configFor];
|
|
2676
|
-
console.log('🔍 SIDEDRAWER - Component loader found:', !!componentLoader);
|
|
2677
|
-
if (componentLoader) {
|
|
2678
|
-
try {
|
|
2679
|
-
console.log('🔍 SIDEDRAWER - Importing component for:', configFor);
|
|
2680
|
-
const componentType = await componentLoader();
|
|
2681
|
-
console.log('🔍 SIDEDRAWER - Component imported successfully');
|
|
2682
|
-
this.currentComponentRef = this.notesContainer.createComponent(componentType);
|
|
2683
|
-
console.log('🔍 SIDEDRAWER - Component created successfully');
|
|
2684
|
-
// Add the cide-lyt-sidedrawer-wrapper-child class to the created component
|
|
2685
|
-
if (this.currentComponentRef.location.nativeElement) {
|
|
2686
|
-
this.currentComponentRef.location.nativeElement.classList.add('cide-lyt-sidedrawer-wrapper-child');
|
|
2687
|
-
}
|
|
2688
|
-
this.setComponentContext(configFor);
|
|
2689
|
-
// Optionally restore state here if you have it
|
|
2690
|
-
this.saveSidedrawerState(configFor);
|
|
2691
|
-
console.log('🔍 SIDEDRAWER - Component setup completed');
|
|
2692
|
-
}
|
|
2693
|
-
catch (error) {
|
|
2694
|
-
console.error(`🚨 SIDEDRAWER - Error loading component for ${configFor}:`, error);
|
|
2695
|
-
this.isExpanded = false;
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
else {
|
|
2699
|
-
console.warn(`⚠️ SIDEDRAWER - No component mapping found for configFor: ${configFor}`);
|
|
2700
|
-
console.log('🔍 SIDEDRAWER - Available mappings:', Object.keys(this.componentMap));
|
|
2701
|
-
this.isExpanded = false;
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
clearComponent(destroy = true) {
|
|
2705
|
-
this.saveSidedrawerState();
|
|
2706
|
-
this.notesContainer?.clear();
|
|
2707
|
-
if (this.currentComponentRef && destroy) {
|
|
2708
|
-
this.currentComponentRef.destroy();
|
|
2709
|
-
}
|
|
2710
|
-
this.currentComponentRef = null;
|
|
2711
|
-
}
|
|
2712
|
-
saveSidedrawerState(configForOverride) {
|
|
2713
|
-
console.log('🔍 SIDEDRAWER - Saving state, current tab:', this.currentTabId);
|
|
2714
|
-
if (!this.currentTabId) {
|
|
2715
|
-
console.log('⚠️ SIDEDRAWER - No current tab, skipping state save');
|
|
2716
|
-
return;
|
|
2717
|
-
}
|
|
2718
|
-
const activeComponent = configForOverride !== undefined ? configForOverride : this.currentActiveComponent;
|
|
2719
|
-
console.log('🔍 SIDEDRAWER - Active component for save:', activeComponent);
|
|
2720
|
-
let componentState = undefined;
|
|
2721
|
-
if (this.currentComponentRef && hasStateMethods(this.currentComponentRef.instance) && activeComponent) {
|
|
2722
|
-
const state = this.currentComponentRef.instance.getState();
|
|
2723
|
-
// Add type guards for each component as needed
|
|
2724
|
-
if (activeComponent === 'drowar_notes' && isNotesComponentState(state)) {
|
|
2725
|
-
componentState = { [activeComponent]: state };
|
|
2726
|
-
}
|
|
2727
|
-
// Example for other components:
|
|
2728
|
-
// else if (activeComponent === 'other_component' && isOtherComponentState(state)) {
|
|
2729
|
-
// componentState = { [activeComponent]: state };
|
|
2730
|
-
// }
|
|
2731
|
-
}
|
|
2732
|
-
console.log('🔍 SIDEDRAWER - Updating tab state with:', {
|
|
2733
|
-
isOpen: !!activeComponent,
|
|
2734
|
-
activeComponent,
|
|
2735
|
-
componentState
|
|
2736
|
-
});
|
|
2737
|
-
this.tabStateService.updateActiveTabState({
|
|
2738
|
-
sideDrawer: {
|
|
2739
|
-
isOpen: !!activeComponent,
|
|
2740
|
-
activeComponent,
|
|
2741
|
-
componentState
|
|
2742
|
-
}
|
|
2743
|
-
});
|
|
2744
|
-
}
|
|
2745
|
-
setComponentContext(configFor) {
|
|
2746
|
-
// You can adjust this logic to pass different context data based on configFor if needed.
|
|
2747
|
-
const context = this.getNotesContext(configFor); // Using your existing method for now.
|
|
2748
|
-
if (this.currentComponentRef && context && this.currentComponentRef.setInput) {
|
|
2749
|
-
// Pass context data
|
|
2750
|
-
this.currentComponentRef.setInput('context', context);
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
// This function needs to be enhanced to handle contexts for different components
|
|
2754
|
-
getNotesContext(configFor) {
|
|
2755
|
-
switch (configFor) {
|
|
2756
|
-
case 'drowar_notes': {
|
|
2757
|
-
// Example for notes component context
|
|
2758
|
-
const activeTab = this.requestService.activeTab(); // Use the computed signal for active tab
|
|
2759
|
-
return {
|
|
2760
|
-
pageId: activeTab?.sytm_page_id_sypg,
|
|
2761
|
-
themeId: activeTab?.themeId,
|
|
2762
|
-
// Add other properties as needed for notes
|
|
2763
|
-
};
|
|
2764
|
-
}
|
|
2765
|
-
case 'drawer_theme': {
|
|
2766
|
-
// You can add theme context here if needed
|
|
2767
|
-
return {};
|
|
2768
|
-
}
|
|
2769
|
-
default:
|
|
2770
|
-
return null; // Or a default context if appropriate
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
onItemClick(item) {
|
|
2774
|
-
const activeTabId = this.requestService.activeTabId();
|
|
2775
|
-
console.log('🔍 SIDEDRAWER - Item clicked:', item);
|
|
2776
|
-
console.log('🔍 SIDEDRAWER - Active tab ID:', activeTabId);
|
|
2777
|
-
// Modern Angular v20 pattern: Use Signals directly
|
|
2778
|
-
const activeComponent = this.sidedrawerService.activeComponent();
|
|
2779
|
-
console.log('🔍 SIDEDRAWER - Current active component:', activeComponent);
|
|
2780
|
-
console.log('🔍 SIDEDRAWER - Item configFor:', item.configFor);
|
|
2781
|
-
if (activeComponent === item?.configFor) {
|
|
2782
|
-
console.log('🔄 SIDEDRAWER - Same component clicked, toggling off');
|
|
2783
|
-
const cide_lyt_sidedrawer_wrapper = document.querySelector('#cide-lyt-sidedrawer-wrapper');
|
|
2784
|
-
const cide_lyt_outlet_wrapper = document.querySelector('#cide-lyt-outlet-wrapper');
|
|
2785
|
-
if (cide_lyt_sidedrawer_wrapper instanceof HTMLElement && cide_lyt_outlet_wrapper instanceof HTMLElement) {
|
|
2786
|
-
const width_of_prev = parseInt(cide_lyt_outlet_wrapper.style.width);
|
|
2787
|
-
const width_of_next = parseInt(cide_lyt_sidedrawer_wrapper.style.width);
|
|
2788
|
-
const width_of_next_default = 32;
|
|
2789
|
-
const width_of_prev_default = width_of_prev + (width_of_next - width_of_next_default);
|
|
2790
|
-
cide_lyt_outlet_wrapper.style.width = `${width_of_prev_default}px`;
|
|
2791
|
-
cide_lyt_sidedrawer_wrapper.style.width = `${width_of_next_default}px`; // Fix: Cast to HTMLElement to access style property
|
|
2792
|
-
}
|
|
2793
|
-
this.sidedrawerService.setActiveComponent(null); // Destroy if same
|
|
2794
|
-
this.saveSidedrawerState(null);
|
|
2795
|
-
}
|
|
2796
|
-
else {
|
|
2797
|
-
console.log('✅ SIDEDRAWER - New component clicked, setting active:', item.configFor);
|
|
2798
|
-
this.sidedrawerService.setActiveComponent(item.configFor); // Set new
|
|
2799
|
-
this.saveSidedrawerState(item.configFor);
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidedrawerWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2803
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideLytSidedrawerWrapperComponent, isStandalone: true, selector: "cide-lyt-sidedrawer-wrapper", viewQueries: [{ propertyName: "notesContainer", first: true, predicate: ["notesContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<nav *ngIf=\"shouldShowSidedrawer()\" \r\n [ngClass]=\"{'expanded tw-min-w-[var(--cide-lyt-sidedrawer-wrapper-width-expanded-min)]': isExpanded, 'tw-w-[var(--cide-lyt-sidedrawer-wrapper-width)]': !isExpanded}\"\r\n id=\"cide-lyt-sidedrawer-wrapper\" class=\"tw-flex tw-relative tw-flex-col tw-items-center tw-justify-between tw-h-full tw-bg-white tw-shadow-lg tw-border-l tw-border-gray-200\">\r\n <!-- Resizer Divider -->\r\n <div parentElementSelector=\"#cide-lyt-outlet-sidedrawer-wrapper\"\r\n [minNextSize]=\"sidedrawerSetupData.cide_lyt_sidedrawer_width\" prevElementSelector=\"#cide-lyt-outlet-wrapper\"\r\n nextElementSelector=\"#cide-lyt-sidedrawer-wrapper\" cideEleResizer direction=\"horizontal\" to=\"right-to-left\">\r\n <div class=\"cide-lyt-devider-track tw-w-full tw-h-full\"></div>\r\n </div>\r\n <!-- Main Icon Area -->\r\n <div class=\"tw-flex tw-justify-between tw-h-full tw-w-full\">\r\n <div class=\"tw-w-8 tw-h-full tw-border-r tw-border-gray-200\">\r\n <div class=\"sidedrawer-icon tw-flex tw-flex-col tw-items-center tw-gap-2\">\r\n <ng-container *ngIf=\"drawerItems() as items; else loading\">\r\n <div *ngFor=\"let item of items\" [title]=\"item.title\">\r\n <button (click)=\"onItemClick(item)\"\r\n class=\"tw-p-0 tw-rounded-md hover:tw-bg-gray-100 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-blue-500\"\r\n [ngClass]=\"{'tw-bg-blue-100 tw-text-blue-700': sidedrawerService.activeComponent() === item.configFor}\">\r\n <cide-ele-icon [cideEleTooltip]=\"item.title\" tooltipPlacement=\"left\" width=\"1.3rem\" height=\"1.5rem\" type=\"box\">{{item.icon}}</cide-ele-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n <ng-template #loading>\r\n <cide-ele-skeleton-loader [width]=\"'2rem'\" [height]=\"'2rem'\" [count]=\"3\"\r\n [circle]=\"true\"></cide-ele-skeleton-loader>\r\n </ng-template>\r\n </div>\r\n </div>\r\n <div class=\"tw-flex-1 cide-lyt-sidedrawer-wrapper-child-holder\">\r\n <div #notesContainer class=\"tw-h-full\">\r\n </div>\r\n </div>\r\n </div>\r\n</nav>", styles: [".cide-lyt-sidebar{display:flex;box-shadow:0 4px 12px var(--sidebar-shadow-color);overflow:hidden;background-color:var(--cide-theme-sidebar-color);--sidebar-tooltip-bg: var(--cide-theme-dark-color);--sidebar-tooltip-color: var(--cide-theme-light-color);--sidebar-shadow-color: var(--cide-theme-shadow-color, rgba(0, 0, 0, .05));transition:width .3s cubic-bezier(.4,0,.2,1);max-height:100%;isolation:isolate;will-change:width;position:relative}cide-ele-icon{color:var(--cide-theme-icon-color, var(--tw-gray-500));transition:all .2s ease}cide-ele-icon:hover{color:var(--cide-theme-icon-hover-color, var(--tw-gray-700));transform:scale(1.1)}.dark-mode .cide-lyt-sidebar{background-color:var(--cide-theme-dark-color)}.dark-mode .cide-lyt-devider-track{background-color:color-mix(in srgb,var(--cide-theme-dark-color) 70%,var(--cide-theme-black, #000));border-left-color:var(--cide-theme-dark-color)}.dark-mode .cide-lyt-devider-track:hover{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 20%,var(--cide-theme-dark-color))}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}@media (prefers-reduced-motion: reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}.tw-text-gray-500{color:var(--cide-theme-icon-color)}.hover\\:tw-text-gray-700:hover{color:var(--cide-theme-icon-hover-color)}.tw-text-gray-700{color:var(--cide-theme-text-color)}.tw-text-red-500,.tw-text-red-600,.tw-text-red-700{color:var(--cide-theme-error-color)}.tw-bg-gray-200{background-color:var(--cide-theme-border-color)}.hover\\:tw-bg-gray-100:hover{background-color:var(--cide-theme-hover-bg-color)}.tw-bg-white{background-color:var(--cide-theme-sidebar-color)}.tw-bg-green-500{background-color:var(--cide-theme-success-color)}.tw-border-gray-100,.tw-divide-gray-100{border-color:var(--cide-theme-border-color)}.tw-shadow-lg{box-shadow:0 10px 15px -3px var(--cide-theme-shadow-color),0 4px 6px -2px var(--cide-theme-shadow-color)}.tw-shadow-sm,.hover\\:tw-shadow:hover{box-shadow:0 1px 2px 0 var(--cide-theme-shadow-color)}.sidedrawer-icon{margin:.25rem 0}.cide-lyt-sidedrawer-wrapper-child-holder{display:grid;height:100%;width:100%;overflow:auto}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "directive", type: CideEleResizerDirective, selector: "[cideEleResizer]", inputs: ["direction", "to", "prevElementSelector", "nextElementSelector", "parentElementSelector", "minPrevSize", "minNextSize", "usePercentage"], outputs: ["resizeStart", "resizing", "resizeEnd"] }, { kind: "component", type: CideEleSkeletonLoaderComponent, selector: "cide-ele-skeleton-loader", inputs: ["width", "height", "borderRadius", "count", "circle", "animation"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: TooltipDirective, selector: "[cideEleTooltip]", inputs: ["cideEleTooltip", "tooltipColor", "tooltipBg", "tooltipPlacement", "tooltipType", "tooltipDelay", "tooltipDir", "tooltipShowArrow", "tooltipMultiline", "tooltipMaxWidth", "tooltipInteractive", "tooltipClass"] }] });
|
|
2804
|
-
}
|
|
2805
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSidedrawerWrapperComponent, decorators: [{
|
|
2806
|
-
type: Component,
|
|
2807
|
-
args: [{ selector: 'cide-lyt-sidedrawer-wrapper', standalone: true, imports: [
|
|
2808
|
-
NgClass,
|
|
2809
|
-
CideIconComponent,
|
|
2810
|
-
CideEleResizerDirective,
|
|
2811
|
-
CideEleSkeletonLoaderComponent,
|
|
2812
|
-
NgFor,
|
|
2813
|
-
NgIf,
|
|
2814
|
-
TooltipDirective
|
|
2815
|
-
], template: "<nav *ngIf=\"shouldShowSidedrawer()\" \r\n [ngClass]=\"{'expanded tw-min-w-[var(--cide-lyt-sidedrawer-wrapper-width-expanded-min)]': isExpanded, 'tw-w-[var(--cide-lyt-sidedrawer-wrapper-width)]': !isExpanded}\"\r\n id=\"cide-lyt-sidedrawer-wrapper\" class=\"tw-flex tw-relative tw-flex-col tw-items-center tw-justify-between tw-h-full tw-bg-white tw-shadow-lg tw-border-l tw-border-gray-200\">\r\n <!-- Resizer Divider -->\r\n <div parentElementSelector=\"#cide-lyt-outlet-sidedrawer-wrapper\"\r\n [minNextSize]=\"sidedrawerSetupData.cide_lyt_sidedrawer_width\" prevElementSelector=\"#cide-lyt-outlet-wrapper\"\r\n nextElementSelector=\"#cide-lyt-sidedrawer-wrapper\" cideEleResizer direction=\"horizontal\" to=\"right-to-left\">\r\n <div class=\"cide-lyt-devider-track tw-w-full tw-h-full\"></div>\r\n </div>\r\n <!-- Main Icon Area -->\r\n <div class=\"tw-flex tw-justify-between tw-h-full tw-w-full\">\r\n <div class=\"tw-w-8 tw-h-full tw-border-r tw-border-gray-200\">\r\n <div class=\"sidedrawer-icon tw-flex tw-flex-col tw-items-center tw-gap-2\">\r\n <ng-container *ngIf=\"drawerItems() as items; else loading\">\r\n <div *ngFor=\"let item of items\" [title]=\"item.title\">\r\n <button (click)=\"onItemClick(item)\"\r\n class=\"tw-p-0 tw-rounded-md hover:tw-bg-gray-100 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-blue-500\"\r\n [ngClass]=\"{'tw-bg-blue-100 tw-text-blue-700': sidedrawerService.activeComponent() === item.configFor}\">\r\n <cide-ele-icon [cideEleTooltip]=\"item.title\" tooltipPlacement=\"left\" width=\"1.3rem\" height=\"1.5rem\" type=\"box\">{{item.icon}}</cide-ele-icon>\r\n </button>\r\n </div>\r\n </ng-container>\r\n <ng-template #loading>\r\n <cide-ele-skeleton-loader [width]=\"'2rem'\" [height]=\"'2rem'\" [count]=\"3\"\r\n [circle]=\"true\"></cide-ele-skeleton-loader>\r\n </ng-template>\r\n </div>\r\n </div>\r\n <div class=\"tw-flex-1 cide-lyt-sidedrawer-wrapper-child-holder\">\r\n <div #notesContainer class=\"tw-h-full\">\r\n </div>\r\n </div>\r\n </div>\r\n</nav>", styles: [".cide-lyt-sidebar{display:flex;box-shadow:0 4px 12px var(--sidebar-shadow-color);overflow:hidden;background-color:var(--cide-theme-sidebar-color);--sidebar-tooltip-bg: var(--cide-theme-dark-color);--sidebar-tooltip-color: var(--cide-theme-light-color);--sidebar-shadow-color: var(--cide-theme-shadow-color, rgba(0, 0, 0, .05));transition:width .3s cubic-bezier(.4,0,.2,1);max-height:100%;isolation:isolate;will-change:width;position:relative}cide-ele-icon{color:var(--cide-theme-icon-color, var(--tw-gray-500));transition:all .2s ease}cide-ele-icon:hover{color:var(--cide-theme-icon-hover-color, var(--tw-gray-700));transform:scale(1.1)}.dark-mode .cide-lyt-sidebar{background-color:var(--cide-theme-dark-color)}.dark-mode .cide-lyt-devider-track{background-color:color-mix(in srgb,var(--cide-theme-dark-color) 70%,var(--cide-theme-black, #000));border-left-color:var(--cide-theme-dark-color)}.dark-mode .cide-lyt-devider-track:hover{background-color:color-mix(in srgb,var(--cide-theme-color-brand-primary) 20%,var(--cide-theme-dark-color))}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}@media (prefers-reduced-motion: reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}.tw-text-gray-500{color:var(--cide-theme-icon-color)}.hover\\:tw-text-gray-700:hover{color:var(--cide-theme-icon-hover-color)}.tw-text-gray-700{color:var(--cide-theme-text-color)}.tw-text-red-500,.tw-text-red-600,.tw-text-red-700{color:var(--cide-theme-error-color)}.tw-bg-gray-200{background-color:var(--cide-theme-border-color)}.hover\\:tw-bg-gray-100:hover{background-color:var(--cide-theme-hover-bg-color)}.tw-bg-white{background-color:var(--cide-theme-sidebar-color)}.tw-bg-green-500{background-color:var(--cide-theme-success-color)}.tw-border-gray-100,.tw-divide-gray-100{border-color:var(--cide-theme-border-color)}.tw-shadow-lg{box-shadow:0 10px 15px -3px var(--cide-theme-shadow-color),0 4px 6px -2px var(--cide-theme-shadow-color)}.tw-shadow-sm,.hover\\:tw-shadow:hover{box-shadow:0 1px 2px 0 var(--cide-theme-shadow-color)}.sidedrawer-icon{margin:.25rem 0}.cide-lyt-sidedrawer-wrapper-child-holder{display:grid;height:100%;width:100%;overflow:auto}\n"] }]
|
|
2816
|
-
}], ctorParameters: () => [], propDecorators: { notesContainer: [{
|
|
2817
|
-
type: ViewChild,
|
|
2818
|
-
args: ['notesContainer', { read: ViewContainerRef }]
|
|
2819
|
-
}] } });
|
|
2820
|
-
|
|
2821
|
-
class CideLytFooterWrapperComponent {
|
|
2822
|
-
footerSetupData = {
|
|
2823
|
-
cide_lyt_footer_height: 0
|
|
2824
|
-
};
|
|
2825
|
-
constructor() {
|
|
2826
|
-
// collecte the width of the sidebar by using the style property
|
|
2827
|
-
const cide_lyt_sidedrawer_wrapper_width = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--cide-lyt-sidedrawer-wrapper-width'));
|
|
2828
|
-
this.footerSetupData.cide_lyt_footer_height = (cide_lyt_sidedrawer_wrapper_width);
|
|
2829
|
-
}
|
|
2830
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFooterWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2831
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideLytFooterWrapperComponent, isStandalone: true, selector: "cide-lyt-footer-wrapper", ngImport: i0, template: "<div\r\n class=\"cide-footer tw-w-full tw-justify-between tw-flex tw-h-5 tw-select-none tw-px-1\">\r\n <!-- Resizer -->\r\n <div parentElementSelector=\"#cide-lyt-layout-wrapper\" prevElementSelector=\"#cide-lyt-sidebar-page-wrapper\"\r\n nextElementSelector=\"#cide-lyt-footer-console-wrapper\" [minNextSize]=\"footerSetupData.cide_lyt_footer_height\" cideEleResizer direction=\"vertical\">\r\n <div class=\"cide-lyt-devider-track tw-w-full tw-h-full\"></div>\r\n </div>\r\n <div></div>\r\n <div>\r\n <cide-ele-icon size=\"3xs\" class=\"tw-mt-0.5\" type=\"none\">contact_support</cide-ele-icon>\r\n </div>\r\n</div>", styles: [""], dependencies: [{ kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "directive", type: CideEleResizerDirective, selector: "[cideEleResizer]", inputs: ["direction", "to", "prevElementSelector", "nextElementSelector", "parentElementSelector", "minPrevSize", "minNextSize", "usePercentage"], outputs: ["resizeStart", "resizing", "resizeEnd"] }] });
|
|
2832
|
-
}
|
|
2833
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFooterWrapperComponent, decorators: [{
|
|
2834
|
-
type: Component,
|
|
2835
|
-
args: [{ selector: 'cide-lyt-footer-wrapper', standalone: true, imports: [CideIconComponent, CideEleResizerDirective], template: "<div\r\n class=\"cide-footer tw-w-full tw-justify-between tw-flex tw-h-5 tw-select-none tw-px-1\">\r\n <!-- Resizer -->\r\n <div parentElementSelector=\"#cide-lyt-layout-wrapper\" prevElementSelector=\"#cide-lyt-sidebar-page-wrapper\"\r\n nextElementSelector=\"#cide-lyt-footer-console-wrapper\" [minNextSize]=\"footerSetupData.cide_lyt_footer_height\" cideEleResizer direction=\"vertical\">\r\n <div class=\"cide-lyt-devider-track tw-w-full tw-h-full\"></div>\r\n </div>\r\n <div></div>\r\n <div>\r\n <cide-ele-icon size=\"3xs\" class=\"tw-mt-0.5\" type=\"none\">contact_support</cide-ele-icon>\r\n </div>\r\n</div>" }]
|
|
2836
|
-
}], ctorParameters: () => [] });
|
|
2837
|
-
|
|
2838
|
-
class CideLytConsoleWrapperComponent {
|
|
2839
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytConsoleWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2840
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideLytConsoleWrapperComponent, isStandalone: true, selector: "cide-lyt-console-wrapper", ngImport: i0, template: "<div id=\"cide-lyt-console-wrapper\" class=\"cide-lyt-console tw-w-full tw-absolute tw-items-center tw-bg-gray-50 tw-h-0 tw-border-b-gray-200 tw-border-solid tw-border-t tw-select-none tw-z-[100] tw-bottom-0 tw-px-3\">\r\n\r\n</div>\r\n<!-- h-6 -->", styles: [""] });
|
|
2841
|
-
}
|
|
2842
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytConsoleWrapperComponent, decorators: [{
|
|
2843
|
-
type: Component,
|
|
2844
|
-
args: [{ selector: 'cide-lyt-console-wrapper', standalone: true, imports: [], template: "<div id=\"cide-lyt-console-wrapper\" class=\"cide-lyt-console tw-w-full tw-absolute tw-items-center tw-bg-gray-50 tw-h-0 tw-border-b-gray-200 tw-border-solid tw-border-t tw-select-none tw-z-[100] tw-bottom-0 tw-px-3\">\r\n\r\n</div>\r\n<!-- h-6 -->" }]
|
|
2845
|
-
}] });
|
|
2846
|
-
|
|
2847
|
-
class CideLytRequestWrapperComponent {
|
|
2848
|
-
requestService = inject(CideLytRequestService);
|
|
2849
|
-
requestItemElements;
|
|
2850
|
-
// Modern Signal-based access for better performance
|
|
2851
|
-
requestItems = this.requestService.tabs;
|
|
2852
|
-
ngAfterViewInit() {
|
|
2853
|
-
// Apply entrance animation to initial items
|
|
2854
|
-
this.applyAnimations();
|
|
2855
|
-
// Listen for changes in request items and apply animations
|
|
2856
|
-
this.requestItemElements.changes.subscribe({
|
|
2857
|
-
next: () => {
|
|
2858
|
-
this.applyAnimations();
|
|
2859
|
-
}
|
|
2860
|
-
});
|
|
2861
|
-
}
|
|
2862
|
-
// Apply animations to request items
|
|
2863
|
-
applyAnimations() {
|
|
2864
|
-
if (!this.requestItemElements)
|
|
2865
|
-
return;
|
|
2866
|
-
this.requestItemElements.forEach((itemEl) => {
|
|
2867
|
-
const element = itemEl.nativeElement;
|
|
2868
|
-
element.classList.remove('request-item-enter');
|
|
2869
|
-
void element.offsetWidth; // Force reflow
|
|
2870
|
-
element.classList.add('request-item-enter');
|
|
2871
|
-
});
|
|
2872
|
-
}
|
|
2873
|
-
// Apply flicker effect to clicked item
|
|
2874
|
-
applyFlickerEffect(event) {
|
|
2875
|
-
const target = event.currentTarget;
|
|
2876
|
-
if (target) {
|
|
2877
|
-
// Remove any existing flicker class
|
|
2878
|
-
target.classList.remove('request-item-flicker');
|
|
2879
|
-
// Force reflow to ensure the class removal is processed
|
|
2880
|
-
void target.offsetWidth;
|
|
2881
|
-
// Add flicker class to trigger animation
|
|
2882
|
-
target.classList.add('request-item-flicker');
|
|
2883
|
-
// Remove the class after animation completes
|
|
2884
|
-
setTimeout(() => {
|
|
2885
|
-
target.classList.remove('request-item-flicker');
|
|
2886
|
-
}, 400); // Match the animation duration
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2890
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideLytRequestWrapperComponent, isStandalone: true, selector: "cide-lyt-request-wrapper", viewQueries: [{ propertyName: "requestItemElements", predicate: ["requestItemEl"], descendants: true }], ngImport: i0, template: "<ng-container *ngIf=\"requestItems() as requestItems\">\n <div id=\"cide-lyt-request-wrapper\" class=\"cide-lyt-request tw-w-[inherit]\" [class.cide-lyt-request-wrapper-hide]=\"!requestItems.length\">\n <div class=\"request-content tw-flex tw-items-center tw-overflow-x-auto\">\n <ng-container *ngFor=\"let item of requestItems; let i = index\">\n <div #requestItemEl class=\"request-item\" (keydown.enter)=\"requestService.activateTab(item.id); applyFlickerEffect($event)\"\n [class.active]=\"item.active\" tabindex=\"0\" (click)=\"requestService.activateTab(item.id); applyFlickerEffect($event)\"\n [attr.data-tooltip]=\"item.title\">\n <span class=\"request-title\">{{ item.title }}</span>\n <button class=\"request-action close-action\" \n (click)=\"requestService.closeTab(item.id); $event.stopPropagation();\"\n (keydown.enter)=\"requestService.closeTab(item.id); $event.stopPropagation();\" \n tabindex=\"0\"\n data-tooltip=\"Close this tab\">\n <cide-ele-icon size=\"3xs\">close</cide-ele-icon>\n </button>\n </div>\n <!-- Only add divider between items -->\n <div class=\"request-divider\" *ngIf=\"i < requestItems.length - 1\"></div>\n </ng-container>\n </div>\n </div>\n</ng-container>", styles: [".cide-lyt-request{height:20px;background:linear-gradient(to right,#fff,#f9fafbf2);border-bottom:1px solid rgba(229,231,235,.8);box-shadow:0 2px 6px #0000000d,0 1px 1px #00000008;display:flex;align-items:center;padding:0 .5rem;position:relative;z-index:15;transition:all .2s cubic-bezier(.4,0,.2,1);will-change:transform;-webkit-user-select:none;user-select:none;backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px)}.request-content{display:flex;align-items:center;height:100%;width:100%;position:relative;overflow:hidden}.request-item{position:relative;padding:8px 12px;margin:0 4px;border-radius:.375rem;display:flex;align-items:center;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);overflow:hidden;background-color:#fff9;box-shadow:0 1px 2px #00000005;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid rgba(229,231,235,.4)}.request-item:hover{background-color:#ffffffe6;transform:translateY(-1px);box-shadow:0 2px 5px #0000000d;border-color:#d1d5db99}.request-item:active{transform:translateY(0);box-shadow:0 1px 2px #00000008}.request-item.active{background:#eff6ffcc;border-color:#3b82f64d;box-shadow:0 2px 6px #3b82f61a,0 0 0 1px #3b82f626;position:relative}@keyframes shimmerGradient{0%{background-position:100% 50%}to{background-position:0% 50%}}.request-title{font-size:.875rem;color:#374151;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .2s ease}.request-item.active .request-title{color:#2563eb;font-weight:500;text-shadow:0 0 1px rgba(37,99,235,.1)}.request-action,.request-add-action{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background-color:transparent;transition:all .2s ease}.request-action:hover,.request-add-action:hover{background-color:#f3f4f6cc;transform:scale(1.1)}.request-action:active,.request-add-action:active{transform:scale(.95)}:host-context(.dark-mode) .cide-lyt-request{background:linear-gradient(to right,#0f172a,#0f172af2);border-bottom-color:#1e293be6;box-shadow:0 2px 6px #00000026,0 1px 1px #0000001a}:host-context(.dark-mode) .request-item{background-color:#1e293b99;border-color:#33415566;box-shadow:0 1px 2px #0000001a}:host-context(.dark-mode) .request-item:hover{background-color:#1e293bcc;border-color:#47556999;box-shadow:0 2px 5px #00000026}:host-context(.dark-mode) .request-title{color:#e2e8f0}:host-context(.dark-mode) .request-item:hover .request-title{color:#f8fafc}:host-context(.dark-mode) .request-item.active{background:#1e3a8a4d;border-color:#3b82f666;box-shadow:0 2px 6px #1e40af40,0 0 0 1px #3b82f633}:host-context(.dark-mode) .request-item.active .request-title{color:#60a5fa;text-shadow:0 0 2px rgba(96,165,250,.2)}:host-context(.dark-mode) .request-action:hover,:host-context(.dark-mode) .request-add-action:hover{background-color:#334155cc}.request-item:hover{transform:translateY(-.5px);box-shadow:0 1px 3px #00000008}.request-item:hover:before{opacity:1}.request-item.active{background:linear-gradient(to right,#eff6ffcc,#dbeafe99);box-shadow:0 1px 3px #3b82f626;transform:translateZ(0)}@keyframes glowEffect{0%{opacity:.6;box-shadow:0 1px 2px #3b82f61a}50%{opacity:1;box-shadow:0 1px 3px #3b82f64d}to{opacity:.6;box-shadow:0 1px 2px #3b82f61a}}.request-title{font-size:.7rem;color:#374151;margin-right:.5rem;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px;letter-spacing:.01em;transition:all .2s ease;position:relative}.request-item.active .request-title{color:#1e40af;font-weight:600;text-shadow:0 0 .5px rgba(37,99,235,.3);letter-spacing:.015em}.request-action{display:flex;align-items:center;justify-content:center;height:14px;width:14px;border-radius:3px;transition:all .2s ease;cursor:pointer;color:#9ca3af;border:none;background:transparent;padding:0;outline:none;position:relative;overflow:hidden;transform:translateZ(0)}.request-action:hover{background-color:#3b82f614;color:#3b82f6;transform:translateY(-.5px) scale(1.05);box-shadow:0 1px 2px #0000000d}.request-action:active{transform:translateY(.5px) scale(.97);transition:all .1s ease}.request-action:focus-visible{outline:1px solid #3b82f6;outline-offset:1px;box-shadow:0 0 0 2px #3b82f61a}.close-action:hover{background-color:#ef444414;color:#ef4444}.close-action:focus-visible{outline-color:#ef4444;box-shadow:0 0 0 2px #ef44441a}.request-divider{width:1px;height:18px;background:linear-gradient(to bottom,#d1d5db1a,#d1d5db80,#d1d5db1a);margin:0 4px}:host-context(.dark-mode) .request-divider{background:linear-gradient(to bottom,#4755691a,#47556980,#4755691a)}.request-divider:before{content:\"\";position:absolute;top:0;bottom:0;left:-1px;width:3px;background:radial-gradient(ellipse at center,#ffffff80,#fff0 70%);opacity:0;transition:opacity .3s ease}.request-content:hover .request-divider:before{opacity:.7}.request-add-action{display:flex;align-items:center;justify-content:center;height:16px;width:16px;border-radius:3px;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#6b7280;background-color:transparent;border:none;padding:0;outline:none;position:relative;overflow:hidden}.request-add-action:hover{background-color:#3b82f614;color:#3b82f6;transform:translateY(-.5px) scale(1.05);box-shadow:0 1px 3px #3b82f626}.request-add-action:active{transform:translateY(.5px) scale(.97);transition:all .1s ease}.request-add-action:focus-visible{outline:1px solid #3b82f6;outline-offset:1px;box-shadow:0 0 0 2px #3b82f61a}.request-add-action:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:radial-gradient(circle,#3b82f64d,#3b82f600 70%);opacity:0;transform:scale(0);transition:transform .4s ease,opacity .3s ease}.request-add-action:active:after{opacity:1;transform:scale(3);transition:transform .1s ease,opacity .1s ease}@keyframes subtlePulse{0%{box-shadow:0 0 #3b82f600;transform:scale(1)}50%{box-shadow:0 0 0 3px #3b82f626;transform:scale(1.05)}to{box-shadow:0 0 #3b82f600;transform:scale(1)}}.request-add-action-pulse{animation:subtlePulse 2s cubic-bezier(.4,0,.6,1) infinite}:host-context(.dark-mode) .cide-lyt-request{background:linear-gradient(to right,#111827f2,#1f2937e6);border-bottom:1px solid rgba(55,65,81,.5)}:host-context(.dark-mode) .request-title{color:#e5e7eb}:host-context(.dark-mode) .request-item.active{background:linear-gradient(to right,#1e3a8a26,#2563eb1a)}:host-context(.dark-mode) .request-item.active .request-title{color:#93c5fd}:host-context(.dark-mode) .request-action,:host-context(.dark-mode) .request-add-action{color:#9ca3af}:host-context(.dark-mode) .request-divider{background:linear-gradient(to bottom,#4b55634d,#6b728080,#4b55634d)}@keyframes slideIn{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}@keyframes slideOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-5px)}}@keyframes flickerEffect{0%{opacity:1;transform:scale(1);box-shadow:0 1px 2px #00000005}25%{opacity:.7;transform:scale(.98);box-shadow:0 2px 8px #3b82f64d}50%{opacity:.9;transform:scale(1.02);box-shadow:0 4px 12px #3b82f666}75%{opacity:.8;transform:scale(.99);box-shadow:0 2px 6px #3b82f633}to{opacity:1;transform:scale(1);box-shadow:0 1px 2px #00000005}}@keyframes flickerEffectDark{0%{opacity:1;transform:scale(1);box-shadow:0 1px 2px #0000001a}25%{opacity:.7;transform:scale(.98);box-shadow:0 2px 8px #60a5fa4d}50%{opacity:.9;transform:scale(1.02);box-shadow:0 4px 12px #60a5fa66}75%{opacity:.8;transform:scale(.99);box-shadow:0 2px 6px #60a5fa33}to{opacity:1;transform:scale(1);box-shadow:0 1px 2px #0000001a}}.request-item-enter{animation:slideIn .2s cubic-bezier(.4,0,.2,1)}.request-item-exit{animation:slideOut .2s cubic-bezier(.4,0,.2,1)}.request-item-flicker{animation:flickerEffect .4s cubic-bezier(.4,0,.2,1)}:host-context(.dark-mode) .request-item-flicker{animation:flickerEffectDark .4s cubic-bezier(.4,0,.2,1)}@media (max-width: 640px){.request-title{max-width:80px}}.request-item:focus,.request-action:focus,.request-add-action:focus{outline:none;box-shadow:0 0 0 2px #3b82f64d}:host-context(.dark-mode) .request-item:focus,:host-context(.dark-mode) .request-action:focus,:host-context(.dark-mode) .request-add-action:focus{box-shadow:0 0 0 2px #60a5fa66}.cide-lyt-request:hover{box-shadow:0 3px 8px #00000012,0 1px 2px #0000000d;transform:translateY(-1px)}.request-content:empty .request-add-action{animation:pulseAttention 2s infinite}@keyframes pulseAttention{0%{box-shadow:0 0 #3b82f666;transform:scale(1)}70%{box-shadow:0 0 0 8px #3b82f600;transform:scale(1.05)}to{box-shadow:0 0 #3b82f600;transform:scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
2891
|
-
}
|
|
2892
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytRequestWrapperComponent, decorators: [{
|
|
2893
|
-
type: Component,
|
|
2894
|
-
args: [{ selector: 'cide-lyt-request-wrapper', standalone: true, imports: [CommonModule, CideIconComponent], template: "<ng-container *ngIf=\"requestItems() as requestItems\">\n <div id=\"cide-lyt-request-wrapper\" class=\"cide-lyt-request tw-w-[inherit]\" [class.cide-lyt-request-wrapper-hide]=\"!requestItems.length\">\n <div class=\"request-content tw-flex tw-items-center tw-overflow-x-auto\">\n <ng-container *ngFor=\"let item of requestItems; let i = index\">\n <div #requestItemEl class=\"request-item\" (keydown.enter)=\"requestService.activateTab(item.id); applyFlickerEffect($event)\"\n [class.active]=\"item.active\" tabindex=\"0\" (click)=\"requestService.activateTab(item.id); applyFlickerEffect($event)\"\n [attr.data-tooltip]=\"item.title\">\n <span class=\"request-title\">{{ item.title }}</span>\n <button class=\"request-action close-action\" \n (click)=\"requestService.closeTab(item.id); $event.stopPropagation();\"\n (keydown.enter)=\"requestService.closeTab(item.id); $event.stopPropagation();\" \n tabindex=\"0\"\n data-tooltip=\"Close this tab\">\n <cide-ele-icon size=\"3xs\">close</cide-ele-icon>\n </button>\n </div>\n <!-- Only add divider between items -->\n <div class=\"request-divider\" *ngIf=\"i < requestItems.length - 1\"></div>\n </ng-container>\n </div>\n </div>\n</ng-container>", styles: [".cide-lyt-request{height:20px;background:linear-gradient(to right,#fff,#f9fafbf2);border-bottom:1px solid rgba(229,231,235,.8);box-shadow:0 2px 6px #0000000d,0 1px 1px #00000008;display:flex;align-items:center;padding:0 .5rem;position:relative;z-index:15;transition:all .2s cubic-bezier(.4,0,.2,1);will-change:transform;-webkit-user-select:none;user-select:none;backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px)}.request-content{display:flex;align-items:center;height:100%;width:100%;position:relative;overflow:hidden}.request-item{position:relative;padding:8px 12px;margin:0 4px;border-radius:.375rem;display:flex;align-items:center;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);overflow:hidden;background-color:#fff9;box-shadow:0 1px 2px #00000005;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid rgba(229,231,235,.4)}.request-item:hover{background-color:#ffffffe6;transform:translateY(-1px);box-shadow:0 2px 5px #0000000d;border-color:#d1d5db99}.request-item:active{transform:translateY(0);box-shadow:0 1px 2px #00000008}.request-item.active{background:#eff6ffcc;border-color:#3b82f64d;box-shadow:0 2px 6px #3b82f61a,0 0 0 1px #3b82f626;position:relative}@keyframes shimmerGradient{0%{background-position:100% 50%}to{background-position:0% 50%}}.request-title{font-size:.875rem;color:#374151;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .2s ease}.request-item.active .request-title{color:#2563eb;font-weight:500;text-shadow:0 0 1px rgba(37,99,235,.1)}.request-action,.request-add-action{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background-color:transparent;transition:all .2s ease}.request-action:hover,.request-add-action:hover{background-color:#f3f4f6cc;transform:scale(1.1)}.request-action:active,.request-add-action:active{transform:scale(.95)}:host-context(.dark-mode) .cide-lyt-request{background:linear-gradient(to right,#0f172a,#0f172af2);border-bottom-color:#1e293be6;box-shadow:0 2px 6px #00000026,0 1px 1px #0000001a}:host-context(.dark-mode) .request-item{background-color:#1e293b99;border-color:#33415566;box-shadow:0 1px 2px #0000001a}:host-context(.dark-mode) .request-item:hover{background-color:#1e293bcc;border-color:#47556999;box-shadow:0 2px 5px #00000026}:host-context(.dark-mode) .request-title{color:#e2e8f0}:host-context(.dark-mode) .request-item:hover .request-title{color:#f8fafc}:host-context(.dark-mode) .request-item.active{background:#1e3a8a4d;border-color:#3b82f666;box-shadow:0 2px 6px #1e40af40,0 0 0 1px #3b82f633}:host-context(.dark-mode) .request-item.active .request-title{color:#60a5fa;text-shadow:0 0 2px rgba(96,165,250,.2)}:host-context(.dark-mode) .request-action:hover,:host-context(.dark-mode) .request-add-action:hover{background-color:#334155cc}.request-item:hover{transform:translateY(-.5px);box-shadow:0 1px 3px #00000008}.request-item:hover:before{opacity:1}.request-item.active{background:linear-gradient(to right,#eff6ffcc,#dbeafe99);box-shadow:0 1px 3px #3b82f626;transform:translateZ(0)}@keyframes glowEffect{0%{opacity:.6;box-shadow:0 1px 2px #3b82f61a}50%{opacity:1;box-shadow:0 1px 3px #3b82f64d}to{opacity:.6;box-shadow:0 1px 2px #3b82f61a}}.request-title{font-size:.7rem;color:#374151;margin-right:.5rem;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px;letter-spacing:.01em;transition:all .2s ease;position:relative}.request-item.active .request-title{color:#1e40af;font-weight:600;text-shadow:0 0 .5px rgba(37,99,235,.3);letter-spacing:.015em}.request-action{display:flex;align-items:center;justify-content:center;height:14px;width:14px;border-radius:3px;transition:all .2s ease;cursor:pointer;color:#9ca3af;border:none;background:transparent;padding:0;outline:none;position:relative;overflow:hidden;transform:translateZ(0)}.request-action:hover{background-color:#3b82f614;color:#3b82f6;transform:translateY(-.5px) scale(1.05);box-shadow:0 1px 2px #0000000d}.request-action:active{transform:translateY(.5px) scale(.97);transition:all .1s ease}.request-action:focus-visible{outline:1px solid #3b82f6;outline-offset:1px;box-shadow:0 0 0 2px #3b82f61a}.close-action:hover{background-color:#ef444414;color:#ef4444}.close-action:focus-visible{outline-color:#ef4444;box-shadow:0 0 0 2px #ef44441a}.request-divider{width:1px;height:18px;background:linear-gradient(to bottom,#d1d5db1a,#d1d5db80,#d1d5db1a);margin:0 4px}:host-context(.dark-mode) .request-divider{background:linear-gradient(to bottom,#4755691a,#47556980,#4755691a)}.request-divider:before{content:\"\";position:absolute;top:0;bottom:0;left:-1px;width:3px;background:radial-gradient(ellipse at center,#ffffff80,#fff0 70%);opacity:0;transition:opacity .3s ease}.request-content:hover .request-divider:before{opacity:.7}.request-add-action{display:flex;align-items:center;justify-content:center;height:16px;width:16px;border-radius:3px;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#6b7280;background-color:transparent;border:none;padding:0;outline:none;position:relative;overflow:hidden}.request-add-action:hover{background-color:#3b82f614;color:#3b82f6;transform:translateY(-.5px) scale(1.05);box-shadow:0 1px 3px #3b82f626}.request-add-action:active{transform:translateY(.5px) scale(.97);transition:all .1s ease}.request-add-action:focus-visible{outline:1px solid #3b82f6;outline-offset:1px;box-shadow:0 0 0 2px #3b82f61a}.request-add-action:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:radial-gradient(circle,#3b82f64d,#3b82f600 70%);opacity:0;transform:scale(0);transition:transform .4s ease,opacity .3s ease}.request-add-action:active:after{opacity:1;transform:scale(3);transition:transform .1s ease,opacity .1s ease}@keyframes subtlePulse{0%{box-shadow:0 0 #3b82f600;transform:scale(1)}50%{box-shadow:0 0 0 3px #3b82f626;transform:scale(1.05)}to{box-shadow:0 0 #3b82f600;transform:scale(1)}}.request-add-action-pulse{animation:subtlePulse 2s cubic-bezier(.4,0,.6,1) infinite}:host-context(.dark-mode) .cide-lyt-request{background:linear-gradient(to right,#111827f2,#1f2937e6);border-bottom:1px solid rgba(55,65,81,.5)}:host-context(.dark-mode) .request-title{color:#e5e7eb}:host-context(.dark-mode) .request-item.active{background:linear-gradient(to right,#1e3a8a26,#2563eb1a)}:host-context(.dark-mode) .request-item.active .request-title{color:#93c5fd}:host-context(.dark-mode) .request-action,:host-context(.dark-mode) .request-add-action{color:#9ca3af}:host-context(.dark-mode) .request-divider{background:linear-gradient(to bottom,#4b55634d,#6b728080,#4b55634d)}@keyframes slideIn{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}@keyframes slideOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-5px)}}@keyframes flickerEffect{0%{opacity:1;transform:scale(1);box-shadow:0 1px 2px #00000005}25%{opacity:.7;transform:scale(.98);box-shadow:0 2px 8px #3b82f64d}50%{opacity:.9;transform:scale(1.02);box-shadow:0 4px 12px #3b82f666}75%{opacity:.8;transform:scale(.99);box-shadow:0 2px 6px #3b82f633}to{opacity:1;transform:scale(1);box-shadow:0 1px 2px #00000005}}@keyframes flickerEffectDark{0%{opacity:1;transform:scale(1);box-shadow:0 1px 2px #0000001a}25%{opacity:.7;transform:scale(.98);box-shadow:0 2px 8px #60a5fa4d}50%{opacity:.9;transform:scale(1.02);box-shadow:0 4px 12px #60a5fa66}75%{opacity:.8;transform:scale(.99);box-shadow:0 2px 6px #60a5fa33}to{opacity:1;transform:scale(1);box-shadow:0 1px 2px #0000001a}}.request-item-enter{animation:slideIn .2s cubic-bezier(.4,0,.2,1)}.request-item-exit{animation:slideOut .2s cubic-bezier(.4,0,.2,1)}.request-item-flicker{animation:flickerEffect .4s cubic-bezier(.4,0,.2,1)}:host-context(.dark-mode) .request-item-flicker{animation:flickerEffectDark .4s cubic-bezier(.4,0,.2,1)}@media (max-width: 640px){.request-title{max-width:80px}}.request-item:focus,.request-action:focus,.request-add-action:focus{outline:none;box-shadow:0 0 0 2px #3b82f64d}:host-context(.dark-mode) .request-item:focus,:host-context(.dark-mode) .request-action:focus,:host-context(.dark-mode) .request-add-action:focus{box-shadow:0 0 0 2px #60a5fa66}.cide-lyt-request:hover{box-shadow:0 3px 8px #00000012,0 1px 2px #0000000d;transform:translateY(-1px)}.request-content:empty .request-add-action{animation:pulseAttention 2s infinite}@keyframes pulseAttention{0%{box-shadow:0 0 #3b82f666;transform:scale(1)}70%{box-shadow:0 0 0 8px #3b82f600;transform:scale(1.05)}to{box-shadow:0 0 #3b82f600;transform:scale(1)}}\n"] }]
|
|
2895
|
-
}], propDecorators: { requestItemElements: [{
|
|
2896
|
-
type: ViewChildren,
|
|
2897
|
-
args: ['requestItemEl']
|
|
2898
|
-
}] } });
|
|
2899
|
-
|
|
2900
|
-
class TabContentComponent {
|
|
2901
|
-
requestService;
|
|
2902
|
-
router;
|
|
2903
|
-
cdr;
|
|
2904
|
-
scrollContainerRef;
|
|
2905
|
-
currentActiveTabId = null;
|
|
2906
|
-
isRestoringScroll = false; // Flag to prevent saving scroll during restoration attempts
|
|
2907
|
-
constructor(requestService, router, // Inject Router to listen for NavigationEnd
|
|
2908
|
-
cdr) {
|
|
2909
|
-
this.requestService = requestService;
|
|
2910
|
-
this.router = router;
|
|
2911
|
-
this.cdr = cdr;
|
|
2912
|
-
// Modern Angular signal pattern: Use effect to react to active tab changes
|
|
2913
|
-
effect(() => {
|
|
2914
|
-
const activeTab = this.requestService.activeTab();
|
|
2915
|
-
if (activeTab && activeTab.id !== this.currentActiveTabId) {
|
|
2916
|
-
this.currentActiveTabId = activeTab.id;
|
|
2917
|
-
// Delay scroll restoration to ensure DOM is ready
|
|
2918
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
2919
|
-
delay(100).then(() => this.restoreScrollPosition(activeTab));
|
|
2920
|
-
}
|
|
2921
|
-
});
|
|
2922
|
-
// Listen to route changes for scroll restoration
|
|
2923
|
-
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe({
|
|
2924
|
-
next: () => {
|
|
2925
|
-
// Use signal to get active tab after navigation
|
|
2926
|
-
const activeTab = this.requestService.activeTab();
|
|
2927
|
-
if (activeTab && activeTab.id !== this.currentActiveTabId) {
|
|
2928
|
-
this.currentActiveTabId = activeTab.id;
|
|
2929
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
2930
|
-
delay(150).then(() => this.restoreScrollPosition(activeTab));
|
|
2931
|
-
}
|
|
2932
|
-
}
|
|
2933
|
-
});
|
|
2934
|
-
}
|
|
2935
|
-
ngAfterViewInit() {
|
|
2936
|
-
// Initial scroll restoration for the active tab
|
|
2937
|
-
const activeTab = this.requestService.activeTab();
|
|
2938
|
-
if (activeTab) {
|
|
2939
|
-
this.currentActiveTabId = activeTab.id;
|
|
2940
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
2941
|
-
delay(50).then(() => this.restoreScrollPosition(activeTab));
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
onScroll() {
|
|
2945
|
-
if (this.isRestoringScroll || !this.scrollContainerRef || !this.currentActiveTabId) {
|
|
2946
|
-
return;
|
|
2947
|
-
}
|
|
2948
|
-
const { scrollTop, scrollLeft } = this.scrollContainerRef.nativeElement;
|
|
2949
|
-
this.requestService.updateTabScrollPosition(this.currentActiveTabId, scrollTop, scrollLeft);
|
|
2950
|
-
}
|
|
2951
|
-
restoreScrollPosition(tab) {
|
|
2952
|
-
if (tab && this.scrollContainerRef?.nativeElement) {
|
|
2953
|
-
this.isRestoringScroll = true;
|
|
2954
|
-
const container = this.scrollContainerRef.nativeElement;
|
|
2955
|
-
container.scrollTop = tab.scrollTop ?? 0;
|
|
2956
|
-
container.scrollLeft = tab.scrollLeft ?? 0;
|
|
2957
|
-
// Modern ES2022+ pattern: Use Promise-based delay
|
|
2958
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
2959
|
-
delay(150).then(() => { this.isRestoringScroll = false; }); // Allow scroll event to settle
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabContentComponent, deps: [{ token: CideLytRequestService }, { token: i2.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2963
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: TabContentComponent, isStandalone: true, selector: "cide-lyt-tab-content", viewQueries: [{ propertyName: "scrollContainerRef", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: `
|
|
2964
|
-
<div class="tab-content-container" #scrollContainer (scroll)="onScroll()">
|
|
2965
|
-
<router-outlet></router-outlet>
|
|
2966
|
-
</div>
|
|
2967
|
-
`, isInline: true, styles: [":host{height:100%;display:flex;flex-direction:column}.tab-content-container{height:100%;width:100%;position:relative;overflow:auto;display:flex;flex-direction:column;flex:1}::ng-deep router-outlet+*{height:100%;flex:1;display:flex;flex-direction:column}::ng-deep cide-lyt-home-wrapper,::ng-deep cide-adm-home-wrapper,::ng-deep cide-adm-entity,::ng-deep cide-adm-entity-list{height:100%!important;flex:1!important;display:flex!important;flex-direction:column!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] });
|
|
2968
|
-
}
|
|
2969
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: TabContentComponent, decorators: [{
|
|
2970
|
-
type: Component,
|
|
2971
|
-
args: [{ selector: 'cide-lyt-tab-content', standalone: true, imports: [CommonModule, RouterModule], template: `
|
|
2972
|
-
<div class="tab-content-container" #scrollContainer (scroll)="onScroll()">
|
|
2973
|
-
<router-outlet></router-outlet>
|
|
2974
|
-
</div>
|
|
2975
|
-
`, styles: [":host{height:100%;display:flex;flex-direction:column}.tab-content-container{height:100%;width:100%;position:relative;overflow:auto;display:flex;flex-direction:column;flex:1}::ng-deep router-outlet+*{height:100%;flex:1;display:flex;flex-direction:column}::ng-deep cide-lyt-home-wrapper,::ng-deep cide-adm-home-wrapper,::ng-deep cide-adm-entity,::ng-deep cide-adm-entity-list{height:100%!important;flex:1!important;display:flex!important;flex-direction:column!important}\n"] }]
|
|
2976
|
-
}], ctorParameters: () => [{ type: CideLytRequestService }, { type: i2.Router }, { type: i0.ChangeDetectorRef }], propDecorators: { scrollContainerRef: [{
|
|
2977
|
-
type: ViewChild,
|
|
2978
|
-
args: ['scrollContainer']
|
|
2979
|
-
}] } });
|
|
2980
|
-
|
|
2981
|
-
class CideLytLayoutWrapperComponent {
|
|
2982
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytLayoutWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2983
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideLytLayoutWrapperComponent, isStandalone: true, selector: "cide-lyt-layout-wrapper", ngImport: i0, template: "<div class=\"tw-w-full tw-table tw-h-screen tw-relative\" id=\"cide-lyt-layout-wrapper\">\r\n <!-- We will use here Header -->\r\n <cide-lyt-header-wrapper class=\"tw-table-row tw-h-0\"></cide-lyt-header-wrapper>\r\n <!-- we will use here request tabs -->\r\n <div class=\"tw-table-row\" id=\"cide-lyt-sidebar-page-wrapper\">\r\n <div class=\"cide-lyt-sidebar-page-wrapper tw-h-full tw-w-screen\" id=\"cide-lyt-sidebar-page-inner-wrapper\">\r\n <!-- We will use here Sidebar -->\r\n <cide-lyt-sidebar-wrapper id=\"cide-lyt-sidebar-page\"\r\n class=\"cide-lyt-sidebar-page-hide\"></cide-lyt-sidebar-wrapper>\r\n <!-- We will use here Router Outlet -->\r\n <div class=\"cide-lyt-page-wrapper tw-block tw-h-full tw-w-full\" id=\"cide-lyt-page-wrapper\">\r\n <cide-lyt-request-wrapper class=\"tw-w-[inherit] tw-h-auto\"></cide-lyt-request-wrapper>\r\n <div class=\"cide-lyt-outlet-sidedrawer-wrapper tw-table-row\"\r\n id=\"cide-lyt-outlet-sidedrawer-wrapper\">\r\n <div class=\"tw-w-full tw-h-full tw-overflow-y-auto cide-scrollbar-width-thin\"\r\n id=\"cide-lyt-outlet-wrapper\">\r\n <cide-lyt-tab-content></cide-lyt-tab-content>\r\n </div>\r\n <!-- We will use here Side Drawer -->\r\n <cide-lyt-sidedrawer-wrapper></cide-lyt-sidedrawer-wrapper>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"cide-lyt-footer-console-wrapper cide-lyt-footer-console-wrapper-hide tw-relative tw-table-row tw-h-0\"\r\n id=\"cide-lyt-footer-console-wrapper\">\r\n <!-- We will use here footer -->\r\n <cide-lyt-footer-wrapper></cide-lyt-footer-wrapper>\r\n <!-- We will use here Console -->\r\n <cide-lyt-console-wrapper></cide-lyt-console-wrapper>\r\n </div>\r\n</div>", styles: [""], dependencies: [{ kind: "component", type: CideLytHeaderWrapperComponent, selector: "cide-lyt-header-wrapper" }, { kind: "component", type: CideLytSidebarWrapperComponent, selector: "cide-lyt-sidebar-wrapper" }, { kind: "component", type: CideLytSidedrawerWrapperComponent, selector: "cide-lyt-sidedrawer-wrapper" }, { kind: "component", type: CideLytFooterWrapperComponent, selector: "cide-lyt-footer-wrapper" }, { kind: "component", type: CideLytConsoleWrapperComponent, selector: "cide-lyt-console-wrapper" }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: CideLytRequestWrapperComponent, selector: "cide-lyt-request-wrapper" }, { kind: "component", type: TabContentComponent, selector: "cide-lyt-tab-content" }] });
|
|
2984
|
-
}
|
|
2985
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytLayoutWrapperComponent, decorators: [{
|
|
2986
|
-
type: Component,
|
|
2987
|
-
args: [{ selector: 'cide-lyt-layout-wrapper', standalone: true, imports: [
|
|
2988
|
-
CideLytHeaderWrapperComponent,
|
|
2989
|
-
CideLytSidebarWrapperComponent,
|
|
2990
|
-
CideLytSidedrawerWrapperComponent,
|
|
2991
|
-
CideLytFooterWrapperComponent,
|
|
2992
|
-
CideLytConsoleWrapperComponent,
|
|
2993
|
-
RouterModule,
|
|
2994
|
-
CideLytRequestWrapperComponent,
|
|
2995
|
-
TabContentComponent
|
|
2996
|
-
], template: "<div class=\"tw-w-full tw-table tw-h-screen tw-relative\" id=\"cide-lyt-layout-wrapper\">\r\n <!-- We will use here Header -->\r\n <cide-lyt-header-wrapper class=\"tw-table-row tw-h-0\"></cide-lyt-header-wrapper>\r\n <!-- we will use here request tabs -->\r\n <div class=\"tw-table-row\" id=\"cide-lyt-sidebar-page-wrapper\">\r\n <div class=\"cide-lyt-sidebar-page-wrapper tw-h-full tw-w-screen\" id=\"cide-lyt-sidebar-page-inner-wrapper\">\r\n <!-- We will use here Sidebar -->\r\n <cide-lyt-sidebar-wrapper id=\"cide-lyt-sidebar-page\"\r\n class=\"cide-lyt-sidebar-page-hide\"></cide-lyt-sidebar-wrapper>\r\n <!-- We will use here Router Outlet -->\r\n <div class=\"cide-lyt-page-wrapper tw-block tw-h-full tw-w-full\" id=\"cide-lyt-page-wrapper\">\r\n <cide-lyt-request-wrapper class=\"tw-w-[inherit] tw-h-auto\"></cide-lyt-request-wrapper>\r\n <div class=\"cide-lyt-outlet-sidedrawer-wrapper tw-table-row\"\r\n id=\"cide-lyt-outlet-sidedrawer-wrapper\">\r\n <div class=\"tw-w-full tw-h-full tw-overflow-y-auto cide-scrollbar-width-thin\"\r\n id=\"cide-lyt-outlet-wrapper\">\r\n <cide-lyt-tab-content></cide-lyt-tab-content>\r\n </div>\r\n <!-- We will use here Side Drawer -->\r\n <cide-lyt-sidedrawer-wrapper></cide-lyt-sidedrawer-wrapper>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"cide-lyt-footer-console-wrapper cide-lyt-footer-console-wrapper-hide tw-relative tw-table-row tw-h-0\"\r\n id=\"cide-lyt-footer-console-wrapper\">\r\n <!-- We will use here footer -->\r\n <cide-lyt-footer-wrapper></cide-lyt-footer-wrapper>\r\n <!-- We will use here Console -->\r\n <cide-lyt-console-wrapper></cide-lyt-console-wrapper>\r\n </div>\r\n</div>" }]
|
|
2997
|
-
}] });
|
|
2998
|
-
|
|
2999
|
-
class CloudIdeLayoutComponent {
|
|
3000
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CloudIdeLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3001
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CloudIdeLayoutComponent, isStandalone: true, selector: "cide-lyt-wrapper", ngImport: i0, template: `
|
|
3002
|
-
<cide-lyt-layout-wrapper></cide-lyt-layout-wrapper>
|
|
3003
|
-
|
|
3004
|
-
<!-- Floating Container Manager -->
|
|
3005
|
-
<cide-ele-floating-container-manager></cide-ele-floating-container-manager>
|
|
3006
|
-
|
|
3007
|
-
<!-- Global Notifications and Confirmation Dialogs -->
|
|
3008
|
-
<cide-ele-global-notifications></cide-ele-global-notifications>
|
|
3009
|
-
`, isInline: true, styles: [""], dependencies: [{ kind: "component", type: CideLytLayoutWrapperComponent, selector: "cide-lyt-layout-wrapper" }, { kind: "component", type: CideEleFloatingContainerManagerComponent, selector: "cide-ele-floating-container-manager" }, { kind: "component", type: CideEleGlobalNotificationsComponent, selector: "cide-ele-global-notifications" }] });
|
|
3010
|
-
}
|
|
3011
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CloudIdeLayoutComponent, decorators: [{
|
|
3012
|
-
type: Component,
|
|
3013
|
-
args: [{ selector: 'cide-lyt-wrapper', standalone: true, template: `
|
|
3014
|
-
<cide-lyt-layout-wrapper></cide-lyt-layout-wrapper>
|
|
3015
|
-
|
|
3016
|
-
<!-- Floating Container Manager -->
|
|
3017
|
-
<cide-ele-floating-container-manager></cide-ele-floating-container-manager>
|
|
3018
|
-
|
|
3019
|
-
<!-- Global Notifications and Confirmation Dialogs -->
|
|
3020
|
-
<cide-ele-global-notifications></cide-ele-global-notifications>
|
|
3021
|
-
`, imports: [
|
|
3022
|
-
CideLytLayoutWrapperComponent,
|
|
3023
|
-
CideEleFloatingContainerManagerComponent,
|
|
3024
|
-
CideEleGlobalNotificationsComponent
|
|
3025
|
-
] }]
|
|
3026
|
-
}] });
|
|
3027
|
-
|
|
3028
|
-
var cloudIdeLayout_component = /*#__PURE__*/Object.freeze({
|
|
3029
|
-
__proto__: null,
|
|
3030
|
-
CloudIdeLayoutComponent: CloudIdeLayoutComponent
|
|
3031
|
-
});
|
|
3032
|
-
|
|
3033
|
-
const layoutRoutes = {
|
|
3034
|
-
path: "control-panel",
|
|
3035
|
-
loadComponent: () => Promise.resolve().then(function () { return cloudIdeLayout_component; }).then(c => c.CloudIdeLayoutComponent),
|
|
3036
|
-
canActivate: [authGuard]
|
|
3037
|
-
};
|
|
3038
|
-
const layoutControlPannelChildRoutes = [{
|
|
3039
|
-
path: "",
|
|
3040
|
-
pathMatch: "full",
|
|
3041
|
-
redirectTo: "home"
|
|
3042
|
-
},
|
|
3043
|
-
{
|
|
3044
|
-
path: "home",
|
|
3045
|
-
loadComponent: () => import('./cloud-ide-layout-home-wrapper.component-iPkvBOgE.mjs').then(c => c.CideLytHomeWrapperComponent),
|
|
3046
|
-
canActivate: [authGuard],
|
|
3047
|
-
data: {
|
|
3048
|
-
reuseTab: true, // For CustomRouteReuseStrategy
|
|
3049
|
-
sypg_page_code: "cide_lyt_home" // Used by RequestService to fetch tab properties
|
|
3050
|
-
}
|
|
3051
|
-
}];
|
|
3052
|
-
|
|
3053
|
-
// Define an InjectionToken for the environment configuration
|
|
3054
|
-
// This is needed to inject environment variables into services provided in 'root'
|
|
3055
|
-
const ENVIRONMENT_CONFIG = new InjectionToken('ENVIRONMENT_CONFIG');
|
|
3056
|
-
/**
|
|
3057
|
-
* Utility function to convert database format keys to CSS format and handle units
|
|
3058
|
-
* @param key - The database format key (e.g., 'font_size_xxxl')
|
|
3059
|
-
* @param value - The value to be set
|
|
3060
|
-
* @returns Object with cssVarName and processed value
|
|
3061
|
-
*/
|
|
3062
|
-
function processThemeVariable(key, value) {
|
|
3063
|
-
// 1. Convert the key from the DB format to the CSS format.
|
|
3064
|
-
const cssVarName = `${key.replace(/_/g, '-')}`;
|
|
3065
|
-
// 2. Append the font_size and line_height value to rem
|
|
3066
|
-
let processedValue = value;
|
|
3067
|
-
if (cssVarName.includes('font-size') || cssVarName.includes('line-height')) {
|
|
3068
|
-
processedValue = parseFloat(value) + 'rem';
|
|
3069
|
-
}
|
|
3070
|
-
return { cssVarName, processedValue: processedValue };
|
|
3071
|
-
}
|
|
3072
|
-
/**
|
|
3073
|
-
* Sets a single CSS variable on the document root element
|
|
3074
|
-
* @param key - The database format key (e.g., 'font_size_xxxl')
|
|
3075
|
-
* @param value - The value to be set
|
|
3076
|
-
* @param platformId - Platform ID for browser check
|
|
3077
|
-
*/
|
|
3078
|
-
function setCSSVariable(key, value, platformId) {
|
|
3079
|
-
if (isPlatformBrowser(platformId)) {
|
|
3080
|
-
const { cssVarName, processedValue } = processThemeVariable(key, value);
|
|
3081
|
-
const root = document.documentElement;
|
|
3082
|
-
root.style.setProperty(cssVarName, processedValue);
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
class CideLytThemeService {
|
|
3086
|
-
// Modern Angular signals for reactive state management
|
|
3087
|
-
selectedThemeSignal = signal("", ...(ngDevMode ? [{ debugName: "selectedThemeSignal" }] : []));
|
|
3088
|
-
selectedTheme = this.selectedThemeSignal.asReadonly();
|
|
3089
|
-
// Modern dependency injection
|
|
3090
|
-
http = inject(HttpClient);
|
|
3091
|
-
platformId = inject(PLATFORM_ID);
|
|
3092
|
-
environment = inject(ENVIRONMENT_CONFIG);
|
|
3093
|
-
/**
|
|
3094
|
-
* Fetches the theme from the API and applies it to the document.
|
|
3095
|
-
* This method is intended to be called when the theme should actually be applied.
|
|
3096
|
-
*/
|
|
3097
|
-
fetchAndApplyTheme() {
|
|
3098
|
-
console.log('Fetching and applying theme via CideLytThemeService...');
|
|
3099
|
-
return this.loadTheme(); // Use the existing loadTheme logic
|
|
3100
|
-
}
|
|
3101
|
-
/**
|
|
3102
|
-
* Fetches the list of system themes from the API.
|
|
3103
|
-
*/
|
|
3104
|
-
fetchSystemThemesList() {
|
|
3105
|
-
return this.http.get(cidePath.join([
|
|
3106
|
-
hostManagerRoutesUrl.cideSuiteHost,
|
|
3107
|
-
designConfigRoutesUrl?.module,
|
|
3108
|
-
designConfigRoutesUrl?.getSystemThemesList
|
|
3109
|
-
]));
|
|
3110
|
-
}
|
|
3111
|
-
/**
|
|
3112
|
-
* Internal method to fetch the theme from the API and apply it.
|
|
3113
|
-
*/
|
|
3114
|
-
loadTheme() {
|
|
3115
|
-
return this.http.post(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, designConfigRoutesUrl?.module, designConfigRoutesUrl?.getSystemTheme]), { syupth_user_id_user: "659a7ea0a1f0e08246661766" }).pipe(tap(theme => {
|
|
3116
|
-
console.log("Applying theme:", theme);
|
|
3117
|
-
this.applyTheme(theme?.data);
|
|
3118
|
-
}));
|
|
3119
|
-
}
|
|
3120
|
-
/**
|
|
3121
|
-
* Sets the CSS variables on the root element.
|
|
3122
|
-
* @param theme - An object where keys are CSS variable names (e.g., '--color-text-body')
|
|
3123
|
-
* and values are the color strings (e.g., '#ffffff').
|
|
3124
|
-
*/
|
|
3125
|
-
applyTheme(theme) {
|
|
3126
|
-
console.log("Applying theme:", theme);
|
|
3127
|
-
let merged = {};
|
|
3128
|
-
if (theme?.core_system_themes?.syth_properties) {
|
|
3129
|
-
merged = { ...merged, ...theme.core_system_themes.syth_properties };
|
|
3130
|
-
this.selectedThemeSignal.set(theme.core_system_themes._id);
|
|
3131
|
-
}
|
|
3132
|
-
if (theme?.core_system_organization_themes?.syoth_overrides) {
|
|
3133
|
-
merged = { ...merged, ...theme.core_system_organization_themes.syoth_overrides };
|
|
3134
|
-
}
|
|
3135
|
-
if (theme?.core_system_theme_user_preferences?.syupth_overrides) {
|
|
3136
|
-
merged = { ...merged, ...theme.core_system_theme_user_preferences.syupth_overrides };
|
|
3137
|
-
}
|
|
3138
|
-
console.log("Merged theme:", merged);
|
|
3139
|
-
// Check if the code is running in a browser environment.
|
|
3140
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
3141
|
-
const root = document.documentElement;
|
|
3142
|
-
for (const [key, value] of Object.entries(merged)) {
|
|
3143
|
-
const { cssVarName, processedValue } = processThemeVariable(key, value);
|
|
3144
|
-
// Set the CSS custom property on the root element.
|
|
3145
|
-
root.style.setProperty(cssVarName, processedValue);
|
|
3146
|
-
}
|
|
3147
|
-
}
|
|
3148
|
-
}
|
|
3149
|
-
/**
|
|
3150
|
-
* Saves the system theme user preferences.
|
|
3151
|
-
* @param themeVars - The theme variables to save.
|
|
3152
|
-
* @returns An observable of the system theme user preferences.
|
|
3153
|
-
*/
|
|
3154
|
-
saveSystemThemeUserPreferences(themeVars) {
|
|
3155
|
-
return this.http.post(cidePath.join([hostManagerRoutesUrl.cideSuiteHost, designConfigRoutesUrl?.module, designConfigRoutesUrl?.systemThemeUserPreferences]), themeVars);
|
|
3156
|
-
}
|
|
3157
|
-
/**
|
|
3158
|
-
* Updates a single CSS variable using the utility function
|
|
3159
|
-
* @param key - The database format key (e.g., 'font_size_xxxl')
|
|
3160
|
-
* @param value - The value to be set
|
|
3161
|
-
*/
|
|
3162
|
-
updateCSSVariable(key, value) {
|
|
3163
|
-
setCSSVariable(key, value, this.platformId);
|
|
3164
|
-
}
|
|
3165
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3166
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytThemeService, providedIn: 'root' });
|
|
3167
|
-
}
|
|
3168
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytThemeService, decorators: [{
|
|
3169
|
-
type: Injectable,
|
|
3170
|
-
args: [{
|
|
3171
|
-
providedIn: 'root'
|
|
3172
|
-
}]
|
|
3173
|
-
}] });
|
|
3174
|
-
/**
|
|
3175
|
-
* Factory function for APP_INITIALIZER.
|
|
3176
|
-
* This function is used in your app.config.ts.
|
|
3177
|
-
* It conditionally triggers theme loading based on the environment setting.
|
|
3178
|
-
*/
|
|
3179
|
-
function themeFactory(themeService, environmentConfig) {
|
|
3180
|
-
return () => {
|
|
3181
|
-
if (!environmentConfig.applyThemeOnLogin) {
|
|
3182
|
-
// If theme should be applied on app initialize, fetch and apply it.
|
|
3183
|
-
console.log('APP_INITIALIZER: Applying theme...');
|
|
3184
|
-
return themeService.fetchAndApplyTheme();
|
|
3185
|
-
}
|
|
3186
|
-
else {
|
|
3187
|
-
// If theme should be applied on login, APP_INITIALIZER does nothing for the theme.
|
|
3188
|
-
// We return an observable that immediately completes.
|
|
3189
|
-
console.log('APP_INITIALIZER: Theme application deferred to after login (per environment config).');
|
|
3190
|
-
return of(undefined); // Return an observable that completes immediately
|
|
3191
|
-
}
|
|
3192
|
-
};
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
class CacheManagerService {
|
|
3196
|
-
sharedService = inject(CideLytSharedService);
|
|
3197
|
-
// Computed cache statistics
|
|
3198
|
-
cacheStats = computed(() => {
|
|
3199
|
-
const info = this.sharedService.cacheInfo();
|
|
3200
|
-
return {
|
|
3201
|
-
...info,
|
|
3202
|
-
hitRatio: info.totalCached > 0 ? (info.validCached / info.totalCached) * 100 : 0,
|
|
3203
|
-
memoryEstimate: this.estimateCacheMemoryUsage()
|
|
3204
|
-
};
|
|
3205
|
-
}, ...(ngDevMode ? [{ debugName: "cacheStats" }] : []));
|
|
3206
|
-
// Public access to cache state
|
|
3207
|
-
isLoading = (pageCode) => this.sharedService.isLoading(pageCode);
|
|
3208
|
-
getCachedData = (pageCode) => this.sharedService.getCachedPageData(pageCode);
|
|
3209
|
-
/**
|
|
3210
|
-
* Get page data with caching - this is the main method components should use
|
|
3211
|
-
*/
|
|
3212
|
-
getPageData(pageCode, forceRefresh = false) {
|
|
3213
|
-
return this.sharedService.loadAndProcessPageData({ sypg_page_code: pageCode }, { forceRefresh, setTitle: true, setSidebarContext: true, updateLayout: true });
|
|
3214
|
-
}
|
|
3215
|
-
/**
|
|
3216
|
-
* Preload multiple pages for better performance
|
|
3217
|
-
*/
|
|
3218
|
-
preloadPages(pageCodes) {
|
|
3219
|
-
console.log(`🚀 PRELOADING: Starting preload for ${pageCodes.length} pages`);
|
|
3220
|
-
this.sharedService.preloadPageData(pageCodes);
|
|
3221
|
-
}
|
|
3222
|
-
/**
|
|
3223
|
-
* Invalidate specific page cache
|
|
3224
|
-
*/
|
|
3225
|
-
invalidatePage(pageCode) {
|
|
3226
|
-
this.sharedService.invalidateCache(pageCode);
|
|
3227
|
-
}
|
|
3228
|
-
/**
|
|
3229
|
-
* Clear all cache
|
|
3230
|
-
*/
|
|
3231
|
-
clearCache() {
|
|
3232
|
-
this.sharedService.clearAllCache();
|
|
3233
|
-
}
|
|
3234
|
-
/**
|
|
3235
|
-
* Get cache status for debugging
|
|
3236
|
-
*/
|
|
3237
|
-
getCacheStatus() {
|
|
3238
|
-
const cache = this.sharedService.pageDataCache();
|
|
3239
|
-
const loadingStates = this.sharedService.loadingStates();
|
|
3240
|
-
const allPageCodes = new Set([
|
|
3241
|
-
...Object.keys(cache),
|
|
3242
|
-
...Object.keys(loadingStates)
|
|
3243
|
-
]);
|
|
3244
|
-
return Array.from(allPageCodes).map(pageCode => ({
|
|
3245
|
-
pageCode,
|
|
3246
|
-
cached: !!cache[pageCode],
|
|
3247
|
-
expired: cache[pageCode] ? !this.isCacheValid(cache[pageCode]) : false,
|
|
3248
|
-
loading: !!loadingStates[pageCode]
|
|
3249
|
-
}));
|
|
3250
|
-
}
|
|
3251
|
-
/**
|
|
3252
|
-
* Estimate memory usage of cache (rough estimate)
|
|
3253
|
-
*/
|
|
3254
|
-
estimateCacheMemoryUsage() {
|
|
3255
|
-
const cache = this.sharedService.pageDataCache();
|
|
3256
|
-
const jsonString = JSON.stringify(cache);
|
|
3257
|
-
const bytes = new Blob([jsonString]).size;
|
|
3258
|
-
if (bytes < 1024)
|
|
3259
|
-
return `${bytes} B`;
|
|
3260
|
-
if (bytes < 1024 * 1024)
|
|
3261
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3262
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3263
|
-
}
|
|
3264
|
-
/**
|
|
3265
|
-
* Check if cache entry is valid
|
|
3266
|
-
*/
|
|
3267
|
-
isCacheValid(cached) {
|
|
3268
|
-
if (!cached)
|
|
3269
|
-
return false;
|
|
3270
|
-
return Date.now() - cached.timestamp < cached.ttl;
|
|
3271
|
-
}
|
|
3272
|
-
/**
|
|
3273
|
-
* Export cache data for debugging or backup
|
|
3274
|
-
*/
|
|
3275
|
-
exportCacheData() {
|
|
3276
|
-
return JSON.stringify(this.sharedService.pageDataCache(), null, 2);
|
|
3277
|
-
}
|
|
3278
|
-
/**
|
|
3279
|
-
* Get performance metrics
|
|
3280
|
-
*/
|
|
3281
|
-
getPerformanceMetrics() {
|
|
3282
|
-
const stats = this.cacheStats();
|
|
3283
|
-
const loadingStates = this.sharedService.loadingStates();
|
|
3284
|
-
return {
|
|
3285
|
-
totalPages: stats.totalCached,
|
|
3286
|
-
cachedPages: stats.validCached,
|
|
3287
|
-
loadingPages: Object.keys(loadingStates).length,
|
|
3288
|
-
cacheHitRatio: stats.hitRatio,
|
|
3289
|
-
// averageResponseTime could be implemented with timing tracking
|
|
3290
|
-
};
|
|
3291
|
-
}
|
|
3292
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CacheManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3293
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CacheManagerService, providedIn: 'root' });
|
|
3294
|
-
}
|
|
3295
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CacheManagerService, decorators: [{
|
|
3296
|
-
type: Injectable,
|
|
3297
|
-
args: [{
|
|
3298
|
-
providedIn: 'root'
|
|
3299
|
-
}]
|
|
3300
|
-
}] });
|
|
3301
|
-
|
|
3302
|
-
class CideLytFloatingEntityRightsSharingComponent {
|
|
3303
|
-
data;
|
|
3304
|
-
close = new EventEmitter();
|
|
3305
|
-
save = new EventEmitter();
|
|
3306
|
-
fb = inject(FormBuilder);
|
|
3307
|
-
// State signals
|
|
3308
|
-
entities = signal([], ...(ngDevMode ? [{ debugName: "entities" }] : []));
|
|
3309
|
-
selectedEntityId = signal('', ...(ngDevMode ? [{ debugName: "selectedEntityId" }] : []));
|
|
3310
|
-
entitiesLoading = signal(false, ...(ngDevMode ? [{ debugName: "entitiesLoading" }] : []));
|
|
3311
|
-
accessPassLoading = signal(false, ...(ngDevMode ? [{ debugName: "accessPassLoading" }] : []));
|
|
3312
|
-
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
3313
|
-
// Form
|
|
3314
|
-
rightsForm;
|
|
3315
|
-
ngOnInit() {
|
|
3316
|
-
this.initializeForm();
|
|
3317
|
-
this.loadEntities();
|
|
3318
|
-
}
|
|
3319
|
-
initializeForm() {
|
|
3320
|
-
this.rightsForm = this.fb.group({
|
|
3321
|
-
can_view: [false],
|
|
3322
|
-
can_edit: [false],
|
|
3323
|
-
is_owner: [false],
|
|
3324
|
-
syepm_isactive: [true]
|
|
3325
|
-
});
|
|
3326
|
-
}
|
|
3327
|
-
async loadEntities() {
|
|
3328
|
-
this.entitiesLoading.set(true);
|
|
3329
|
-
try {
|
|
3330
|
-
// Mock data for now
|
|
3331
|
-
const mockEntities = [
|
|
3332
|
-
{
|
|
3333
|
-
_id: 'owner-entity',
|
|
3334
|
-
syen_name: 'Owner Entity',
|
|
3335
|
-
syen_entity_code: 'E001',
|
|
3336
|
-
syen_isactive: true
|
|
3337
|
-
},
|
|
3338
|
-
{
|
|
3339
|
-
_id: 'entity-1',
|
|
3340
|
-
syen_name: 'Entity 1',
|
|
3341
|
-
syen_entity_code: 'E002',
|
|
3342
|
-
syen_isactive: true
|
|
3343
|
-
},
|
|
3344
|
-
{
|
|
3345
|
-
_id: 'entity-2',
|
|
3346
|
-
syen_name: 'Entity 2',
|
|
3347
|
-
syen_entity_code: 'E003',
|
|
3348
|
-
syen_isactive: true
|
|
3349
|
-
}
|
|
3350
|
-
];
|
|
3351
|
-
this.entities.set(mockEntities);
|
|
3352
|
-
}
|
|
3353
|
-
catch (error) {
|
|
3354
|
-
console.error('Error loading entities:', error);
|
|
3355
|
-
}
|
|
3356
|
-
finally {
|
|
3357
|
-
this.entitiesLoading.set(false);
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
selectEntity(entityId) {
|
|
3361
|
-
this.selectedEntityId.set(entityId);
|
|
3362
|
-
this.loadAccessPassData();
|
|
3363
|
-
}
|
|
3364
|
-
async loadAccessPassData() {
|
|
3365
|
-
if (!this.selectedEntityId())
|
|
3366
|
-
return;
|
|
3367
|
-
this.accessPassLoading.set(true);
|
|
3368
|
-
try {
|
|
3369
|
-
// Mock data for now
|
|
3370
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
3371
|
-
// Set form values based on mock data
|
|
3372
|
-
this.rightsForm.patchValue({
|
|
3373
|
-
can_view: true,
|
|
3374
|
-
can_edit: false,
|
|
3375
|
-
is_owner: this.selectedEntityId() === 'owner-entity',
|
|
3376
|
-
syepm_isactive: true
|
|
3377
|
-
});
|
|
3378
|
-
}
|
|
3379
|
-
catch (error) {
|
|
3380
|
-
console.error('Error loading access pass data:', error);
|
|
3381
|
-
}
|
|
3382
|
-
finally {
|
|
3383
|
-
this.accessPassLoading.set(false);
|
|
3384
|
-
}
|
|
3385
|
-
}
|
|
3386
|
-
async saveRights() {
|
|
3387
|
-
if (this.rightsForm.invalid)
|
|
3388
|
-
return;
|
|
3389
|
-
this.saving.set(true);
|
|
3390
|
-
try {
|
|
3391
|
-
const formValue = this.rightsForm.value;
|
|
3392
|
-
console.log('Saving rights:', formValue);
|
|
3393
|
-
// TODO: Replace with actual API call
|
|
3394
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
3395
|
-
console.log('Rights saved successfully');
|
|
3396
|
-
this.save.emit(formValue);
|
|
3397
|
-
}
|
|
3398
|
-
catch (error) {
|
|
3399
|
-
console.error('Error saving rights:', error);
|
|
3400
|
-
}
|
|
3401
|
-
finally {
|
|
3402
|
-
this.saving.set(false);
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
onCancel() {
|
|
3406
|
-
this.close.emit();
|
|
3407
|
-
}
|
|
3408
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFloatingEntityRightsSharingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3409
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideLytFloatingEntityRightsSharingComponent, isStandalone: true, selector: "cide-lyt-floating-entity-rights-sharing", inputs: { data: "data" }, outputs: { close: "close", save: "save" }, ngImport: i0, template: `
|
|
3410
|
-
<!-- Entity Rights Sharing Content -->
|
|
3411
|
-
<div class="tw-p-4 tw-overflow-y-auto tw-max-h-[calc(80vh-120px)] tw-scrollbar-thin tw-scrollbar-thumb-gray-300 tw-scrollbar-track-transparent tw-select-none">
|
|
3412
|
-
<!-- Entity Selection -->
|
|
3413
|
-
<div class="tw-mb-4">
|
|
3414
|
-
|
|
3415
|
-
@if (entitiesLoading()) {
|
|
3416
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-py-6">
|
|
3417
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3418
|
-
<span class="tw-ml-3 tw-text-gray-600 tw-text-sm">Loading entities...</span>
|
|
3419
|
-
</div>
|
|
3420
|
-
} @else if (entities().length === 0) {
|
|
3421
|
-
<div class="tw-text-center tw-py-6">
|
|
3422
|
-
<div class="tw-w-10 tw-h-10 tw-bg-gray-100 tw-rounded-xl tw-flex tw-items-center tw-justify-center tw-mx-auto tw-mb-2">
|
|
3423
|
-
<cide-ele-icon class="tw-w-5 tw-h-5 tw-text-gray-400">business</cide-ele-icon>
|
|
3424
|
-
</div>
|
|
3425
|
-
<p class="tw-text-gray-500 tw-text-sm">No entities available</p>
|
|
3426
|
-
</div>
|
|
3427
|
-
} @else {
|
|
3428
|
-
<div class="tw-space-y-2">
|
|
3429
|
-
@defer (when entities().length > 0) {
|
|
3430
|
-
@for (entity of entities(); track entity._id || entity.syen_name) {
|
|
3431
|
-
<div class="tw-bg-white tw-bg-opacity-80 tw-backdrop-blur-sm tw-border tw-border-gray-200 tw-border-opacity-50 tw-rounded-xl tw-p-3 tw-cursor-pointer tw-transition-all tw-duration-200 hover:tw-shadow-md hover:tw-border-blue-300 hover:tw-border-opacity-50 hover:tw-scale-[1.01] tw-group tw-select-none"
|
|
3432
|
-
[class.tw-border-blue-500]="selectedEntityId() === entity._id"
|
|
3433
|
-
[class.tw-bg-blue-50]="selectedEntityId() === entity._id"
|
|
3434
|
-
(click)="selectEntity(entity._id || '')">
|
|
3435
|
-
<div class="tw-flex tw-items-start tw-space-x-3">
|
|
3436
|
-
<div class="tw-w-6 tw-h-6 tw-bg-gray-100 tw-rounded-lg tw-flex tw-items-center tw-justify-center tw-flex-shrink-0">
|
|
3437
|
-
<cide-ele-icon class="tw-text-gray-600">person</cide-ele-icon>
|
|
3438
|
-
</div>
|
|
3439
|
-
<div class="tw-flex-1 tw-min-w-0">
|
|
3440
|
-
<div class="tw-flex tw-items-center tw-space-x-2 tw-mb-1">
|
|
3441
|
-
<h4 class="tw-text-sm tw-font-semibold tw-text-gray-900 tw-truncate">{{ entity.syen_name }}</h4>
|
|
3442
|
-
@if (entity._id === 'owner-entity') {
|
|
3443
|
-
<div class="tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium tw-bg-green-100 tw-text-green-800 tw-space-x-1">
|
|
3444
|
-
<cide-ele-icon>admin_panel_settings</cide-ele-icon>
|
|
3445
|
-
<span>Owner</span>
|
|
3446
|
-
</div>
|
|
3447
|
-
}
|
|
3448
|
-
</div>
|
|
3449
|
-
<div class="tw-flex tw-items-center tw-space-x-3">
|
|
3450
|
-
<div class="tw-flex tw-items-center tw-space-x-1">
|
|
3451
|
-
<cide-ele-icon class="tw-text-gray-400">visibility</cide-ele-icon>
|
|
3452
|
-
<span class="tw-text-xs tw-text-gray-500">View</span>
|
|
3453
|
-
</div>
|
|
3454
|
-
<div class="tw-flex tw-items-center tw-space-x-1">
|
|
3455
|
-
<cide-ele-icon class="tw-text-gray-400">edit</cide-ele-icon>
|
|
3456
|
-
<span class="tw-text-xs tw-text-gray-500">Edit</span>
|
|
3457
|
-
</div>
|
|
3458
|
-
</div>
|
|
3459
|
-
</div>
|
|
3460
|
-
@if (selectedEntityId() === entity._id) {
|
|
3461
|
-
<div class="tw-w-4 tw-h-4 tw-bg-green-500 tw-rounded-full tw-flex tw-items-center tw-justify-center tw-flex-shrink-0">
|
|
3462
|
-
<cide-ele-icon class="tw-text-white">done</cide-ele-icon>
|
|
3463
|
-
</div>
|
|
3464
|
-
}
|
|
3465
|
-
</div>
|
|
3466
|
-
</div>
|
|
3467
|
-
}
|
|
3468
|
-
} @placeholder {
|
|
3469
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-py-4">
|
|
3470
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3471
|
-
<span class="tw-ml-2 tw-text-sm tw-text-gray-600">Loading entities...</span>
|
|
3472
|
-
</div>
|
|
3473
|
-
}
|
|
3474
|
-
</div>
|
|
3475
|
-
}
|
|
3476
|
-
</div>
|
|
3477
|
-
|
|
3478
|
-
<!-- Rights Management -->
|
|
3479
|
-
@if (selectedEntityId()) {
|
|
3480
|
-
<div class="tw-bg-white tw-bg-opacity-80 tw-backdrop-blur-sm tw-border tw-border-gray-200 tw-border-opacity-50 tw-rounded-xl tw-p-3 tw-shadow-sm">
|
|
3481
|
-
<div class="tw-flex tw-items-center tw-space-x-2 tw-mb-3">
|
|
3482
|
-
<cide-ele-icon class="tw-text-green-600">security</cide-ele-icon>
|
|
3483
|
-
<h3 class="tw-text-sm tw-font-semibold tw-text-gray-900">Manage Rights</h3>
|
|
3484
|
-
</div>
|
|
3485
|
-
|
|
3486
|
-
@if (accessPassLoading()) {
|
|
3487
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-min-h-[80px]">
|
|
3488
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3489
|
-
<span class="tw-ml-3 tw-text-gray-600 tw-text-sm">Loading...</span>
|
|
3490
|
-
</div>
|
|
3491
|
-
} @else {
|
|
3492
|
-
@defer (when !accessPassLoading()) {
|
|
3493
|
-
<form [formGroup]="rightsForm" (ngSubmit)="saveRights()">
|
|
3494
|
-
<div class="tw-flex tw-flex-wrap tw-gap-3 tw-min-h-[80px] tw-items-center">
|
|
3495
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3496
|
-
<cide-ele-input
|
|
3497
|
-
type="checkbox"
|
|
3498
|
-
formControlName="can_view"
|
|
3499
|
-
label="View">
|
|
3500
|
-
</cide-ele-input>
|
|
3501
|
-
</div>
|
|
3502
|
-
|
|
3503
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3504
|
-
<cide-ele-input
|
|
3505
|
-
type="checkbox"
|
|
3506
|
-
formControlName="can_edit"
|
|
3507
|
-
label="Edit">
|
|
3508
|
-
</cide-ele-input>
|
|
3509
|
-
</div>
|
|
3510
|
-
|
|
3511
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3512
|
-
<cide-ele-input
|
|
3513
|
-
type="checkbox"
|
|
3514
|
-
formControlName="is_owner"
|
|
3515
|
-
label="Is Owner">
|
|
3516
|
-
</cide-ele-input>
|
|
3517
|
-
</div>
|
|
3518
|
-
|
|
3519
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3520
|
-
<cide-ele-input
|
|
3521
|
-
type="checkbox"
|
|
3522
|
-
formControlName="syepm_isactive"
|
|
3523
|
-
label="Is Active">
|
|
3524
|
-
</cide-ele-input>
|
|
3525
|
-
</div>
|
|
3526
|
-
</div>
|
|
3527
|
-
</form>
|
|
3528
|
-
} @placeholder {
|
|
3529
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-min-h-[80px]">
|
|
3530
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3531
|
-
<span class="tw-ml-2 tw-text-sm tw-text-gray-600">Loading rights...</span>
|
|
3532
|
-
</div>
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
</div>
|
|
3536
|
-
}
|
|
3537
|
-
</div>
|
|
3538
|
-
|
|
3539
|
-
<!-- Footer -->
|
|
3540
|
-
<div class="tw-bg-white tw-bg-opacity-80 tw-backdrop-blur-sm tw-px-4 tw-py-3 tw-border-t tw-border-gray-200 tw-border-opacity-30 tw-flex tw-justify-end tw-space-x-2">
|
|
3541
|
-
<button cideEleButton variant="secondary" size="xs" type="button" (click)="onCancel()">
|
|
3542
|
-
Cancel
|
|
3543
|
-
</button>
|
|
3544
|
-
@if (selectedEntityId()) {
|
|
3545
|
-
<button cideEleButton variant="primary" size="xs" type="button" (click)="saveRights()"
|
|
3546
|
-
[disabled]="rightsForm.invalid || saving()" [loading]="saving()">
|
|
3547
|
-
{{ saving() ? 'Saving...' : 'Save' }}
|
|
3548
|
-
</button>
|
|
3549
|
-
}
|
|
3550
|
-
</div>
|
|
3551
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton]", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideSpinnerComponent, selector: "cide-ele-spinner", inputs: ["size", "type"] }], deferBlockDependencies: [() => [CideIconComponent], () => [i2$1.ɵNgNoValidate, i2$1.NgControlStatus, i2$1.NgControlStatusGroup, i2$1.FormGroupDirective, i2$1.FormControlName, CideInputComponent]] });
|
|
3552
|
-
}
|
|
3553
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFloatingEntityRightsSharingComponent, decorators: [{
|
|
3554
|
-
type: Component,
|
|
3555
|
-
args: [{ selector: 'cide-lyt-floating-entity-rights-sharing', standalone: true, imports: [
|
|
3556
|
-
CommonModule,
|
|
3557
|
-
ReactiveFormsModule,
|
|
3558
|
-
CideEleButtonComponent,
|
|
3559
|
-
CideInputComponent,
|
|
3560
|
-
CideIconComponent,
|
|
3561
|
-
CideSpinnerComponent
|
|
3562
|
-
], template: `
|
|
3563
|
-
<!-- Entity Rights Sharing Content -->
|
|
3564
|
-
<div class="tw-p-4 tw-overflow-y-auto tw-max-h-[calc(80vh-120px)] tw-scrollbar-thin tw-scrollbar-thumb-gray-300 tw-scrollbar-track-transparent tw-select-none">
|
|
3565
|
-
<!-- Entity Selection -->
|
|
3566
|
-
<div class="tw-mb-4">
|
|
3567
|
-
|
|
3568
|
-
@if (entitiesLoading()) {
|
|
3569
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-py-6">
|
|
3570
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3571
|
-
<span class="tw-ml-3 tw-text-gray-600 tw-text-sm">Loading entities...</span>
|
|
3572
|
-
</div>
|
|
3573
|
-
} @else if (entities().length === 0) {
|
|
3574
|
-
<div class="tw-text-center tw-py-6">
|
|
3575
|
-
<div class="tw-w-10 tw-h-10 tw-bg-gray-100 tw-rounded-xl tw-flex tw-items-center tw-justify-center tw-mx-auto tw-mb-2">
|
|
3576
|
-
<cide-ele-icon class="tw-w-5 tw-h-5 tw-text-gray-400">business</cide-ele-icon>
|
|
3577
|
-
</div>
|
|
3578
|
-
<p class="tw-text-gray-500 tw-text-sm">No entities available</p>
|
|
3579
|
-
</div>
|
|
3580
|
-
} @else {
|
|
3581
|
-
<div class="tw-space-y-2">
|
|
3582
|
-
@defer (when entities().length > 0) {
|
|
3583
|
-
@for (entity of entities(); track entity._id || entity.syen_name) {
|
|
3584
|
-
<div class="tw-bg-white tw-bg-opacity-80 tw-backdrop-blur-sm tw-border tw-border-gray-200 tw-border-opacity-50 tw-rounded-xl tw-p-3 tw-cursor-pointer tw-transition-all tw-duration-200 hover:tw-shadow-md hover:tw-border-blue-300 hover:tw-border-opacity-50 hover:tw-scale-[1.01] tw-group tw-select-none"
|
|
3585
|
-
[class.tw-border-blue-500]="selectedEntityId() === entity._id"
|
|
3586
|
-
[class.tw-bg-blue-50]="selectedEntityId() === entity._id"
|
|
3587
|
-
(click)="selectEntity(entity._id || '')">
|
|
3588
|
-
<div class="tw-flex tw-items-start tw-space-x-3">
|
|
3589
|
-
<div class="tw-w-6 tw-h-6 tw-bg-gray-100 tw-rounded-lg tw-flex tw-items-center tw-justify-center tw-flex-shrink-0">
|
|
3590
|
-
<cide-ele-icon class="tw-text-gray-600">person</cide-ele-icon>
|
|
3591
|
-
</div>
|
|
3592
|
-
<div class="tw-flex-1 tw-min-w-0">
|
|
3593
|
-
<div class="tw-flex tw-items-center tw-space-x-2 tw-mb-1">
|
|
3594
|
-
<h4 class="tw-text-sm tw-font-semibold tw-text-gray-900 tw-truncate">{{ entity.syen_name }}</h4>
|
|
3595
|
-
@if (entity._id === 'owner-entity') {
|
|
3596
|
-
<div class="tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium tw-bg-green-100 tw-text-green-800 tw-space-x-1">
|
|
3597
|
-
<cide-ele-icon>admin_panel_settings</cide-ele-icon>
|
|
3598
|
-
<span>Owner</span>
|
|
3599
|
-
</div>
|
|
3600
|
-
}
|
|
3601
|
-
</div>
|
|
3602
|
-
<div class="tw-flex tw-items-center tw-space-x-3">
|
|
3603
|
-
<div class="tw-flex tw-items-center tw-space-x-1">
|
|
3604
|
-
<cide-ele-icon class="tw-text-gray-400">visibility</cide-ele-icon>
|
|
3605
|
-
<span class="tw-text-xs tw-text-gray-500">View</span>
|
|
3606
|
-
</div>
|
|
3607
|
-
<div class="tw-flex tw-items-center tw-space-x-1">
|
|
3608
|
-
<cide-ele-icon class="tw-text-gray-400">edit</cide-ele-icon>
|
|
3609
|
-
<span class="tw-text-xs tw-text-gray-500">Edit</span>
|
|
3610
|
-
</div>
|
|
3611
|
-
</div>
|
|
3612
|
-
</div>
|
|
3613
|
-
@if (selectedEntityId() === entity._id) {
|
|
3614
|
-
<div class="tw-w-4 tw-h-4 tw-bg-green-500 tw-rounded-full tw-flex tw-items-center tw-justify-center tw-flex-shrink-0">
|
|
3615
|
-
<cide-ele-icon class="tw-text-white">done</cide-ele-icon>
|
|
3616
|
-
</div>
|
|
3617
|
-
}
|
|
3618
|
-
</div>
|
|
3619
|
-
</div>
|
|
3620
|
-
}
|
|
3621
|
-
} @placeholder {
|
|
3622
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-py-4">
|
|
3623
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3624
|
-
<span class="tw-ml-2 tw-text-sm tw-text-gray-600">Loading entities...</span>
|
|
3625
|
-
</div>
|
|
3626
|
-
}
|
|
3627
|
-
</div>
|
|
3628
|
-
}
|
|
3629
|
-
</div>
|
|
3630
|
-
|
|
3631
|
-
<!-- Rights Management -->
|
|
3632
|
-
@if (selectedEntityId()) {
|
|
3633
|
-
<div class="tw-bg-white tw-bg-opacity-80 tw-backdrop-blur-sm tw-border tw-border-gray-200 tw-border-opacity-50 tw-rounded-xl tw-p-3 tw-shadow-sm">
|
|
3634
|
-
<div class="tw-flex tw-items-center tw-space-x-2 tw-mb-3">
|
|
3635
|
-
<cide-ele-icon class="tw-text-green-600">security</cide-ele-icon>
|
|
3636
|
-
<h3 class="tw-text-sm tw-font-semibold tw-text-gray-900">Manage Rights</h3>
|
|
3637
|
-
</div>
|
|
3638
|
-
|
|
3639
|
-
@if (accessPassLoading()) {
|
|
3640
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-min-h-[80px]">
|
|
3641
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3642
|
-
<span class="tw-ml-3 tw-text-gray-600 tw-text-sm">Loading...</span>
|
|
3643
|
-
</div>
|
|
3644
|
-
} @else {
|
|
3645
|
-
@defer (when !accessPassLoading()) {
|
|
3646
|
-
<form [formGroup]="rightsForm" (ngSubmit)="saveRights()">
|
|
3647
|
-
<div class="tw-flex tw-flex-wrap tw-gap-3 tw-min-h-[80px] tw-items-center">
|
|
3648
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3649
|
-
<cide-ele-input
|
|
3650
|
-
type="checkbox"
|
|
3651
|
-
formControlName="can_view"
|
|
3652
|
-
label="View">
|
|
3653
|
-
</cide-ele-input>
|
|
3654
|
-
</div>
|
|
3655
|
-
|
|
3656
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3657
|
-
<cide-ele-input
|
|
3658
|
-
type="checkbox"
|
|
3659
|
-
formControlName="can_edit"
|
|
3660
|
-
label="Edit">
|
|
3661
|
-
</cide-ele-input>
|
|
3662
|
-
</div>
|
|
3663
|
-
|
|
3664
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3665
|
-
<cide-ele-input
|
|
3666
|
-
type="checkbox"
|
|
3667
|
-
formControlName="is_owner"
|
|
3668
|
-
label="Is Owner">
|
|
3669
|
-
</cide-ele-input>
|
|
3670
|
-
</div>
|
|
3671
|
-
|
|
3672
|
-
<div class="tw-flex tw-items-center tw-space-x-2">
|
|
3673
|
-
<cide-ele-input
|
|
3674
|
-
type="checkbox"
|
|
3675
|
-
formControlName="syepm_isactive"
|
|
3676
|
-
label="Is Active">
|
|
3677
|
-
</cide-ele-input>
|
|
3678
|
-
</div>
|
|
3679
|
-
</div>
|
|
3680
|
-
</form>
|
|
3681
|
-
} @placeholder {
|
|
3682
|
-
<div class="tw-flex tw-items-center tw-justify-center tw-min-h-[80px]">
|
|
3683
|
-
<cide-ele-spinner size="sm"></cide-ele-spinner>
|
|
3684
|
-
<span class="tw-ml-2 tw-text-sm tw-text-gray-600">Loading rights...</span>
|
|
3685
|
-
</div>
|
|
3686
|
-
}
|
|
3687
|
-
}
|
|
3688
|
-
</div>
|
|
3689
|
-
}
|
|
3690
|
-
</div>
|
|
3691
|
-
|
|
3692
|
-
<!-- Footer -->
|
|
3693
|
-
<div class="tw-bg-white tw-bg-opacity-80 tw-backdrop-blur-sm tw-px-4 tw-py-3 tw-border-t tw-border-gray-200 tw-border-opacity-30 tw-flex tw-justify-end tw-space-x-2">
|
|
3694
|
-
<button cideEleButton variant="secondary" size="xs" type="button" (click)="onCancel()">
|
|
3695
|
-
Cancel
|
|
3696
|
-
</button>
|
|
3697
|
-
@if (selectedEntityId()) {
|
|
3698
|
-
<button cideEleButton variant="primary" size="xs" type="button" (click)="saveRights()"
|
|
3699
|
-
[disabled]="rightsForm.invalid || saving()" [loading]="saving()">
|
|
3700
|
-
{{ saving() ? 'Saving...' : 'Save' }}
|
|
3701
|
-
</button>
|
|
3702
|
-
}
|
|
3703
|
-
</div>
|
|
3704
|
-
` }]
|
|
3705
|
-
}], propDecorators: { data: [{
|
|
3706
|
-
type: Input
|
|
3707
|
-
}], close: [{
|
|
3708
|
-
type: Output
|
|
3709
|
-
}], save: [{
|
|
3710
|
-
type: Output
|
|
3711
|
-
}] } });
|
|
3712
|
-
|
|
3713
|
-
var floatingEntityRightsSharing_component = /*#__PURE__*/Object.freeze({
|
|
3714
|
-
__proto__: null,
|
|
3715
|
-
CideLytFloatingEntityRightsSharingComponent: CideLytFloatingEntityRightsSharingComponent
|
|
3716
|
-
});
|
|
3717
|
-
|
|
3718
|
-
class CideLytFloatingEntityRightsSharingService {
|
|
3719
|
-
containerService = inject(CideEleFloatingContainerService);
|
|
3720
|
-
/**
|
|
3721
|
-
* Show entity rights sharing in floating container
|
|
3722
|
-
*/
|
|
3723
|
-
async show(data) {
|
|
3724
|
-
console.log('🚀 Showing entity rights sharing...');
|
|
3725
|
-
console.log('📋 Current registered components:', this.containerService.getRegisteredComponentIds());
|
|
3726
|
-
// Lazy registration - only register if not already registered
|
|
3727
|
-
if (!this.containerService.isComponentRegistered('entity-rights-sharing')) {
|
|
3728
|
-
console.log('📝 Registering entity rights sharing component...');
|
|
3729
|
-
await this.registerEntityRightsSharingComponent();
|
|
3730
|
-
console.log('📋 Components after registration:', this.containerService.getRegisteredComponentIds());
|
|
3731
|
-
}
|
|
3732
|
-
else {
|
|
3733
|
-
console.log('✅ Entity rights sharing component already registered');
|
|
3734
|
-
}
|
|
3735
|
-
const config = {
|
|
3736
|
-
id: 'entity-rights-sharing-main',
|
|
3737
|
-
title: 'Entity Rights Sharing',
|
|
3738
|
-
icon: 'share',
|
|
3739
|
-
width: '450px',
|
|
3740
|
-
height: '500px',
|
|
3741
|
-
minWidth: '400px',
|
|
3742
|
-
minHeight: '400px',
|
|
3743
|
-
resizable: true,
|
|
3744
|
-
draggable: true,
|
|
3745
|
-
closable: true,
|
|
3746
|
-
minimizable: true,
|
|
3747
|
-
maximizable: true,
|
|
3748
|
-
componentId: 'entity-rights-sharing',
|
|
3749
|
-
componentConfig: {
|
|
3750
|
-
inputs: data ? { data } : undefined
|
|
3751
|
-
}
|
|
3752
|
-
};
|
|
3753
|
-
const containerId = this.containerService.show(config);
|
|
3754
|
-
console.log('✅ Container created with ID:', containerId);
|
|
3755
|
-
return containerId;
|
|
3756
|
-
}
|
|
3757
|
-
/**
|
|
3758
|
-
* Hide entity rights sharing
|
|
3759
|
-
*/
|
|
3760
|
-
hide() {
|
|
3761
|
-
this.containerService.hide('entity-rights-sharing-main');
|
|
3762
|
-
}
|
|
3763
|
-
/**
|
|
3764
|
-
* Register the entity rights sharing component
|
|
3765
|
-
*/
|
|
3766
|
-
async registerEntityRightsSharingComponent() {
|
|
3767
|
-
try {
|
|
3768
|
-
console.log('📦 Importing entity rights sharing component...');
|
|
3769
|
-
const module = await Promise.resolve().then(function () { return floatingEntityRightsSharing_component; });
|
|
3770
|
-
console.log('📦 Module imported:', module);
|
|
3771
|
-
console.log('📦 Component class:', module.CideLytFloatingEntityRightsSharingComponent);
|
|
3772
|
-
if (!module.CideLytFloatingEntityRightsSharingComponent) {
|
|
3773
|
-
throw new Error('Component class not found in module');
|
|
3774
|
-
}
|
|
3775
|
-
this.containerService.registerComponent('entity-rights-sharing', module.CideLytFloatingEntityRightsSharingComponent);
|
|
3776
|
-
console.log('✅ Entity rights sharing component registered successfully');
|
|
3777
|
-
console.log('📋 Available components after registration:', this.containerService.getRegisteredComponentIds());
|
|
3778
|
-
}
|
|
3779
|
-
catch (error) {
|
|
3780
|
-
console.error('❌ Failed to register entity rights sharing component:', error);
|
|
3781
|
-
console.error('❌ Error details:', error);
|
|
3782
|
-
}
|
|
3783
|
-
}
|
|
3784
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFloatingEntityRightsSharingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3785
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFloatingEntityRightsSharingService, providedIn: 'root' });
|
|
3786
|
-
}
|
|
3787
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytFloatingEntityRightsSharingService, decorators: [{
|
|
3788
|
-
type: Injectable,
|
|
3789
|
-
args: [{
|
|
3790
|
-
providedIn: 'root'
|
|
3791
|
-
}]
|
|
3792
|
-
}] });
|
|
3793
|
-
|
|
3794
|
-
/*
|
|
3795
|
-
* Public API Surface of cloud-ide-layout
|
|
3796
|
-
*/
|
|
3797
|
-
// Floating Container (moved to cloud-ide-element)
|
|
3798
|
-
// export * from './lib/components/floating-container';
|
|
3799
|
-
// Layout Components
|
|
3800
|
-
|
|
3801
|
-
/**
|
|
3802
|
-
* Generated bundle index. Do not edit.
|
|
3803
|
-
*/
|
|
3804
|
-
|
|
3805
|
-
export { AppStateHelperService as A, CideLytSharedWrapperComponent as C, ENVIRONMENT_CONFIG as E, CideLytSidebarService as a, CideLytRequestService as b, CideLytSidedrawerService as c, CideLytThemeService as d, CloudIdeLayoutService as e, CloudIdeLayoutComponent as f, CideLytSharedService as g, layoutControlPannelChildRoutes as h, CustomRouteReuseStrategy as i, AppStateService as j, CideLytUserStatusService as k, layoutRoutes as l, CacheManagerService as m, CideLytFileManagerService as n, CideLytFloatingEntityRightsSharingComponent as o, processThemeVariable as p, CideLytFloatingEntityRightsSharingService as q, setCSSVariable as s, themeFactory as t };
|
|
3806
|
-
//# sourceMappingURL=cloud-ide-layout-cloud-ide-layout-B7zJJ9zM.mjs.map
|