@sd-angular/core 19.0.35 → 19.0.36
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/components/query-builder/src/query-builder.model.d.ts +1 -1
- package/components/table/src/components/desktop-cell/desktop-cell.component.d.ts +1 -1
- package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-query-bar.mjs +8 -8
- package/fesm2022/sd-angular-core-components-query-bar.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-query-builder.mjs +2 -2
- package/fesm2022/sd-angular-core-components-query-builder.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-tab-router.mjs +1 -1
- package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-table.mjs +10 -10
- package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date-range.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-datetime.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-inline-text.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-inline-text.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input-color.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-input-color.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input-number.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-select.mjs +1 -1
- package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-auth.mjs +35 -3
- package/fesm2022/sd-angular-core-modules-auth.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-authom.mjs +847 -241
- package/fesm2022/sd-angular-core-modules-authom.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs +2 -2
- package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-cache.mjs +1 -1
- package/fesm2022/sd-angular-core-services-cache.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-confirm.mjs +2 -2
- package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-storage.mjs.map +1 -1
- package/modules/authom/authom.auth-configuration.d.ts +18 -0
- package/modules/authom/authom.configuration.d.ts +158 -2
- package/modules/authom/authom.event.d.ts +44 -0
- package/modules/authom/authom.guard.d.ts +13 -0
- package/modules/authom/authom.interceptor.d.ts +7 -1
- package/modules/authom/authom.module.d.ts +35 -5
- package/modules/authom/authom.return-url.d.ts +37 -0
- package/modules/authom/authom.service.d.ts +109 -15
- package/modules/authom/authom.session-channel.d.ts +26 -0
- package/modules/authom/index.d.ts +19 -9
- package/package.json +9 -3
- package/utilities/extensions/src/string.extension.d.ts +6 -2
- package/utilities/models/src/maybe-async.model.d.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sd-angular-core-modules-authom.mjs","sources":["../../../projects/sd-angular/modules/authom/authom.configuration.ts","../../../projects/sd-angular/modules/authom/authom.service.ts","../../../projects/sd-angular/modules/authom/authom.interceptor.ts","../../../projects/sd-angular/modules/authom/authom.module.ts","../../../projects/sd-angular/modules/authom/index.ts","../../../projects/sd-angular/modules/authom/sd-angular-core-modules-authom.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SdAuthOmTenantConfig {\n domain: string;\n clientId: string;\n\n redirectUri?: string;\n audience?: string;\n organization?: string;\n scope?: string;\n\n secureRoutes?: string[];\n\n silentRefreshRedirectUri?: string;\n refreshThresholdSeconds?: number;\n authorizeTimeoutInSeconds?: number;\n}\n\nexport interface ISdAuthOmConfiguration {\n loadTenantConfig: () => Promise<SdAuthOmTenantConfig>;\n}\n\nexport const SD_AUTHOM_CONFIGURATION = new InjectionToken<ISdAuthOmConfiguration>('sd-authom.configuration');\n","import { Injectable, PLATFORM_ID, computed, inject, signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { SdAuthOmTenantConfig } from './authom.configuration';\nimport { StringUtilities } from '@sdcorejs/utils/fns';\n\nconst STORAGE_KEY_STATE = 'authom_state';\nconst STORAGE_KEY_CODE_VERIFIER = 'authom_code_verifier';\nconst STORAGE_KEY_RETURN_TO = 'authom_return_to';\n\nconst DEFAULT_SCOPE = 'openid profile email';\nconst DEFAULT_REFRESH_THRESHOLD_SECONDS = 30;\nconst DEFAULT_AUTHORIZE_TIMEOUT_SECONDS = 5;\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthOmService {\n private readonly platformId = inject(PLATFORM_ID);\n private readonly isBrowser = isPlatformBrowser(this.platformId);\n\n readonly accessToken = signal<string | null>(null);\n readonly idTokenClaims = signal<Record<string, unknown> | null>(null);\n readonly isAuthenticated = computed(() => this.accessToken() !== null);\n\n config!: SdAuthOmTenantConfig;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\n getAccessToken(): string | null {\n return this.accessToken();\n }\n\n private base64UrlEncode(bytes: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n }\n\n private generateCodeVerifier(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return this.base64UrlEncode(bytes);\n }\n\n private async generateCodeChallenge(verifier: string): Promise<string> {\n return StringUtilities.sha256(verifier);\n }\n\n private buildAuthorizeUrl(params: {\n state: string;\n codeChallenge: string;\n redirectUri: string;\n prompt?: 'none';\n }): string {\n const search = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: params.redirectUri,\n state: params.state,\n code_challenge: params.codeChallenge,\n code_challenge_method: 'S256',\n scope: this.config.scope || DEFAULT_SCOPE,\n });\n if (this.config.audience) search.set('audience', this.config.audience);\n if (this.config.organization) search.set('organization', this.config.organization);\n if (params.prompt) search.set('prompt', params.prompt);\n return `https://${this.config.domain}/authorize?${search.toString()}`;\n }\n\n private getDefaultRedirectUri(): string {\n return this.config.redirectUri || window.location.origin;\n }\n\n private getSilentRedirectUri(): string {\n return this.config.silentRefreshRedirectUri || `${window.location.origin}/silent-authom.html`;\n }\n\n private decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const padded = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const json = atob(padded + '==='.slice((padded.length + 3) % 4));\n return JSON.parse(json);\n } catch {\n return null;\n }\n }\n\n async login(options?: { returnTo?: string }): Promise<void> {\n if (!this.isBrowser) return;\n\n const state = crypto.randomUUID();\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n const returnTo = options?.returnTo || (window.location.pathname + window.location.search);\n\n sessionStorage.setItem(STORAGE_KEY_STATE, state);\n sessionStorage.setItem(STORAGE_KEY_CODE_VERIFIER, codeVerifier);\n sessionStorage.setItem(STORAGE_KEY_RETURN_TO, returnTo);\n\n window.location.href = this.buildAuthorizeUrl({\n state,\n codeChallenge,\n redirectUri: this.getDefaultRedirectUri(),\n });\n }\n\n logout(options?: { returnTo?: string }): void {\n if (!this.isBrowser) return;\n\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n\n const returnTo = options?.returnTo || window.location.origin;\n const search = new URLSearchParams({\n client_id: this.config.clientId,\n returnTo,\n });\n window.location.href = `https://${this.config.domain}/v2/logout?${search.toString()}`;\n }\n\n private async exchangeCode(code: string, codeVerifier: string, redirectUri: string): Promise<boolean> {\n try {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n code_verifier: codeVerifier,\n redirect_uri: redirectUri,\n client_id: this.config.clientId,\n });\n const res = await fetch(`https://${this.config.domain}/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n if (!res.ok) return false;\n const data = await res.json() as { access_token?: string; id_token?: string };\n if (!data.access_token) return false;\n\n this.accessToken.set(data.access_token);\n if (data.id_token) {\n this.idTokenClaims.set(this.decodeJwtPayload(data.id_token));\n }\n this.scheduleRefresh(data.access_token);\n return true;\n } catch {\n return false;\n }\n }\n\n private scheduleRefresh(token: string): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n const claims = this.decodeJwtPayload(token);\n const exp = claims?.['exp'];\n if (typeof exp !== 'number') {\n // @i18n-ignore — dev console warning\n console.warn('[SdAuthOmService] Token không có exp — bỏ qua auto-refresh');\n return;\n }\n const threshold = this.config.refreshThresholdSeconds ?? DEFAULT_REFRESH_THRESHOLD_SECONDS;\n const nowSeconds = Math.floor(Date.now() / 1000);\n const delayMs = Math.max(0, (exp - nowSeconds - threshold) * 1000);\n this.refreshTimer = setTimeout(() => {\n this.silentRefresh();\n }, delayMs);\n }\n\n silentRefresh(): Promise<boolean> {\n if (!this.isBrowser) return Promise.resolve(false);\n\n return new Promise(async (resolve) => {\n const state = crypto.randomUUID();\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n const redirectUri = this.getSilentRedirectUri();\n const timeoutMs = (this.config.authorizeTimeoutInSeconds ?? DEFAULT_AUTHORIZE_TIMEOUT_SECONDS) * 1000;\n\n const iframe = document.createElement('iframe');\n iframe.style.display = 'none';\n\n let done = false;\n const finish = (success: boolean) => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n iframe.remove();\n window.removeEventListener('message', onMessage);\n resolve(success);\n };\n\n const timer = setTimeout(() => finish(false), timeoutMs);\n\n const onMessage = async (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n const data = event.data as { type?: string; code?: string; state?: string } | null;\n if (!data || typeof data !== 'object') return;\n\n if (data.type === 'AUTHOM_SILENT_SUCCESS' && data.code && data.state === state) {\n const ok = await this.exchangeCode(data.code, codeVerifier, redirectUri);\n if (!ok) {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n }\n finish(ok);\n } else if (data.type === 'AUTHOM_SILENT_ERROR') {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n finish(false);\n }\n };\n\n window.addEventListener('message', onMessage);\n\n iframe.src = this.buildAuthorizeUrl({\n state,\n codeChallenge,\n redirectUri,\n prompt: 'none',\n });\n document.body.appendChild(iframe);\n });\n }\n\n async handleCallback(): Promise<boolean> {\n if (!this.isBrowser) return false;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get('code');\n const state = params.get('state');\n if (!code) return false;\n\n const savedState = sessionStorage.getItem(STORAGE_KEY_STATE);\n const codeVerifier = sessionStorage.getItem(STORAGE_KEY_CODE_VERIFIER);\n const returnTo = sessionStorage.getItem(STORAGE_KEY_RETURN_TO);\n sessionStorage.removeItem(STORAGE_KEY_STATE);\n sessionStorage.removeItem(STORAGE_KEY_CODE_VERIFIER);\n\n if (state !== savedState || !codeVerifier) {\n window.history.replaceState({}, '', window.location.pathname);\n sessionStorage.removeItem(STORAGE_KEY_RETURN_TO);\n return false;\n }\n\n const redirectUri = this.getDefaultRedirectUri();\n const ok = await this.exchangeCode(code, codeVerifier, redirectUri);\n\n if (returnTo) {\n sessionStorage.removeItem(STORAGE_KEY_RETURN_TO);\n window.history.replaceState({}, '', returnTo);\n } else {\n window.history.replaceState({}, '', window.location.pathname);\n }\n\n return ok;\n }\n\n async init(config: SdAuthOmTenantConfig): Promise<boolean> {\n this.config = config;\n if (!this.isBrowser) return false;\n\n const hasCode = new URLSearchParams(window.location.search).has('code');\n if (hasCode) {\n const ok = await this.handleCallback();\n if (ok) return true;\n }\n\n return this.silentRefresh();\n }\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { SdAuthOmService } from './authom.service';\n\nconst escapeRegExp = (str: string): string => str.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const matchGlob = (pattern: string, url: string): boolean => {\n const regexBody = pattern.split('*').map(escapeRegExp).join('.*');\n return new RegExp(`^${regexBody}$`).test(url);\n};\n\nexport const SdAuthOmInterceptor: HttpInterceptorFn = (req, next) => {\n const authom = inject(SdAuthOmService);\n const token = authom.getAccessToken();\n const config = authom.config;\n\n if (!token || !config) return next(req);\n\n const isSecure = config.secureRoutes?.some(pattern => matchGlob(pattern, req.url));\n if (!isSecure) return next(req);\n\n const authReq = req.clone({\n headers: req.headers.set('Authorization', `Bearer ${token}`),\n });\n return next(authReq);\n};\n","import {\n ModuleWithProviders,\n NgModule,\n Provider,\n Type,\n EnvironmentProviders,\n makeEnvironmentProviders,\n provideAppInitializer,\n inject,\n APP_INITIALIZER,\n} from '@angular/core';\nimport { ISdAuthOmConfiguration, SD_AUTHOM_CONFIGURATION } from './authom.configuration';\nimport { SdAuthOmService } from './authom.service';\n\ninterface ProvideOptions {\n useClass?: Type<ISdAuthOmConfiguration>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useFactory?: (...args: any[]) => ISdAuthOmConfiguration;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n deps?: any[];\n}\n\nexport function provideSdAuthOm(options: ProvideOptions): EnvironmentProviders {\n const providers: Array<Provider | EnvironmentProviders> = [SdAuthOmService];\n\n if (options.useFactory) {\n providers.push({ provide: SD_AUTHOM_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] });\n } else if (options.useClass) {\n providers.push({ provide: SD_AUTHOM_CONFIGURATION, useClass: options.useClass });\n }\n\n providers.push(\n provideAppInitializer(() => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return configLoader.loadTenantConfig().then((config) => authom.init(config));\n })\n );\n\n return makeEnvironmentProviders(providers);\n}\n\n@NgModule({})\nexport class SdAuthOmModule {\n static forRoot(options: ProvideOptions): ModuleWithProviders<SdAuthOmModule> {\n return {\n ngModule: SdAuthOmModule,\n providers: [\n SdAuthOmService,\n ...(options.useFactory\n ? [{ provide: SD_AUTHOM_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] }]\n : [{ provide: SD_AUTHOM_CONFIGURATION, useClass: options.useClass! }]\n ),\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: () => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return () => configLoader.loadTenantConfig().then((config) => authom.init(config));\n },\n },\n ],\n };\n }\n}\n","/**\n * Module `authom` — OAuth 2.0 + PKCE authentication client cho AuthOM (Auth0-based).\n *\n * Cách dùng (standalone):\n * ```ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { provideSdAuthOm, SdAuthOmInterceptor } from '@sd-angular/core/modules/authom';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([SdAuthOmInterceptor])),\n * provideSdAuthOm({\n * useFactory: () => ({\n * loadTenantConfig: () => Promise.resolve({\n * domain: 'login.example.com',\n * clientId: 'YOUR_CLIENT_ID',\n * audience: 'https://api.example.com',\n * organization: 'org_xxx',\n * scope: 'openid profile email offline_access',\n * secureRoutes: ['https://api.example.com/*'],\n * }),\n * }),\n * }),\n * ],\n * };\n * ```\n *\n * Yêu cầu setup thêm:\n * 1. Copy file `silent-authom.html` từ source module này vào thư mục `public/` của app.\n * 2. App phải chạy trên HTTPS (hoặc localhost) — Web Crypto API yêu cầu secure context.\n */\nexport * from './authom.configuration';\nexport * from './authom.service';\nexport * from './authom.interceptor';\nexport * from './authom.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sd-angular-core-modules-authom.mjs","sources":["../../../projects/sd-angular/modules/authom/authom.configuration.ts","../../../projects/sd-angular/modules/authom/authom.return-url.ts","../../../projects/sd-angular/modules/authom/authom.session-channel.ts","../../../projects/sd-angular/modules/authom/authom.service.ts","../../../projects/sd-angular/modules/authom/authom.interceptor.ts","../../../projects/sd-angular/modules/authom/authom.guard.ts","../../../projects/sd-angular/modules/authom/authom.auth-configuration.ts","../../../projects/sd-angular/modules/authom/authom.module.ts","../../../projects/sd-angular/modules/authom/index.ts","../../../projects/sd-angular/modules/authom/sd-angular-core-modules-authom.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\n/**\n * Hành vi mặc định khi phiên đăng nhập thực sự hết hạn (refresh thất bại VÀ access token\n * hiện tại đã hết hạn).\n *\n * - `'login'` (mặc định): tự khởi động lại code flow. Nếu phiên SSO ở AuthOM còn sống thì user\n * quay lại app mà không thấy màn hình đăng nhập; nếu đã chết thì AuthOM hiện form đăng nhập.\n * - `'none'`: chỉ phát sự kiện `session_expired` và để app tự quyết định (ví dụ chờ một request\n * thật trả về 401 rồi mới hiện toast).\n */\nexport type SdAuthOmSessionExpiredAction = 'login' | 'none';\n\n/**\n * Tham số `prompt` gửi kèm lần khởi động code flow ngay sau khi đăng xuất.\n *\n * AuthOM không có `end_session_endpoint` nên cookie SSO của nó KHÔNG bị xoá khi app đăng xuất.\n * Nếu không ép `prompt=login`, lần `/authorize` kế tiếp sẽ được cookie đó trả lời ngay và user\n * bị đăng nhập lại im lặng đúng vào tài khoản vừa thoát.\n *\n * - `'login'` (mặc định): luôn bắt nhập lại thông tin đăng nhập.\n * - `'none'`: cho phép SSO im lặng (chỉ dùng khi IdP có cơ chế kết thúc phiên riêng).\n * - `false`: không khởi động lại code flow; điều hướng về `postLogoutRedirectUri` (hoặc origin).\n */\nexport type SdAuthOmLogoutPrompt = 'login' | 'none' | false;\n\n/**\n * Cấu hình tenant cho một client AuthOM (hoặc bất kỳ IdP OIDC nào hỗ trợ Authorization Code + PKCE).\n *\n * Mọi endpoint đều được lấy từ discovery document (`<issuer>/.well-known/openid-configuration`);\n * module không suy đoán đường dẫn endpoint từ hostname.\n */\nexport interface SdAuthOmTenantConfig {\n /**\n * Issuer đầy đủ, đúng như trong discovery document — kể cả path và dấu `/` cuối.\n *\n * Ví dụ AuthOM thật: `https://api-qc.onemount.dev/auth-om/iam/v1/mag-qc/`.\n *\n * Bắt buộc, trừ khi dùng `domain` (dạng cũ, chỉ hợp với IdP có issuer là host trần).\n */\n issuer?: string;\n\n /**\n * Dạng cũ chỉ-hostname (tương thích ngược). Được quy đổi thành `https://<domain>/`.\n *\n * KHÔNG dùng cho AuthOM: issuer của AuthOM có path (`/auth-om/iam/v1/<realm>/`) nên\n * hostname trần sẽ trỏ sai discovery document. Ưu tiên `issuer`.\n *\n * @deprecated Dùng `issuer`.\n */\n domain?: string;\n\n /** Client id của application đăng ký trên AuthOM. */\n clientId: string;\n\n /**\n * Redirect URI đã đăng ký với client. Mặc định `window.location.origin`.\n *\n * Các portal hiện tại đăng ký `<origin>/callback` — khi dùng giá trị đó thì app phải có route\n * `/callback` (hoặc để nó rơi vào redirect mặc định), vì `init()` chạy trong APP_INITIALIZER,\n * tức trước điều hướng đầu tiên của Router.\n */\n redirectUri?: string;\n\n /** Nơi điều hướng tới sau khi đăng xuất khi `logoutPrompt === false`. Mặc định origin. */\n postLogoutRedirectUri?: string;\n\n /**\n * Giá trị `audience` mà resource server dùng để giới hạn phạm vi access token.\n *\n * Được gửi như custom query param nên đi kèm CẢ `/authorize`, CẢ lần đổi authorization code\n * lấy token, CẢ các lần refresh_token. Bỏ trống thì không gửi (không gửi chuỗi rỗng).\n */\n audience?: string;\n\n /** Auth0 organization id (`org_...`). Chỉ gắn vào `/authorize`. AuthOM không dùng. */\n organization?: string;\n\n /**\n * Scope yêu cầu. Mặc định `'openid profile email offline_access'`.\n *\n * `offline_access` là bắt buộc để token endpoint phát hành `refresh_token`; thiếu nó thì\n * auto-refresh phải rơi về silent refresh bằng iframe ẩn.\n */\n scope?: string;\n\n /**\n * Danh sách URL được đính `Authorization: Bearer <token>`.\n *\n * Mỗi phần tử là một trong hai dạng:\n * - Có `*` → khớp theo glob trên toàn bộ URL (`'https://api.example.com/*'`).\n * - Không có `*` → khớp theo tiền tố, không phân biệt hoa thường (`'https://api.example.com'`).\n *\n * Bỏ trống thì interceptor KHÔNG đính token vào bất kỳ request nào. Đừng dùng `['*']` — token\n * sẽ rò sang mọi host bên thứ ba (analytics, CDN...).\n */\n secureRoutes?: string[];\n\n /** Ép HTTPS khi kiểm tra discovery document. Mặc định `true`, trừ khi app chạy trên localhost. */\n requireHttps?: boolean | 'remoteOnly';\n\n /**\n * Yêu cầu mọi endpoint trong discovery document phải bắt đầu bằng issuer. Mặc định `true`.\n *\n * Tenant AuthOM thật thoả điều kiện này. Chỉ đặt `false` cho IdP công bố endpoint nằm ngoài\n * cây issuer, và hiểu rằng làm vậy là bỏ một phép kiểm tra an toàn.\n */\n strictDiscoveryDocumentValidation?: boolean;\n\n /** In log chi tiết của client OIDC ra console. Mặc định `false`. */\n showDebugInformation?: boolean;\n\n /** Dung sai lệch đồng hồ giữa client và IdP, tính bằng giây. */\n clockSkewInSec?: number;\n\n /**\n * Thời điểm refresh chủ động, tính theo tỷ lệ vòng đời token. Mặc định `0.75`\n * (refresh khi đã dùng hết 75% thời gian sống của access token).\n */\n timeoutFactor?: number;\n\n /**\n * Coi access token là hết hạn sớm hơn `exp` bấy nhiêu giây.\n *\n * Tên giữ nguyên từ bản trước của module. Ảnh hưởng tới cả `hasValidAccessToken()` lẫn thời\n * điểm interceptor quyết định phải refresh trước khi gửi request.\n */\n refreshThresholdSeconds?: number;\n\n /**\n * Ép dùng silent refresh bằng iframe ẩn thay vì refresh_token grant.\n *\n * Chỉ cần khi IdP không phát hành refresh_token. Khi bật thì phải có `silentRefreshRedirectUri`\n * và file tĩnh tương ứng trong `public/`.\n */\n useSilentRefresh?: boolean;\n\n /**\n * URL trang tĩnh nhận redirect của silent refresh. Mặc định `<origin>/silent-authom.html`.\n * Mẫu file nằm cạnh source module này (`silent-authom.html`).\n */\n silentRefreshRedirectUri?: string;\n\n /** Timeout của silent refresh, tính bằng giây. Mặc định `20`. */\n silentRefreshTimeoutInSeconds?: number;\n\n /**\n * Tên cũ của `silentRefreshTimeoutInSeconds` (tương thích ngược).\n *\n * @deprecated Dùng `silentRefreshTimeoutInSeconds`.\n */\n authorizeTimeoutInSeconds?: number;\n\n /** Hành vi khi phiên hết hạn thật. Mặc định `'login'`. */\n onSessionExpired?: SdAuthOmSessionExpiredAction;\n\n /** `prompt` gửi kèm lần code flow ngay sau đăng xuất. Mặc định `'login'`. */\n logoutPrompt?: SdAuthOmLogoutPrompt;\n\n /**\n * Gọi `end_session_endpoint` của IdP khi đăng xuất. Mặc định `false`.\n *\n * AuthOM KHÔNG công bố `end_session_endpoint` và không hỗ trợ thu hồi token, nên bật cờ này\n * với AuthOM là vô nghĩa. Chỉ dành cho IdP OIDC khác có RP-initiated logout thật.\n */\n endSessionOnLogout?: boolean;\n\n /**\n * Phát tín hiệu đăng xuất sang các tab cùng origin qua `BroadcastChannel`. Mặc định `true`.\n *\n * Không có cờ này, đăng xuất ở một tab chỉ dọn đúng tab đó; các tab còn lại vẫn dùng được cho\n * tới khi access token của chính chúng hết hạn.\n */\n crossTabLogout?: boolean;\n\n /** Tên kênh `BroadcastChannel`. Mặc định `sd-authom-session:<clientId>`. */\n sessionChannelName?: string;\n\n /**\n * Basename (KHÔNG kèm `.html`) của trang lỗi tĩnh trong `public/`, dùng khi bootstrap thất bại\n * (không tải được discovery document, IdP sập, cấu hình sai).\n *\n * Cùng quy ước với `authErrorUrl` của module keycloak: cho phép kèm `/` đầu hoặc đuôi `.html`,\n * và cho phép đặt sâu hơn (`'assets/auth-error'`). Bỏ trống thì không điều hướng, chỉ phát sự\n * kiện `auth_error` và app tự xử lý.\n */\n authErrorUrl?: string;\n\n /** Query param tuỳ ý gửi kèm cả `/authorize` lẫn các request tới token endpoint. */\n customQueryParams?: Record<string, string>;\n}\n\nexport interface ISdAuthOmConfiguration {\n loadTenantConfig: () => Promise<SdAuthOmTenantConfig>;\n}\n\nexport const SD_AUTHOM_CONFIGURATION = new InjectionToken<ISdAuthOmConfiguration>('sd-authom.configuration');\n\n/** Scope mặc định. `offline_access` giữ cho token endpoint phát hành refresh_token. */\nexport const SD_AUTHOM_DEFAULT_SCOPE = 'openid profile email offline_access';\n\n/** Tỷ lệ vòng đời token mà tại đó refresh chủ động được kích hoạt. */\nexport const SD_AUTHOM_DEFAULT_TIMEOUT_FACTOR = 0.75;\n\n/** Timeout mặc định của silent refresh bằng iframe, tính bằng giây. */\nexport const SD_AUTHOM_DEFAULT_SILENT_REFRESH_TIMEOUT_SECONDS = 20;\n","/**\n * Giữ deep link sống sót qua vòng redirect đăng nhập.\n *\n * AuthOM chỉ chấp nhận một `redirect_uri` đã đăng ký (các portal hiện dùng `/callback`), nên một\n * user mở thẳng link sâu mà chưa có token sẽ mất đích đến: guard gọi code flow, cookie SSO của\n * AuthOM trả lời ngay không cần hỏi, và trình duyệt quay về `/callback` rồi bị route mặc định đẩy\n * sang trang chủ.\n *\n * Đích đến đi kèm tham số OIDC `state` (IdP round-trip nguyên vẹn) chứ không nằm trong storage:\n * như vậy nó gắn với đúng một lần đăng nhập, không thể cũ đi và không rò sang điều hướng khác.\n */\n\n/** Đường dẫn callback mặc định — khôi phục nó chỉ khiến user bị đẩy về trang chủ lần nữa. */\nconst DEFAULT_CALLBACK_PATH = '/callback';\n\n/**\n * Mã hoá trang mà user thực sự muốn vào, để gửi kèm `initCodeFlow`.\n *\n * Trả về `undefined` khi không có gì đáng khôi phục — lúc đó caller khởi động đăng nhập bình thường.\n *\n * Giá trị trả về là chuỗi THÔ, cố ý không percent-encode: client OIDC đã tự\n * `encodeURIComponent(additionalState)` trước khi ghép vào `state`, và chuỗi giải mã của nó chỉ bóc\n * đúng một lớp. Encode thêm ở đây làm giá trị quay về dạng double-encoded (`%252F...`) và\n * `resolveReturnUrl` sẽ loại bỏ nó.\n */\nexport function captureReturnUrl(requestedUrl: string | undefined | null, callbackPath = DEFAULT_CALLBACK_PATH): string | undefined {\n const url = requestedUrl?.trim();\n if (!url || !isRestorableAppUrl(url, callbackPath)) {\n return undefined;\n }\n return url;\n}\n\n/**\n * Đọc lại giá trị mà `captureReturnUrl` đã gửi đi; trả `null` khi `state` rỗng hoặc không khôi phục được.\n *\n * Một lần `decodeURIComponent` để bóc lớp mã hoá mà client OIDC áp lên phần state của app; lớp ngoài\n * bọc cả tham số đã được chính nó gỡ trước khi giá trị tới đây. Chuỗi không mã hoá sẽ giải mã ra\n * chính nó, nên hàm vẫn đúng trong cả hai trường hợp.\n */\nexport function resolveReturnUrl(state: string | undefined | null, callbackPath = DEFAULT_CALLBACK_PATH): string | null {\n const raw = state?.trim();\n if (!raw) {\n return null;\n }\n\n let url: string;\n try {\n url = decodeURIComponent(raw);\n } catch {\n // why: percent-encoding hỏng — bị can thiệp trên đường truyền hoặc IdP làm méo.\n // Bỏ hẳn thay vì đoán đích đến.\n return null;\n }\n\n return isRestorableAppUrl(url, callbackPath) ? url : null;\n}\n\n/**\n * `state` đi vòng qua IdP nên kẻ tấn công có thể tác động; chỉ chấp nhận path nằm trong chính app này.\n *\n * URL tuyệt đối hoặc protocol-relative (`//evil.example`, hoặc `/\\evil.example` — trình duyệt tự\n * chuẩn hoá dấu gạch ngược) sẽ biến vòng đăng nhập thành open redirect.\n */\nexport function isRestorableAppUrl(url: string, callbackPath = DEFAULT_CALLBACK_PATH): boolean {\n if (!url.startsWith('/') || url.startsWith('//') || url.startsWith('/\\\\')) {\n return false;\n }\n // why: ký tự điều khiển (CR/LF/NUL/DEL) trong path mở đường cho header hoặc log injection.\n if ([...url].some(character => character.charCodeAt(0) < 0x20 || character.charCodeAt(0) === 0x7f)) {\n return false;\n }\n return url.split(/[?#]/)[0] !== callbackPath;\n}\n","/**\n * Đồng bộ đăng xuất giữa các tab cùng origin.\n *\n * Phiên AuthOM sống trong bộ nhớ của client OIDC, có `sessionStorage` làm nền — mà `sessionStorage`\n * bị trình duyệt cô lập theo tab. Không có server-side session để thu hồi (AuthOM không công bố\n * `end_session_endpoint`) và cũng không có cơ chế kiểm tra liveness xuyên tab. Hệ quả: đăng xuất ở\n * một tab để các tab còn lại dùng được bình thường cho tới khi access token của chính chúng hết hạn.\n *\n * `BroadcastChannel` không bao giờ gửi message ngược về đối tượng đã post, nên tab kích hoạt đăng\n * xuất không tự xử lý lại sự kiện của chính nó.\n */\n\nconst LOGOUT_MESSAGE_TYPE = 'sd-authom:logout';\n\ninterface SdAuthOmChannelMessage {\n type: typeof LOGOUT_MESSAGE_TYPE;\n}\n\n/**\n * Bọc `BroadcastChannel` cho tín hiệu đăng xuất, và tự vô hiệu hoá khi môi trường không hỗ trợ\n * (SSR, trình duyệt cũ, `BroadcastChannel` bị chặn).\n */\nexport class SdAuthOmSessionChannel {\n readonly #channel: BroadcastChannel | null;\n\n constructor(name: string) {\n this.#channel = SdAuthOmSessionChannel.#create(name);\n }\n\n /** `true` khi môi trường thực sự hỗ trợ kênh; `false` thì mọi thao tác là no-op. */\n get enabled(): boolean {\n return this.#channel !== null;\n }\n\n /** Báo cho mọi tab khác rằng phiên vừa kết thúc. */\n notifyLogout(): void {\n const message: SdAuthOmChannelMessage = { type: LOGOUT_MESSAGE_TYPE };\n this.#channel?.postMessage(message);\n }\n\n /** Đăng ký callback chạy khi một tab khác báo đã đăng xuất. */\n onLogout(handler: () => void): void {\n if (!this.#channel) {\n return;\n }\n this.#channel.onmessage = (event: MessageEvent) => {\n if ((event.data as SdAuthOmChannelMessage | null)?.type === LOGOUT_MESSAGE_TYPE) {\n handler();\n }\n };\n }\n\n close(): void {\n this.#channel?.close();\n }\n\n static #create(name: string): BroadcastChannel | null {\n if (typeof BroadcastChannel === 'undefined') {\n return null;\n }\n try {\n return new BroadcastChannel(name);\n } catch {\n // why: một số trình duyệt ném khi site data bị chặn. Đăng xuất xuyên tab là tính năng\n // phụ trợ, không được phép làm hỏng bootstrap.\n return null;\n }\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { DestroyRef, Injectable, PLATFORM_ID, computed, inject, signal } from '@angular/core';\nimport { AuthConfig, OAuthErrorEvent, OAuthEvent, OAuthService } from 'angular-oauth2-oidc';\nimport { Observable, Subject, Subscription } from 'rxjs';\nimport type { SdAuthInfo } from '@sd-angular/core/modules/auth';\nimport type { SdLayoutUserInfo } from '@sd-angular/core/modules/layout';\nimport {\n SD_AUTHOM_DEFAULT_SCOPE,\n SD_AUTHOM_DEFAULT_SILENT_REFRESH_TIMEOUT_SECONDS,\n SD_AUTHOM_DEFAULT_TIMEOUT_FACTOR,\n SdAuthOmTenantConfig,\n} from './authom.configuration';\nimport { SdAuthOmEvent, SdAuthOmEventType } from './authom.event';\nimport { captureReturnUrl, resolveReturnUrl } from './authom.return-url';\nimport { SdAuthOmSessionChannel } from './authom.session-channel';\n\n/** Sự kiện của client OIDC được coi là lỗi xác thực ngoài luồng refresh. */\nconst AUTH_ERROR_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'discovery_document_load_error',\n 'discovery_document_validation_error',\n 'jwks_load_error',\n 'invalid_nonce_in_state',\n 'code_error',\n 'token_error',\n 'token_validation_error',\n]);\n\n/** Sự kiện của client OIDC báo một lần gia hạn token thất bại. */\nconst REFRESH_ERROR_EVENT_TYPES: ReadonlySet<string> = new Set(['token_refresh_error', 'silent_refresh_error', 'silent_refresh_timeout']);\n\n/**\n * Client xác thực AuthOM: Authorization Code + PKCE trên discovery document của IdP.\n *\n * Service sở hữu toàn bộ vòng đời phiên — bootstrap, khôi phục deep link, gia hạn token chủ động,\n * phát hiện hết phiên, đăng xuất xuyên tab — nên app chỉ cần khai báo cấu hình tenant.\n */\n@Injectable({ providedIn: 'root' })\nexport class SdAuthOmService {\n readonly #platformId = inject(PLATFORM_ID);\n readonly #isBrowser = isPlatformBrowser(this.#platformId);\n readonly #destroyRef = inject(DestroyRef);\n\n /**\n * Optional để một app chỉ đăng ký `SdAuthOmInterceptor` mà quên `provideSdAuthOm()` thì bị hỏng\n * đúng luồng đăng nhập, chứ không ném NullInjectorError trên mọi HTTP request.\n */\n readonly #oauth = inject(OAuthService, { optional: true });\n\n /** Cấu hình tenant đang dùng. `undefined` cho tới khi `init()` chạy xong. */\n config?: SdAuthOmTenantConfig;\n\n readonly accessToken = signal<string | null>(null);\n readonly idTokenClaims = signal<Record<string, unknown> | null>(null);\n readonly isAuthenticated = computed(() => this.accessToken() !== null);\n\n /** `true` khi gia hạn đã thất bại VÀ access token cuối cùng cũng đã hết hạn. */\n readonly sessionExpired = signal(false);\n\n /** Sự kiện `auth_error` gần nhất, hoặc `null` khi lần đăng nhập gần nhất sạch. */\n readonly lastAuthError = signal<SdAuthOmEvent | null>(null);\n\n readonly #events = new Subject<SdAuthOmEvent>();\n\n /** Luồng sự kiện xác thực. Xem `SdAuthOmEventType`. */\n readonly events$: Observable<SdAuthOmEvent> = this.#events.asObservable();\n\n #channel: SdAuthOmSessionChannel | null = null;\n #eventsSubscription: Subscription | null = null;\n #expiryCheckTimer: ReturnType<typeof setTimeout> | null = null;\n #inFlightRefresh: Promise<boolean> | null = null;\n #initialized = false;\n\n constructor() {\n this.#destroyRef.onDestroy(() => this.#teardown());\n }\n\n // ---------------------------------------------------------------------------------------------\n // Bootstrap\n // ---------------------------------------------------------------------------------------------\n\n /**\n * Cấu hình client, tải discovery document, xử lý callback nếu có, rồi bật gia hạn chủ động.\n *\n * Chạy trong APP_INITIALIZER (xem `provideSdAuthOm`), tức trước điều hướng đầu tiên của Router —\n * nhờ vậy guard của route đầu tiên đã thấy trạng thái phiên đúng.\n *\n * @returns `true` khi kết thúc với một access token hợp lệ.\n */\n async init(config: SdAuthOmTenantConfig): Promise<boolean> {\n this.config = config;\n if (!this.#isBrowser || !this.#oauth) {\n return false;\n }\n\n const authErrorHref = config.authErrorUrl ? this.#toPublicHtmlUrl(config.authErrorUrl) : null;\n // why: đang đứng sẵn ở trang lỗi thì KHÔNG init lại, tránh vòng lặp redirect khi IdP sập lâu.\n if (authErrorHref && window.location.pathname === new URL(authErrorHref).pathname) {\n return false;\n }\n\n this.#oauth.configure(this.buildAuthConfig(config));\n if (!this.#initialized) {\n this.#initialized = true;\n this.#subscribeOAuthEvents();\n }\n\n try {\n await this.#oauth.loadDiscoveryDocumentAndTryLogin();\n } catch (error) {\n this.#emitAuthError('discovery_document_load_error', error);\n if (authErrorHref) {\n this.navigate(authErrorHref);\n }\n return false;\n }\n\n this.#restoreReturnUrl();\n this.#syncFromOAuth();\n this.#emit('ready');\n\n this.#oauth.setupAutomaticSilentRefresh({}, 'access_token');\n\n if (config.crossTabLogout !== false) {\n this.#listenCrossTabLogout(config);\n }\n\n return this.isAuthenticated();\n }\n\n /**\n * Quy đổi cấu hình tenant sang `AuthConfig` của client OIDC.\n *\n * Public để app kiểm tra được cấu hình thực tế trong test mà không phải chạy cả `init()`.\n */\n buildAuthConfig(config: SdAuthOmTenantConfig): AuthConfig {\n const audience = config.audience?.trim();\n const silentRefreshSeconds =\n config.silentRefreshTimeoutInSeconds ?? config.authorizeTimeoutInSeconds ?? SD_AUTHOM_DEFAULT_SILENT_REFRESH_TIMEOUT_SECONDS;\n\n const authConfig: AuthConfig = {\n issuer: this.#resolveIssuer(config),\n clientId: config.clientId,\n redirectUri: config.redirectUri || window.location.origin,\n postLogoutRedirectUri: config.postLogoutRedirectUri || window.location.origin,\n responseType: 'code',\n scope: config.scope || SD_AUTHOM_DEFAULT_SCOPE,\n // why: giữ bật (mặc định của client OIDC). Discovery document thật của AuthOM QC đặt mọi\n // endpoint trong cây issuer nên phép kiểm tra này đi qua; chỉ tenant nào công bố endpoint\n // ngoài cây issuer mới cần tắt.\n strictDiscoveryDocumentValidation: config.strictDiscoveryDocumentValidation ?? true,\n // why: 'remoteOnly' vẫn ép HTTPS với IdP thật nhưng cho phép chạy dev trên localhost.\n requireHttps: config.requireHttps ?? 'remoteOnly',\n showDebugInformation: config.showDebugInformation ?? false,\n timeoutFactor: config.timeoutFactor ?? SD_AUTHOM_DEFAULT_TIMEOUT_FACTOR,\n silentRefreshTimeout: silentRefreshSeconds * 1000,\n useSilentRefresh: config.useSilentRefresh ?? false,\n clearHashAfterLogin: true,\n };\n\n if (config.refreshThresholdSeconds !== undefined) {\n // why: client OIDC đo ngưỡng refresh bằng \"coi token hết hạn sớm N giây\", không có\n // tham số \"refresh trước exp N giây\" riêng. Hai cách diễn đạt cho cùng một hành vi.\n authConfig.decreaseExpirationBySec = config.refreshThresholdSeconds;\n }\n if (config.clockSkewInSec !== undefined) {\n authConfig.clockSkewInSec = config.clockSkewInSec;\n }\n if (config.useSilentRefresh) {\n authConfig.silentRefreshRedirectUri = config.silentRefreshRedirectUri || `${window.location.origin}/silent-authom.html`;\n }\n\n // why: customQueryParams được gắn vào CẢ /authorize, CẢ lần đổi code lấy token, CẢ refresh —\n // đó là đường duy nhất để `audience` bắt buộc của AuthOM tới được token endpoint.\n const customQueryParams: Record<string, string> = { ...(config.customQueryParams ?? {}) };\n if (audience) {\n customQueryParams['audience'] = audience;\n }\n if (Object.keys(customQueryParams).length > 0) {\n authConfig.customQueryParams = customQueryParams;\n }\n\n return authConfig;\n }\n\n // ---------------------------------------------------------------------------------------------\n // Đăng nhập / đăng xuất\n // ---------------------------------------------------------------------------------------------\n\n /**\n * Khởi động Authorization Code flow.\n *\n * `returnTo` mặc định là URL hiện tại, đi kèm tham số OIDC `state` để deep link sống sót qua\n * vòng redirect (xem `authom.return-url.ts`).\n */\n async login(options?: { returnTo?: string; prompt?: 'login' | 'none' | 'consent' }): Promise<void> {\n if (!this.#isBrowser || !this.#oauth) {\n return;\n }\n const requested = options?.returnTo ?? `${window.location.pathname}${window.location.search}${window.location.hash}`;\n this.#oauth.initCodeFlow(captureReturnUrl(requested), this.#authorizeParams(options?.prompt));\n }\n\n /**\n * Kết thúc phiên cục bộ rồi (mặc định) bắt đăng nhập lại với `prompt=login`.\n *\n * AuthOM không công bố `end_session_endpoint` và không hỗ trợ thu hồi token, nên đăng xuất\n * KHÔNG gọi endpoint nào của IdP trừ khi `endSessionOnLogout` được bật rõ ràng.\n */\n logout(options?: { returnTo?: string; prompt?: 'login' | 'none' | false }): void {\n if (!this.#isBrowser || !this.#oauth) {\n return;\n }\n\n const config = this.config;\n const prompt = options?.prompt ?? config?.logoutPrompt ?? 'login';\n\n this.#clearExpiryCheck();\n this.#inFlightRefresh = null;\n\n if (config?.endSessionOnLogout) {\n this.#resetSessionState();\n this.#emit('logout');\n this.#notifyOtherTabs(config);\n // logOut() không tham số sẽ redirect tới end_session_endpoint khi IdP có công bố.\n this.#oauth.logOut();\n return;\n }\n\n // why: logOut(true) chỉ dọn phiên cục bộ, không thử redirect tới logout URL không tồn tại.\n this.#oauth.logOut(true);\n this.#resetSessionState();\n this.#emit('logout');\n this.#notifyOtherTabs(config);\n\n if (prompt === false) {\n const returnTo = options?.returnTo ?? config?.postLogoutRedirectUri ?? window.location.origin;\n this.navigate(returnTo);\n return;\n }\n\n // why: cookie SSO của AuthOM không bị xoá cùng phiên cục bộ. Không ép prompt thì lần\n // /authorize kế tiếp được cookie đó trả lời ngay và user bị đăng nhập lại im lặng.\n this.#oauth.initCodeFlow(undefined, this.#authorizeParams(prompt));\n }\n\n // ---------------------------------------------------------------------------------------------\n // Gia hạn token\n // ---------------------------------------------------------------------------------------------\n\n /**\n * Gia hạn access token ngay lập tức.\n *\n * Dùng refresh_token grant khi IdP đã phát hành refresh token, ngược lại rơi về silent refresh\n * bằng iframe ẩn. Các lời gọi chồng nhau dùng chung một request đang bay.\n *\n * @returns `true` khi kết thúc với access token hợp lệ.\n */\n refresh(): Promise<boolean> {\n if (!this.#isBrowser || !this.#oauth) {\n return Promise.resolve(false);\n }\n if (this.#inFlightRefresh) {\n return this.#inFlightRefresh;\n }\n const pending = this.#runRefresh().finally(() => {\n this.#inFlightRefresh = null;\n });\n this.#inFlightRefresh = pending;\n return pending;\n }\n\n /**\n * Tên cũ của `refresh()`, giữ để tương thích ngược.\n *\n * @deprecated Dùng `refresh()`.\n */\n silentRefresh(): Promise<boolean> {\n return this.refresh();\n }\n\n /**\n * Ghi nhận một `401` từ route đã được bảo vệ.\n *\n * Đây là tín hiệu hết phiên đáng tin nhất: một lần refresh hỏng vẫn có thể xảy ra khi token cũ\n * còn hạn, nhưng `401` nghĩa là resource server đã từ chối đúng token đang dùng.\n *\n * - Token đã hết hạn ⇒ phiên chết, phát `session_expired` (và đăng nhập lại nếu cấu hình cho phép).\n * - Token vẫn còn hạn ⇒ server từ chối vì lý do khác (sai `audience`, client bị thu hồi...);\n * phát `auth_error` với `reason = 'unauthorized'` để app tự hiển thị thông báo.\n *\n * `SdAuthOmInterceptor` gọi sẵn; chỉ cần gọi tay khi app dùng HTTP client riêng.\n */\n handleUnauthorized(): void {\n if (!this.#oauth) {\n return;\n }\n if (this.#oauth.hasValidAccessToken()) {\n this.#emitAuthError('unauthorized');\n return;\n }\n this.#markSessionExpired();\n }\n\n /** `true` khi có thể thử gia hạn mà không cần user tương tác. */\n canRefresh(): boolean {\n if (!this.#oauth) {\n return false;\n }\n return Boolean(this.#oauth.getRefreshToken()) || this.config?.useSilentRefresh === true;\n }\n\n // ---------------------------------------------------------------------------------------------\n // Callback\n // ---------------------------------------------------------------------------------------------\n\n /**\n * Xử lý callback `?code=` thủ công.\n *\n * `init()` đã tự làm việc này; chỉ cần gọi tay khi app tách riêng một route `/callback` chạy\n * ngoài APP_INITIALIZER.\n */\n async handleCallback(): Promise<boolean> {\n if (!this.#isBrowser || !this.#oauth) {\n return false;\n }\n try {\n await this.#oauth.tryLogin();\n } catch (error) {\n this.#emitAuthError('code_error', error);\n return false;\n }\n this.#restoreReturnUrl();\n this.#syncFromOAuth();\n return this.isAuthenticated();\n }\n\n // ---------------------------------------------------------------------------------------------\n // Đọc trạng thái\n // ---------------------------------------------------------------------------------------------\n\n getAccessToken(): string | null {\n return this.accessToken();\n }\n\n /** `true` khi không còn access token dùng được (chưa đăng nhập, hoặc đã quá hạn). */\n isAccessTokenExpired(): boolean {\n return !this.#oauth?.hasValidAccessToken();\n }\n\n /** URL có nằm trong `secureRoutes` không — tức có được đính Bearer token không. */\n isSecureUrl(url: string): boolean {\n const patterns = this.config?.secureRoutes;\n if (!patterns?.length) {\n return false;\n }\n return patterns.some(pattern => matchSecureRoute(pattern, url));\n }\n\n /** Origin của IdP, để interceptor không bao giờ tự đính token vào chính request lấy token. */\n getIssuerOrigin(): string | null {\n const issuer = this.config ? this.#resolveIssuer(this.config) : null;\n if (!issuer) {\n return null;\n }\n try {\n return new URL(issuer).origin;\n } catch {\n return null;\n }\n }\n\n /** Claims của user hiện tại ở dạng `SdAuthInfo` của module auth, hoặc `null` khi chưa đăng nhập. */\n authInfo(): SdAuthInfo | null {\n const claims = this.idTokenClaims();\n if (!claims) {\n return null;\n }\n return {\n id: claims['sub'] as string | undefined,\n username: resolveUsername(claims),\n email: claims['email'] as string | undefined,\n firstName: claims['given_name'] as string | undefined,\n lastName: claims['family_name'] as string | undefined,\n data: claims,\n };\n }\n\n /** Claims của user hiện tại ở dạng `SdLayoutUserInfo` của module layout. */\n userInfo(): SdLayoutUserInfo {\n const claims = this.idTokenClaims();\n if (!claims) {\n return {};\n }\n return {\n username: resolveUsername(claims),\n email: claims['email'] as string | undefined,\n fullName: resolveFullName(claims),\n avatar: claims['picture'] as string | undefined,\n };\n }\n\n // ---------------------------------------------------------------------------------------------\n // Mối nối với trình duyệt (tách riêng để test ghi đè được)\n // ---------------------------------------------------------------------------------------------\n\n /** Điều hướng full-page. */\n protected navigate(url: string): void {\n window.location.href = url;\n }\n\n /** Viết lại URL trên thanh địa chỉ mà không tạo entry mới trong history. */\n protected replaceHistoryState(url: string): void {\n window.history.replaceState(null, '', url);\n }\n\n // ---------------------------------------------------------------------------------------------\n // Nội bộ\n // ---------------------------------------------------------------------------------------------\n\n async #runRefresh(): Promise<boolean> {\n const oauth = this.#oauth;\n if (!oauth) {\n return false;\n }\n try {\n if (!this.config?.useSilentRefresh && oauth.getRefreshToken()) {\n await oauth.refreshToken();\n } else {\n await oauth.silentRefresh();\n }\n } catch (error) {\n this.#emit('token_refresh_error', undefined, error);\n this.#evaluateSessionExpired();\n return false;\n }\n this.#syncFromOAuth();\n return this.isAuthenticated();\n }\n\n #subscribeOAuthEvents(): void {\n const oauth = this.#oauth;\n if (!oauth) {\n return;\n }\n // why: KHÔNG đăng ký onDestroy ở đây — hàm này chạy bên trong init() bất đồng bộ, và\n // DestroyRef.onDestroy ném NG0205 nếu injector đã bị huỷ trước khi init() kịp chạy xong.\n // Dọn dẹp dồn hết về #teardown(), vốn đã đăng ký một lần trong constructor.\n this.#eventsSubscription = oauth.events.subscribe(event => this.#onOAuthEvent(event));\n }\n\n #onOAuthEvent(event: OAuthEvent): void {\n if (event.type === 'token_received') {\n this.#clearExpiryCheck();\n this.sessionExpired.set(false);\n this.lastAuthError.set(null);\n this.#syncFromOAuth();\n this.#emit('token_received');\n return;\n }\n\n if (event.type === 'token_refreshed' || event.type === 'silently_refreshed') {\n this.#clearExpiryCheck();\n this.sessionExpired.set(false);\n this.#syncFromOAuth();\n this.#emit('token_refreshed');\n return;\n }\n\n if (REFRESH_ERROR_EVENT_TYPES.has(event.type)) {\n this.#emit('token_refresh_error', event.type, reasonOf(event));\n this.#evaluateSessionExpired();\n return;\n }\n\n if (AUTH_ERROR_EVENT_TYPES.has(event.type)) {\n this.#emitAuthError(event.type, reasonOf(event));\n }\n }\n\n /**\n * Quyết định một lần gia hạn thất bại có phải là hết phiên hay không.\n *\n * Refresh hỏng CHƯA phải hết phiên: access token đang giữ thường vẫn còn hạn thêm một lúc, và\n * báo \"phiên hết hạn\" ngay lúc đó là báo sớm — app vẫn chạy bình thường cho tới khi có một\n * request thật trả 401. Chỉ khi token đó cũng chết thì phiên mới thực sự kết thúc.\n */\n #evaluateSessionExpired(): void {\n if (!this.#oauth) {\n return;\n }\n if (this.#oauth.hasValidAccessToken()) {\n // why: lần gia hạn chủ động này đã hỏng và sẽ không tự lặp lại (timer chỉ lên lịch lại khi\n // nhận token mới), nên phải tự hẹn kiểm tra đúng lúc token hiện tại hết hạn.\n this.#scheduleExpiryCheck();\n return;\n }\n this.#markSessionExpired();\n }\n\n #scheduleExpiryCheck(): void {\n this.#clearExpiryCheck();\n const expiresAt = this.#oauth?.getAccessTokenExpiration();\n if (!expiresAt) {\n return;\n }\n const delay = Math.max(0, expiresAt - Date.now()) + 1000;\n this.#expiryCheckTimer = setTimeout(() => {\n this.#expiryCheckTimer = null;\n if (!this.#oauth?.hasValidAccessToken()) {\n this.#markSessionExpired();\n }\n }, delay);\n }\n\n #clearExpiryCheck(): void {\n if (this.#expiryCheckTimer !== null) {\n clearTimeout(this.#expiryCheckTimer);\n this.#expiryCheckTimer = null;\n }\n }\n\n #markSessionExpired(): void {\n if (this.sessionExpired()) {\n return;\n }\n this.#clearExpiryCheck();\n this.#resetSessionState();\n this.sessionExpired.set(true);\n this.#emit('session_expired');\n\n if ((this.config?.onSessionExpired ?? 'login') === 'login') {\n void this.login();\n }\n }\n\n #listenCrossTabLogout(config: SdAuthOmTenantConfig): void {\n if (this.#channel) {\n return;\n }\n this.#channel = new SdAuthOmSessionChannel(config.sessionChannelName || `sd-authom-session:${config.clientId}`);\n this.#channel.onLogout(() => {\n this.#clearExpiryCheck();\n this.#oauth?.logOut(true);\n this.#resetSessionState();\n this.#emit('logout_other_tab');\n this.#oauth?.initCodeFlow(undefined, this.#authorizeParams('login'));\n });\n }\n\n #notifyOtherTabs(config: SdAuthOmTenantConfig | undefined): void {\n if (config?.crossTabLogout === false) {\n return;\n }\n this.#channel?.notifyLogout();\n }\n\n #restoreReturnUrl(): void {\n const returnTo = resolveReturnUrl(this.#oauth?.state);\n if (returnTo) {\n // why: vẫn đang trong APP_INITIALIZER, tức trước điều hướng đầu tiên của Router — viết lại\n // URL ở đây khiến lần điều hướng đó nhắm thẳng trang user yêu cầu và chạy guard bình thường.\n this.replaceHistoryState(window.location.origin + returnTo);\n }\n }\n\n #syncFromOAuth(): void {\n const oauth = this.#oauth;\n if (!oauth?.hasValidAccessToken()) {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n return;\n }\n this.accessToken.set(oauth.getAccessToken() || null);\n this.idTokenClaims.set((oauth.getIdentityClaims() as Record<string, unknown> | null) ?? null);\n }\n\n #resetSessionState(): void {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n }\n\n #authorizeParams(prompt?: string): Record<string, string> {\n const params: Record<string, string> = {};\n if (this.config?.organization) {\n params['organization'] = this.config.organization;\n }\n if (prompt) {\n params['prompt'] = prompt;\n }\n return params;\n }\n\n #resolveIssuer(config: SdAuthOmTenantConfig): string {\n const issuer = config.issuer?.trim();\n if (issuer) {\n return issuer;\n }\n const domain = config.domain?.trim();\n if (domain) {\n return /^https?:\\/\\//i.test(domain) ? domain : `https://${domain}/`;\n }\n throw new Error('[SdAuthOmService] Missing tenant \"issuer\" (or legacy \"domain\") in SdAuthOmTenantConfig.');\n }\n\n /**\n * Chuẩn hoá basename file tĩnh trong `public/` thành URL tuyệt đối.\n * `'auth-error'` | `'/auth-error'` | `'auth-error.html'` → `${origin}/auth-error.html`.\n */\n #toPublicHtmlUrl(name: string): string {\n const base = name\n .trim()\n .replace(/^\\/+/, '')\n .replace(/\\.html$/i, '');\n return `${window.location.origin}/${base}.html`;\n }\n\n #emit(type: SdAuthOmEventType, reason?: string, error?: unknown): void {\n this.#events.next({ type, reason, error });\n }\n\n #emitAuthError(reason: string, error?: unknown): void {\n const event: SdAuthOmEvent = { type: 'auth_error', reason, error };\n this.lastAuthError.set(event);\n this.#events.next(event);\n }\n\n #teardown(): void {\n this.#clearExpiryCheck();\n this.#eventsSubscription?.unsubscribe();\n this.#eventsSubscription = null;\n this.#channel?.close();\n this.#channel = null;\n this.#events.complete();\n }\n}\n\n/**\n * Khớp một URL với một phần tử `secureRoutes`.\n *\n * Có `*` → glob trên toàn bộ URL. Không có `*` → khớp tiền tố, không phân biệt hoa thường\n * (đây là dạng \"danh sách host\" mà các portal đang dùng).\n */\nexport function matchSecureRoute(pattern: string, url: string): boolean {\n if (!pattern) {\n return false;\n }\n if (pattern.includes('*')) {\n return matchGlob(pattern, url);\n }\n return url.toLowerCase().startsWith(pattern.toLowerCase());\n}\n\nconst escapeRegExp = (value: string): string => value.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Khớp glob đơn giản, `*` thay cho một đoạn bất kỳ. Giữ nguyên hành vi từ bản trước của module. */\nexport const matchGlob = (pattern: string, url: string): boolean => {\n const regexBody = pattern.split('*').map(escapeRegExp).join('.*');\n return new RegExp(`^${regexBody}$`).test(url);\n};\n\n/**\n * AuthOM không phát claim `preferred_username` (xác nhận từ discovery document thật), nên phải có\n * chuỗi fallback thay vì đọc thẳng một claim.\n */\nfunction resolveUsername(claims: Record<string, unknown>): string | undefined {\n return (claims['preferred_username'] ?? claims['email'] ?? claims['nickname'] ?? claims['sub']) as string | undefined;\n}\n\nfunction resolveFullName(claims: Record<string, unknown>): string | undefined {\n const lastName = claims['family_name'] as string | undefined;\n const firstName = claims['given_name'] as string | undefined;\n const composed = [lastName, firstName].filter(Boolean).join(' ');\n return composed || ((claims['name'] ?? claims['nickname']) as string | undefined);\n}\n\nfunction reasonOf(event: OAuthEvent): unknown {\n return event instanceof OAuthErrorEvent ? event.reason : (event as { info?: unknown }).info;\n}\n","import { HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { Observable, catchError, from, switchMap, throwError } from 'rxjs';\nimport { SdAuthOmService } from './authom.service';\n\n/**\n * Đính `Authorization: Bearer <token>` vào các request khớp `secureRoutes`.\n *\n * Interceptor này cũng là chốt gia hạn cuối cùng: nếu access token đã hết hạn ngay trước khi gửi\n * (máy vừa ngủ dậy, tab bị treo, timer refresh chủ động lỡ nhịp) thì nó gia hạn trước rồi mới gửi,\n * thay vì để request chết 401 rồi mới xử lý.\n */\nexport const SdAuthOmInterceptor: HttpInterceptorFn = (req, next) => {\n const authom = inject(SdAuthOmService);\n if (!authom.config) {\n return next(req);\n }\n\n // why: request lấy/gia hạn token cũng đi qua HttpClient. Không loại trừ origin của IdP thì một\n // `secureRoutes` rộng tay sẽ khiến refresh tự gọi lại chính nó.\n const issuerOrigin = authom.getIssuerOrigin();\n if (issuerOrigin && req.url.toLowerCase().startsWith(issuerOrigin.toLowerCase())) {\n return next(req);\n }\n\n if (!authom.isSecureUrl(req.url)) {\n return next(req);\n }\n\n const send = (token: string | null): Observable<HttpEvent<unknown>> => forward(req, next, authom, token);\n\n if (!authom.isAccessTokenExpired()) {\n return send(authom.getAccessToken());\n }\n\n if (!authom.canRefresh()) {\n return send(null);\n }\n\n return from(authom.refresh()).pipe(switchMap(refreshed => send(refreshed ? authom.getAccessToken() : null)));\n};\n\nfunction forward(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn,\n authom: SdAuthOmService,\n token: string | null\n): Observable<HttpEvent<unknown>> {\n const outgoing = token ? req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) }) : req;\n return next(outgoing).pipe(\n catchError((error: unknown) => {\n // why: 401 từ một route đã được bảo vệ là tín hiệu đáng tin cậy nhất cho biết phiên đã chết\n // thật — đáng tin hơn một lần refresh hỏng, vì lúc đó token cũ thường vẫn còn hạn.\n if (error instanceof HttpErrorResponse && error.status === 401) {\n authom.handleUnauthorized();\n }\n return throwError(() => error);\n })\n );\n}\n","import { inject } from '@angular/core';\nimport { CanActivateFn } from '@angular/router';\nimport { SdAuthOmService } from './authom.service';\n\n/**\n * Chặn route khi chưa đăng nhập và khởi động code flow, mang theo URL được yêu cầu.\n *\n * Dùng trực tiếp trên route:\n * ```ts\n * { path: 'orders', canActivate: [sdAuthOmGuard], loadChildren: ... }\n * ```\n *\n * App đã dùng `SdAuthGuard` của module auth thì không cần guard này — hãy để\n * `SdAuthOmAuthConfiguration` cấp `SD_AUTH_CONFIGURATION` (mặc định `provideSdAuthOm` làm sẵn).\n */\nexport const sdAuthOmGuard: CanActivateFn = (_route, state) => {\n const authom = inject(SdAuthOmService);\n if (authom.isAuthenticated()) {\n return true;\n }\n void authom.login({ returnTo: state.url });\n return false;\n};\n","import { Injectable, inject } from '@angular/core';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { Observable, map, of } from 'rxjs';\nimport type { ISdAuthConfiguration, SdAuthInfo } from '@sd-angular/core/modules/auth';\nimport { SdAuthOmService } from './authom.service';\n\n/**\n * Nối AuthOM vào module `auth` dùng chung.\n *\n * Cấp token này (mặc định `provideSdAuthOm` đã làm) là đủ để `SdAuthGuard`, `SdPortalGuard` và\n * `SdAuthService.signout()` chạy đúng trên AuthOM mà app không phải viết thêm gì.\n *\n * `changePassword` cố ý KHÔNG được cài đặt: AuthOM chưa công bố cơ chế đổi mật khẩu, và\n * `SdAuthService.changePassword()` tự bỏ qua khi thiếu action — đúng hơn là ném lỗi lúc chạy.\n */\n@Injectable({ providedIn: 'root' })\nexport class SdAuthOmAuthConfiguration implements ISdAuthConfiguration {\n readonly #authom = inject(SdAuthOmService);\n\n /**\n * Phát lại mỗi khi claims đổi, thay vì trả một giá trị chụp một lần.\n *\n * `SdAuthService` bọc giá trị này bằng `toSignal` trong constructor của chính nó, nên một\n * Observable sống giữ cho `getAuthInfo()` đúng cả khi service được tạo trước lúc đăng nhập xong.\n */\n readonly #authInfo$: Observable<SdAuthInfo> = toObservable(this.#authom.idTokenClaims).pipe(map(() => this.#authom.authInfo() ?? {}));\n\n guard: ISdAuthConfiguration['guard'] = {\n auth: (_route, state) => {\n if (this.#authom.isAuthenticated()) {\n return of(true);\n }\n void this.#authom.login({ returnTo: state.url });\n return of(false);\n },\n\n /** AuthOM không có khái niệm portal riêng — mặc định cho qua, app tự override nếu cần. */\n portal: () => of(true),\n\n authInfo: () => this.#authInfo$,\n };\n\n action: ISdAuthConfiguration['action'] = {\n signout: () => this.#authom.logout(),\n };\n}\n","import {\n APP_INITIALIZER,\n EnvironmentProviders,\n ModuleWithProviders,\n NgModule,\n Provider,\n Type,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { SD_AUTH_CONFIGURATION } from '@sd-angular/core/modules/auth';\nimport { provideOAuthClient } from 'angular-oauth2-oidc';\nimport { SdAuthOmAuthConfiguration } from './authom.auth-configuration';\nimport { ISdAuthOmConfiguration, SD_AUTHOM_CONFIGURATION } from './authom.configuration';\nimport { SdAuthOmService } from './authom.service';\n\nexport interface SdAuthOmProvideOptions {\n useClass?: Type<ISdAuthOmConfiguration>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useFactory?: (...args: any[]) => ISdAuthOmConfiguration;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n deps?: any[];\n\n /**\n * Cấp luôn `SD_AUTH_CONFIGURATION` bằng `SdAuthOmAuthConfiguration`. Mặc định `true`.\n *\n * Nhờ vậy `SdAuthGuard`, `SdPortalGuard` và `SdAuthService.signout()` chạy được trên AuthOM mà\n * app không phải khai báo gì thêm. Đặt `false` khi app tự bind token này (ví dụ portal chạy\n * song song nhiều provider và tự chọn theo môi trường).\n */\n withAuthConfiguration?: boolean;\n}\n\nfunction buildConfigurationProvider(options: SdAuthOmProvideOptions): Provider {\n if (options.useFactory) {\n return { provide: SD_AUTHOM_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] };\n }\n if (options.useClass) {\n return { provide: SD_AUTHOM_CONFIGURATION, useClass: options.useClass };\n }\n throw new Error('[provideSdAuthOm] Either \"useClass\" or \"useFactory\" must be supplied.');\n}\n\nfunction buildCommonProviders(options: SdAuthOmProvideOptions): (Provider | EnvironmentProviders)[] {\n const providers: (Provider | EnvironmentProviders)[] = [\n // why: config = null nên interceptor DI mặc định của client OIDC không đính token cho ai cả.\n // Việc đính token thuộc về SdAuthOmInterceptor, nhờ đó app chỉ cần withInterceptors([...])\n // và không phải nhớ thêm withInterceptorsFromDi().\n provideOAuthClient(),\n SdAuthOmService,\n buildConfigurationProvider(options),\n ];\n\n if (options.withAuthConfiguration !== false) {\n providers.push({ provide: SD_AUTH_CONFIGURATION, useExisting: SdAuthOmAuthConfiguration });\n }\n\n return providers;\n}\n\n/**\n * Đăng ký AuthOM cho app standalone (gọi trong `app.config.ts`).\n *\n * ```ts\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([SdAuthOmInterceptor])),\n * provideSdAuthOm({\n * useFactory: () => ({\n * loadTenantConfig: () => Promise.resolve({\n * issuer: 'https://api-qc.onemount.dev/auth-om/iam/v1/mag-qc/',\n * clientId: 'YOUR_CLIENT_ID',\n * redirectUri: `${location.origin}/callback`,\n * audience: 'http://api-qc.int.onemount.dev/auth-om/api/v1/mag-qc',\n * secureRoutes: ['https://api-qc.onemount.dev'],\n * }),\n * }),\n * }),\n * ],\n * };\n * ```\n */\nexport function provideSdAuthOm(options: SdAuthOmProvideOptions): EnvironmentProviders {\n const providers = buildCommonProviders(options);\n\n providers.push(\n provideAppInitializer(() => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return configLoader.loadTenantConfig().then(config => authom.init(config));\n })\n );\n\n return makeEnvironmentProviders(providers);\n}\n\n/** Đăng ký AuthOM cho kiến trúc `NgModule` cũ. */\n@NgModule({})\nexport class SdAuthOmModule {\n static forRoot(options: SdAuthOmProvideOptions): ModuleWithProviders<SdAuthOmModule> {\n return {\n ngModule: SdAuthOmModule,\n providers: [\n ...buildCommonProviders(options),\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: () => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return () => configLoader.loadTenantConfig().then(config => authom.init(config));\n },\n },\n ],\n };\n }\n}\n","/**\n * Module `authom` — client xác thực OAuth 2.0 Authorization Code + PKCE cho AuthOM.\n *\n * Module sở hữu trọn vòng đời phiên: bootstrap qua discovery document, đổi authorization code,\n * khôi phục deep link, gia hạn token chủ động, phát hiện hết phiên, xử lý lỗi xác thực và đăng\n * xuất đồng bộ giữa các tab.\n *\n * Cách dùng (standalone):\n * ```ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { provideSdAuthOm, SdAuthOmInterceptor } from '@sd-angular/core/modules/authom';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([SdAuthOmInterceptor])),\n * provideSdAuthOm({\n * useFactory: () => ({\n * loadTenantConfig: () => Promise.resolve({\n * issuer: 'https://api-qc.onemount.dev/auth-om/iam/v1/mag-qc/',\n * clientId: 'YOUR_CLIENT_ID',\n * redirectUri: `${location.origin}/callback`,\n * audience: 'http://api-qc.int.onemount.dev/auth-om/api/v1/mag-qc',\n * secureRoutes: ['https://api-qc.onemount.dev'],\n * }),\n * }),\n * }),\n * ],\n * };\n * ```\n *\n * Yêu cầu phía app:\n * 1. Cài `angular-oauth2-oidc` (peer dependency, chọn major khớp Angular của app).\n * 2. App chạy trên HTTPS hoặc localhost — Web Crypto API cần secure context để tạo PKCE.\n * 3. `redirectUri` phải trùng giá trị đã đăng ký trên client AuthOM.\n * 4. Chỉ khi bật `useSilentRefresh`: copy `silent-authom.html` cạnh source module này vào `public/`.\n */\nexport * from './authom.configuration';\nexport * from './authom.event';\nexport * from './authom.return-url';\nexport * from './authom.session-channel';\nexport * from './authom.service';\nexport * from './authom.interceptor';\nexport * from './authom.guard';\nexport * from './authom.auth-configuration';\nexport * from './authom.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1032,7 +1032,7 @@ class SidebarComponent {
|
|
|
1032
1032
|
return count;
|
|
1033
1033
|
};
|
|
1034
1034
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1035
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SidebarComponent, isStandalone: true, selector: "sidebar", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobileOrTablet ? screenHeight + 'px' : '100%'\">\n <div class=\"c-header\">\n @if (_sidebar.logoUrl) {\n <div class=\"c-logo\">\n <a href=\"javascript:;\" (click)=\"openHomePage()\">\n <img alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n </a>\n </div>\n }\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive() ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: isPinnedMenuGroupActive() ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n push_pin\n </mat-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n {{ nodeMenuGroup.icon || 'widgets' }}\n </mat-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() >= 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <mat-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\">cancel</mat-icon>\n } @else {\n <mat-icon class=\"c-search-prefix-icon search\">search</mat-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandColor || '#2962FF' : '#1F1F1F',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <mat-icon\n class=\"c-menu-node-description-icon-pin\"\n [fontSet]=\"isPinnedNode(node) ? 'material-icons' : 'material-icons-outlined'\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || '#2962FF' : null\"\n (click)=\"onTogglePin($event, node)\"\n >push_pin\n </mat-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node : activeMenuPath() }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <mat-icon class=\"c-menu-node-description-icon-expand\">\n {{ treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\n </mat-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobileOrTablet\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{position:relative;display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{flex-shrink:0;display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:cover}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;min-height:0;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{margin-bottom:80px;display:flex;flex-direction:column;align-items:center;flex:0 0 60px;width:60px;min-width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;min-height:0;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search{flex-shrink:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;min-height:0;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{position:absolute;bottom:0;left:0;width:60px;height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}\n"], dependencies: [{ kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i3$1.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i3$1.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i3$1.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i3$1.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "pipe", type: SdSafeHtmlPipe, name: "sdSafeHtml" }, { kind: "ngmodule", type: MatInputModule }, { kind: "pipe", type: MenuFocusPipe, name: "menuFocus" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: HighlightSearchPipe, name: "highLightSearch" }, { kind: "directive", type: SdSuffixDefDirective, selector: "[sdSuffixDef]" }, { kind: "component", type: LayoutUserComponent$1, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1035
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SidebarComponent, isStandalone: true, selector: "sidebar", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobileOrTablet ? screenHeight + 'px' : '100%'\">\n <div class=\"c-header\">\n @if (_sidebar.logoUrl) {\n <div class=\"c-logo\">\n <a href=\"javascript:;\" (click)=\"openHomePage()\">\n <img alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n </a>\n </div>\n }\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive() ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: isPinnedMenuGroupActive() ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n push_pin\n </mat-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n {{ nodeMenuGroup.icon || 'widgets' }}\n </mat-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() >= 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input [clearable]=\"true\"\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <mat-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\">cancel</mat-icon>\n } @else {\n <mat-icon class=\"c-search-prefix-icon search\">search</mat-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandColor || '#2962FF' : '#1F1F1F',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <mat-icon\n class=\"c-menu-node-description-icon-pin\"\n [fontSet]=\"isPinnedNode(node) ? 'material-icons' : 'material-icons-outlined'\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || '#2962FF' : null\"\n (click)=\"onTogglePin($event, node)\"\n >push_pin\n </mat-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node : activeMenuPath() }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <mat-icon class=\"c-menu-node-description-icon-expand\">\n {{ treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\n </mat-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobileOrTablet\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{position:relative;display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{flex-shrink:0;display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:cover}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;min-height:0;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{margin-bottom:80px;display:flex;flex-direction:column;align-items:center;flex:0 0 60px;width:60px;min-width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;min-height:0;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search{flex-shrink:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;min-height:0;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{position:absolute;bottom:0;left:0;width:60px;height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}\n"], dependencies: [{ kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i3$1.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i3$1.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i3$1.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i3$1.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "pipe", type: SdSafeHtmlPipe, name: "sdSafeHtml" }, { kind: "ngmodule", type: MatInputModule }, { kind: "pipe", type: MenuFocusPipe, name: "menuFocus" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: HighlightSearchPipe, name: "highLightSearch" }, { kind: "directive", type: SdSuffixDefDirective, selector: "[sdSuffixDef]" }, { kind: "component", type: LayoutUserComponent$1, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1036
1036
|
}
|
|
1037
1037
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SidebarComponent, decorators: [{
|
|
1038
1038
|
type: Component,
|
|
@@ -1051,7 +1051,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
1051
1051
|
SdSuffixDefDirective,
|
|
1052
1052
|
LayoutUserComponent$1,
|
|
1053
1053
|
TranslatePipe,
|
|
1054
|
-
], template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobileOrTablet ? screenHeight + 'px' : '100%'\">\n <div class=\"c-header\">\n @if (_sidebar.logoUrl) {\n <div class=\"c-logo\">\n <a href=\"javascript:;\" (click)=\"openHomePage()\">\n <img alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n </a>\n </div>\n }\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive() ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: isPinnedMenuGroupActive() ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n push_pin\n </mat-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n {{ nodeMenuGroup.icon || 'widgets' }}\n </mat-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() >= 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <mat-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\">cancel</mat-icon>\n } @else {\n <mat-icon class=\"c-search-prefix-icon search\">search</mat-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandColor || '#2962FF' : '#1F1F1F',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <mat-icon\n class=\"c-menu-node-description-icon-pin\"\n [fontSet]=\"isPinnedNode(node) ? 'material-icons' : 'material-icons-outlined'\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || '#2962FF' : null\"\n (click)=\"onTogglePin($event, node)\"\n >push_pin\n </mat-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node : activeMenuPath() }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <mat-icon class=\"c-menu-node-description-icon-expand\">\n {{ treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\n </mat-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobileOrTablet\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{position:relative;display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{flex-shrink:0;display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:cover}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;min-height:0;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{margin-bottom:80px;display:flex;flex-direction:column;align-items:center;flex:0 0 60px;width:60px;min-width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;min-height:0;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search{flex-shrink:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;min-height:0;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{position:absolute;bottom:0;left:0;width:60px;height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}\n"] }]
|
|
1054
|
+
], template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobileOrTablet ? screenHeight + 'px' : '100%'\">\n <div class=\"c-header\">\n @if (_sidebar.logoUrl) {\n <div class=\"c-logo\">\n <a href=\"javascript:;\" (click)=\"openHomePage()\">\n <img alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n </a>\n </div>\n }\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive() ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: isPinnedMenuGroupActive() ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n push_pin\n </mat-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <mat-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandColor || '#2962FF' : '#8C8C8C',\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n {{ nodeMenuGroup.icon || 'widgets' }}\n </mat-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() >= 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input [clearable]=\"true\"\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <mat-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\">cancel</mat-icon>\n } @else {\n <mat-icon class=\"c-search-prefix-icon search\">search</mat-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandLightColor || '#F8F9FA' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color: (currentPath() | menuFocus: node : activeMenuPath()) ? _sidebar.brandColor || '#2962FF' : '#1F1F1F',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <mat-icon\n class=\"c-menu-node-description-icon-pin\"\n [fontSet]=\"isPinnedNode(node) ? 'material-icons' : 'material-icons-outlined'\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || '#2962FF' : null\"\n (click)=\"onTogglePin($event, node)\"\n >push_pin\n </mat-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node : activeMenuPath() }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <mat-icon class=\"c-menu-node-description-icon-expand\">\n {{ treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}\n </mat-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobileOrTablet\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{position:relative;display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{flex-shrink:0;display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:cover}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;min-height:0;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{margin-bottom:80px;display:flex;flex-direction:column;align-items:center;flex:0 0 60px;width:60px;min-width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;min-height:0;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search{flex-shrink:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;min-height:0;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{position:absolute;bottom:0;left:0;width:60px;height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}\n"] }]
|
|
1055
1055
|
}], ctorParameters: () => [] });
|
|
1056
1056
|
|
|
1057
1057
|
class SidebarV1Component {
|