web-mojo 2.2.64 → 2.2.66
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/CHANGELOG.md +14 -0
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/ChatView-CYIOOvHM.js +2 -0
- package/dist/chunks/ChatView-CYIOOvHM.js.map +1 -0
- package/dist/chunks/ChatView-DTcs3Xh7.js +2 -0
- package/dist/chunks/ChatView-DTcs3Xh7.js.map +1 -0
- package/dist/chunks/{Collection-1sPoIFvQ.js → Collection-BgfCzEE5.js} +2 -2
- package/dist/chunks/{Collection-1sPoIFvQ.js.map → Collection-BgfCzEE5.js.map} +1 -1
- package/dist/chunks/ContextMenu-C7QJDUar.js +2 -0
- package/dist/chunks/ContextMenu-C7QJDUar.js.map +1 -0
- package/dist/chunks/ContextMenu-C9RiB1kl.js +2 -0
- package/dist/chunks/ContextMenu-C9RiB1kl.js.map +1 -0
- package/dist/chunks/{DataView--nUWtq6r.js → DataView-Dl2jl_0h.js} +2 -2
- package/dist/chunks/{DataView--nUWtq6r.js.map → DataView-Dl2jl_0h.js.map} +1 -1
- package/dist/chunks/{Dialog-DwCTFV6O.js → Dialog-9FzcToYV.js} +2 -2
- package/dist/chunks/{Dialog-BcgSR01Z.js.map → Dialog-9FzcToYV.js.map} +1 -1
- package/dist/chunks/{Dialog-BcgSR01Z.js → Dialog-DZG_XYbk.js} +2 -2
- package/dist/chunks/{Dialog-DwCTFV6O.js.map → Dialog-DZG_XYbk.js.map} +1 -1
- package/dist/chunks/Files-BDqCTw9A.js +2 -0
- package/dist/chunks/Files-BDqCTw9A.js.map +1 -0
- package/dist/chunks/Files-B_NE3QwI.js +2 -0
- package/dist/chunks/Files-B_NE3QwI.js.map +1 -0
- package/dist/chunks/FormView-CQ0eFCJu.js +3 -0
- package/dist/chunks/FormView-CQ0eFCJu.js.map +1 -0
- package/dist/chunks/FormView-D2Ia0Idj.js +3 -0
- package/dist/chunks/FormView-D2Ia0Idj.js.map +1 -0
- package/dist/chunks/{ListView-6JQ6tRXs.js → ListView-BunTmkAU.js} +2 -2
- package/dist/chunks/{ListView-6JQ6tRXs.js.map → ListView-BunTmkAU.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-J067qrrt.js → MetricsCountryMapView-C_p6hcly.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-J067qrrt.js.map → MetricsCountryMapView-C_p6hcly.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-BzuQ1Imn.js → MetricsMiniChartWidget-C1RCd-Xw.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-BzuQ1Imn.js.map → MetricsMiniChartWidget-C1RCd-Xw.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-C3oO3WuD.js → MetricsMiniChartWidget-C9QdxxBn.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-C3oO3WuD.js.map → MetricsMiniChartWidget-C9QdxxBn.js.map} +1 -1
- package/dist/chunks/{PDFViewer-DSa4BZCm.js → PDFViewer-B_LSyM7g.js} +2 -2
- package/dist/chunks/{PDFViewer-DSa4BZCm.js.map → PDFViewer-B_LSyM7g.js.map} +1 -1
- package/dist/chunks/{PDFViewer-CsyKn-gh.js → PDFViewer-DI9v3Vkg.js} +2 -2
- package/dist/chunks/{PDFViewer-CsyKn-gh.js.map → PDFViewer-DI9v3Vkg.js.map} +1 -1
- package/dist/chunks/{Rest-DHbszkuP.js → Rest-DFnCoMvk.js} +2 -2
- package/dist/chunks/{Rest-DHbszkuP.js.map → Rest-DFnCoMvk.js.map} +1 -1
- package/dist/chunks/TokenManager-Dk5oiCh1.js +2 -0
- package/dist/chunks/{TokenManager-FlWEoTIa.js.map → TokenManager-Dk5oiCh1.js.map} +1 -1
- package/dist/chunks/TokenManager-DzLjXShS.js +2 -0
- package/dist/chunks/{TokenManager-DZJGwkbs.js.map → TokenManager-DzLjXShS.js.map} +1 -1
- package/dist/chunks/User-BwDOeY-q.js +3 -0
- package/dist/chunks/User-BwDOeY-q.js.map +1 -0
- package/dist/chunks/User-CbqYlTBK.js +3 -0
- package/dist/chunks/User-CbqYlTBK.js.map +1 -0
- package/dist/chunks/{WebApp-DovLtA60.js → WebApp-BCfA2Dlw.js} +2 -2
- package/dist/chunks/{WebApp-DovLtA60.js.map → WebApp-BCfA2Dlw.js.map} +1 -1
- package/dist/chunks/{WebApp-CULZpO_0.js → WebApp-kKz5FV57.js} +2 -2
- package/dist/chunks/{WebApp-CULZpO_0.js.map → WebApp-kKz5FV57.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-BoF7TV7i.js → WebSocketClient-fEGJDFXJ.js} +2 -2
- package/dist/chunks/{WebSocketClient-BoF7TV7i.js.map → WebSocketClient-fEGJDFXJ.js.map} +1 -1
- package/dist/chunks/{version-CaVHauah.js → version-Bykv8fhW.js} +2 -2
- package/dist/chunks/{version-CaVHauah.js.map → version-Bykv8fhW.js.map} +1 -1
- package/dist/chunks/{version-3OFdtu10.js → version-D0hwRoO4.js} +2 -2
- package/dist/chunks/{version-3OFdtu10.js.map → version-D0hwRoO4.js.map} +1 -1
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.cjs.js.map +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/docit.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.cjs.js.map +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/lightbox.es.js.map +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/web-mojo.lite.iife.js +1226 -3
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +95 -75
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/ChatView-1-S71P4O.js +0 -2
- package/dist/chunks/ChatView-1-S71P4O.js.map +0 -1
- package/dist/chunks/ChatView-p0x1jRbF.js +0 -2
- package/dist/chunks/ChatView-p0x1jRbF.js.map +0 -1
- package/dist/chunks/ContextMenu-CsQGpSrv.js +0 -3
- package/dist/chunks/ContextMenu-CsQGpSrv.js.map +0 -1
- package/dist/chunks/ContextMenu-dqFxitXY.js +0 -3
- package/dist/chunks/ContextMenu-dqFxitXY.js.map +0 -1
- package/dist/chunks/FormView-CRmEReTC.js +0 -3
- package/dist/chunks/FormView-CRmEReTC.js.map +0 -1
- package/dist/chunks/FormView-OLA7t-yv.js +0 -3
- package/dist/chunks/FormView-OLA7t-yv.js.map +0 -1
- package/dist/chunks/TokenManager-DZJGwkbs.js +0 -2
- package/dist/chunks/TokenManager-FlWEoTIa.js +0 -2
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/core/utils/ConsoleSilencer.js","../src/core/views/navigation/GroupSearchView.js","../src/core/views/navigation/Sidebar.js","../src/core/views/navigation/PageHeader.js","../src/core/pages/DeniedPage.js","../src/core/pages/NotFoundPage.js","../src/core/PortalApp.js","../src/core/forms/FormPage.js","../src/core/utils/MustacheFormatter.js","../src/index.js"],"sourcesContent":["/**\n * ConsoleSilencer\n * \n * Purpose:\n * - Reduce console noise by filtering out non-critical logs in production.\n * - Keep critical logs (warn, error) by default in production.\n * - Allow runtime override via API, URL params, or localStorage without rebuilding.\n *\n * Usage:\n * import ConsoleSilencer from '@core/utils/ConsoleSilencer.js';\n * ConsoleSilencer.install(); // defaults to 'warn' in production, 'debug' in dev\n *\n * // Optional: set level at install-time\n * ConsoleSilencer.install({ level: 'error' });\n *\n * // Change level at runtime (optionally persist to localStorage)\n * ConsoleSilencer.setLevel('debug', { persist: true });\n *\n * // Temporarily change level around a block\n * ConsoleSilencer.withTemporaryLevel('debug', () => {\n * // noisy diagnostics here\n * });\n *\n * Runtime overrides (no code changes needed):\n * - URL: ?logLevel=debug|info|warn|error|silent\n * also accepts mojoLog and loglevel as param names\n * - localStorage: localStorage.setItem('MOJO_LOG_LEVEL', 'debug')\n *\n * Notes:\n * - This module is side-effect free until install() is called.\n * - Idempotent: calling install() multiple times won't double-wrap console.\n */\n\nconst LEVELS = Object.freeze({\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n log: 3, // alias to info\n debug: 4,\n trace: 5,\n all: 5, // alias\n});\n\n// Resolve environment in a bundler/browser-safe way\n// __DEV__ can be injected by bundlers (e.g., Vite define)\nconst isDev = (() => {\n // Vite/ESM: import.meta.env.DEV if available\n try {\n /* eslint-disable-next-line no-undef */\n if (typeof import.meta !== 'undefined' && import.meta && import.meta.env && typeof import.meta.env.DEV !== 'undefined') {\n return !!import.meta.env.DEV;\n }\n } catch {\n // ignore\n }\n\n // Prefer global __DEV__ if present (e.g., defined via bundler define)\n if (typeof globalThis !== 'undefined' && typeof globalThis.__DEV__ !== 'undefined') {\n try {\n return !!globalThis.__DEV__;\n } catch {\n // fall through if __DEV__ is not directly evaluable\n }\n }\n\n // Fallback detection (best effort)\n const hasProcess = typeof process !== 'undefined' && process && typeof process.env === 'object';\n if (hasProcess && typeof process.env.NODE_ENV === 'string') {\n return process.env.NODE_ENV !== 'production';\n }\n // Default conservative behavior: assume production if unknown\n return false;\n})();\n\nconst isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';\nconst GLOBAL = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : global);\n\n// Capture the original console just once\nconst ORIGINAL_CONSOLE = GLOBAL.console || {};\nconst ORIGINALS = {};\nlet INSTALLED = false;\nlet CURRENT_LEVEL = null;\n\n// Default levels\nconst DEFAULT_DEV_LEVEL = 'debug';\nconst DEFAULT_PROD_LEVEL = 'warn'; // keep warn + error by default\n\n// Helper: normalize/parse level\nfunction parseLevel(level) {\n if (typeof level === 'number') {\n // Clamp to bounds\n const min = LEVELS.silent;\n const max = LEVELS.trace;\n return Math.min(Math.max(level, min), max);\n }\n if (typeof level === 'string') {\n const key = level.toLowerCase();\n if (Object.prototype.hasOwnProperty.call(LEVELS, key)) {\n return LEVELS[key];\n }\n }\n return null;\n}\n\n// Helper: detect override via URLSearchParams\nfunction getUrlLogLevel() {\n if (!isBrowser || typeof location === 'undefined' || !location.search) return null;\n try {\n const params = new URLSearchParams(location.search);\n const keys = ['logLevel', 'loglevel', 'mojoLog'];\n for (const k of keys) {\n const v = params.get(k);\n if (v != null) {\n const parsed = parseLevel(v);\n if (parsed !== null) return parsed;\n }\n }\n } catch {\n // ignore\n }\n return null;\n}\n\n// Helper: detect override via localStorage\nfunction getStoredLogLevel() {\n if (!isBrowser || !('localStorage' in GLOBAL)) return null;\n try {\n const v = GLOBAL.localStorage.getItem('MOJO_LOG_LEVEL');\n if (v != null) {\n const parsed = parseLevel(v);\n if (parsed !== null) return parsed;\n }\n } catch {\n // ignore storage errors\n }\n return null;\n}\n\nfunction storeLogLevel(levelNumberOrName) {\n if (!isBrowser || !('localStorage' in GLOBAL)) return;\n try {\n const key = typeof levelNumberOrName === 'string'\n ? levelNumberOrName\n : levelNumberOrName === null\n ? null\n : Object.entries(LEVELS).find(([, num]) => num === levelNumberOrName)?.[0] ?? null;\n if (key) {\n GLOBAL.localStorage.setItem('MOJO_LOG_LEVEL', key);\n } else {\n GLOBAL.localStorage.removeItem('MOJO_LOG_LEVEL');\n }\n } catch {\n // ignore storage errors\n }\n}\n\n// Build a wrapped method that checks the current level before calling through\nfunction makeWrapper(methodName, methodLevel) {\n const original = ORIGINALS[methodName] || ORIGINAL_CONSOLE[methodName] || (() => {});\n return function wrappedConsoleMethod(...args) {\n // If the method is allowed at the current level, call through\n if (CURRENT_LEVEL >= methodLevel) {\n return original.apply(ORIGINAL_CONSOLE, args);\n }\n // Otherwise, noop\n return undefined;\n };\n}\n\n// Special wrapper for assert: only logs when assertion fails\nfunction makeAssertWrapper() {\n const original = ORIGINALS.assert || ORIGINAL_CONSOLE.assert || (() => {});\n return function wrappedAssert(condition, ...args) {\n // Only logs when condition is falsy; treat failed assert like error-level\n if (!condition) {\n if (CURRENT_LEVEL >= LEVELS.error) {\n return original.apply(ORIGINAL_CONSOLE, [condition, ...args]);\n }\n return undefined;\n }\n // When assertion passes, no-op\n return undefined;\n };\n}\n\nfunction determineInitialLevel(explicitLevel) {\n // 1) Explicit install option\n const explicit = parseLevel(explicitLevel);\n if (explicit !== null) return explicit;\n\n // 2) URL override\n const urlLevel = getUrlLogLevel();\n if (urlLevel !== null) return urlLevel;\n\n // 3) localStorage override\n const storedLevel = getStoredLogLevel();\n if (storedLevel !== null) return storedLevel;\n\n // 4) Environment default\n return parseLevel(isDev ? DEFAULT_DEV_LEVEL : DEFAULT_PROD_LEVEL);\n}\n\n// Construct a patched console that respects CURRENT_LEVEL\nfunction buildPatchedConsole() {\n const patched = { ...ORIGINAL_CONSOLE };\n\n // Save originals once\n const methodLevels = {\n // Critical\n error: LEVELS.error,\n warn: LEVELS.warn,\n\n // Informational\n info: LEVELS.info,\n log: LEVELS.info,\n dir: LEVELS.info,\n table: LEVELS.info,\n\n // Verbose / Debug\n debug: LEVELS.debug,\n group: LEVELS.debug,\n groupCollapsed: LEVELS.debug,\n groupEnd: LEVELS.debug,\n time: LEVELS.debug,\n timeEnd: LEVELS.debug,\n timeLog: LEVELS.debug,\n trace: LEVELS.trace,\n };\n\n // Capture originals and build wrappers\n for (const name of Object.keys(methodLevels)) {\n ORIGINALS[name] = ORIGINAL_CONSOLE[name] || (() => {});\n patched[name] = makeWrapper(name, methodLevels[name]);\n }\n\n // Special-case assert\n ORIGINALS.assert = ORIGINAL_CONSOLE.assert || (() => {});\n patched.assert = makeAssertWrapper();\n\n // Preserve any other console methods as-is (clear, profile, count, etc.)\n return patched;\n}\n\nconst ConsoleSilencer = {\n // Install the silencer (idempotent)\n install(options = {}) {\n if (INSTALLED) {\n // Already installed; update level if provided\n if (options && typeof options.level !== 'undefined') {\n this.setLevel(options.level, { persist: !!options.persist });\n }\n return this;\n }\n\n if (!GLOBAL || !ORIGINAL_CONSOLE) {\n // No console available; nothing to do\n INSTALLED = true; // Prevent re-entry\n return this;\n }\n\n CURRENT_LEVEL = determineInitialLevel(options.level);\n\n const patched = buildPatchedConsole();\n // Replace the global console\n GLOBAL.console = patched;\n\n INSTALLED = true;\n\n // Expose for quick manual access if desired\n GLOBAL.MOJOConsoleSilencer = this;\n\n return this;\n },\n\n // Uninstall and restore the original console\n uninstall() {\n if (!INSTALLED) return this;\n\n try {\n GLOBAL.console = ORIGINAL_CONSOLE;\n } catch {\n // ignore\n }\n INSTALLED = false;\n return this;\n },\n\n // Set current level at runtime; accepts string or number\n // levels: 'silent' | 'error' | 'warn' | 'info' | 'debug' | 'trace'\n setLevel(level, { persist = false } = {}) {\n const parsed = parseLevel(level);\n if (parsed === null) return this; // ignore invalid level\n CURRENT_LEVEL = parsed;\n if (persist) {\n storeLogLevel(level);\n }\n return this;\n },\n\n // Get the current numeric level\n getLevel() {\n return CURRENT_LEVEL;\n },\n\n // Get the current level name (best-effort)\n getLevelName() {\n const entry = Object.entries(LEVELS).find(([, num]) => num === CURRENT_LEVEL);\n return entry ? entry[0] : null;\n },\n\n // Convenience helpers\n criticalOnly({ persist = false } = {}) {\n return this.setLevel('warn', { persist });\n },\n\n errorsOnly({ persist = false } = {}) {\n return this.setLevel('error', { persist });\n },\n\n silent({ persist = false } = {}) {\n return this.setLevel('silent', { persist });\n },\n\n verbose({ persist = false } = {}) {\n return this.setLevel(isDev ? 'debug' : 'info', { persist });\n },\n\n allowAll({ persist = false } = {}) {\n return this.setLevel('trace', { persist });\n },\n\n // Run a block with a temporary level, then restore\n withTemporaryLevel(level, fn) {\n const prev = CURRENT_LEVEL;\n const parsed = parseLevel(level);\n if (parsed === null || typeof fn !== 'function') return fn?.();\n CURRENT_LEVEL = parsed;\n try {\n return fn();\n } finally {\n CURRENT_LEVEL = prev;\n }\n },\n\n // Expose levels map for consumers\n LEVELS,\n};\n\n// Optional: convenience named export alias\nexport default ConsoleSilencer;\nexport const installConsoleSilencer = (options) => ConsoleSilencer.install(options);","/**\n * GroupSearchView - Hierarchical tree-view search component\n * Extends SimpleSearchView to support parent/child relationships\n * Displays items in a tree structure based on parent property\n */\n\nimport SimpleSearchView from './SimpleSearchView.js';\n\nclass GroupSearchView extends SimpleSearchView {\n constructor(options = {}) {\n super({\n ...options,\n className: `group-search-view ${options.className || ''}`.trim()\n });\n\n // Tree-specific configuration\n this.showKind = options.showKind !== undefined ? options.showKind : true;\n this.parentField = options.parentField || 'parent';\n this.kindField = options.kindField || 'kind';\n this.treeData = []; // Processed hierarchical data\n this.flattenedItems = []; // Flattened tree for display\n \n // Visual customization\n this.showLines = options.showLines !== undefined ? options.showLines : true;\n }\n\n /**\n * Build tree hierarchy from flat list\n */\n buildTreeHierarchy(items) {\n if (!items || items.length === 0) {\n return [];\n }\n\n // Create a map of items by ID for quick lookup\n const itemsById = new Map();\n \n // First pass: add all items and extract parent objects\n items.forEach(item => {\n if (!itemsById.has(item.id)) {\n itemsById.set(item.id, {\n ...item,\n children: [],\n level: 0,\n hasChildren: false\n });\n }\n \n // If this item has a parent object, add the parent too\n const parentObj = item[this.parentField];\n if (parentObj && parentObj.id && !itemsById.has(parentObj.id)) {\n itemsById.set(parentObj.id, {\n ...parentObj,\n children: [],\n level: 0,\n hasChildren: false\n });\n }\n });\n\n const rootItems = [];\n\n // Build parent-child relationships\n itemsById.forEach((treeItem, itemId) => {\n const originalItem = items.find(i => i.id === itemId) || treeItem;\n const parentId = originalItem[this.parentField]?.id;\n\n if (parentId && itemsById.has(parentId)) {\n const parent = itemsById.get(parentId);\n parent.children.push(treeItem);\n parent.hasChildren = true;\n } else {\n rootItems.push(treeItem);\n }\n });\n\n // Calculate levels recursively\n const calculateLevels = (nodes, level = 0) => {\n nodes.forEach(node => {\n node.level = level;\n if (node.children.length > 0) {\n node.children.sort((a, b) => (a.name || '').localeCompare(b.name || ''));\n calculateLevels(node.children, level + 1);\n }\n });\n };\n\n rootItems.sort((a, b) => (a.name || '').localeCompare(b.name || ''));\n calculateLevels(rootItems);\n\n return rootItems;\n }\n\n /**\n * Flatten tree structure for rendering\n * Tracks ancestor \"last child\" flags for proper line rendering\n */\n flattenTree(nodes, result = [], ancestorLastFlags = []) {\n nodes.forEach((node, index) => {\n node._isLastChild = index === nodes.length - 1;\n node._ancestorLastFlags = [...ancestorLastFlags];\n \n // Check if this node is the last descendant in its branch\n // (all ancestors are last children AND this node is last child with no children)\n const allAncestorsLast = ancestorLastFlags.every(flag => flag);\n node._isLastDescendant = allAncestorsLast && node._isLastChild && \n (!node.children || node.children.length === 0);\n \n result.push(node);\n \n if (node.children && node.children.length > 0) {\n const newFlags = [...ancestorLastFlags, node._isLastChild];\n this.flattenTree(node.children, result, newFlags);\n }\n });\n \n return result;\n }\n\n /**\n * Second pass: compute which vertical lines should continue for each item\n * \n * Vertical line at segment s shows if there are more siblings coming at level s+1\n * We need to look ahead until we find an item at level s+1 or shallower\n */\n computeVerticalLines(flatList) {\n for (let i = 0; i < flatList.length; i++) {\n const item = flatList[i];\n item._continueVertical = [];\n \n for (let s = 0; s < item.level; s++) {\n // Look ahead to find next item at level s+1 or shallower\n let foundSibling = false;\n for (let j = i + 1; j < flatList.length; j++) {\n const futureItem = flatList[j];\n if (futureItem.level === s + 1) {\n // Found a sibling at this level - vertical should continue\n foundSibling = true;\n break;\n } else if (futureItem.level <= s) {\n // Went back to ancestor level or shallower - no more siblings\n break;\n }\n // Keep searching if futureItem.level > s+1 (still in deeper subtree)\n }\n item._continueVertical[s] = foundSibling;\n }\n }\n }\n\n /**\n * Update filtered items with tree structure\n */\n updateFilteredItems() {\n if (!this.collection) {\n this.filteredItems = [];\n this.treeData = [];\n this.flattenedItems = [];\n return;\n }\n\n // Server-side filtering is handled in performSearch() (from parent class)\n const items = this.collection.toJSON();\n this.treeData = this.buildTreeHierarchy(items);\n this.flattenedItems = this.flattenTree(this.treeData);\n \n // Second pass: compute vertical line continuation\n this.computeVerticalLines(this.flattenedItems);\n \n // Set filteredItems to flattened tree for parent class compatibility\n this.filteredItems = this.flattenedItems;\n\n this.updateResultsView();\n }\n\n /**\n * Get tree-specific item template\n */\n getDefaultItemTemplate() {\n return `\n <div class=\"tree-item-content\">\n <div class=\"tree-item-name\">{{name}}</div>\n {{#showKind}}\n <div class=\"tree-item-kind\">{{kind}}</div>\n {{/showKind}}\n </div>\n `;\n }\n\n /**\n * Process item template with tree structure\n */\n processItemTemplate(item) {\n // Process the base template\n let content = this.itemTemplate;\n content = content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, prop) => {\n if (prop === 'showKind') {\n return this.showKind ? 'true' : '';\n }\n return this.getNestedValue(item, prop) || '';\n });\n\n // Handle conditional sections for kind\n if (this.showKind) {\n content = content.replace(/\\{\\{#showKind\\}\\}(.*?)\\{\\{\\/showKind\\}\\}/gs, '$1');\n } else {\n content = content.replace(/\\{\\{#showKind\\}\\}.*?\\{\\{\\/showKind\\}\\}/gs, '');\n }\n\n // Build tree line segments\n // For an item at level N, we need N segments (one for each ancestor level)\n let lineSegments = '';\n if (this.showLines && item.level > 0) {\n for (let i = 0; i < item.level; i++) {\n const isLastSegment = (i === item.level - 1);\n \n if (isLastSegment) {\n // This segment connects directly to this item\n // Use _isLastChild to determine if this is └ or ├\n const segClass = item._isLastChild ? 'tree-seg tree-seg-last' : 'tree-seg tree-seg-mid';\n lineSegments += `<span class=\"${segClass}\"></span>`;\n } else {\n // Ancestor segment - vertical line continues if next item is deeper than this level\n const continueVertical = item._continueVertical && item._continueVertical[i];\n if (continueVertical) {\n lineSegments += `<span class=\"tree-seg tree-seg-vert\"></span>`;\n } else {\n lineSegments += `<span class=\"tree-seg\"></span>`;\n }\n }\n }\n }\n\n // Classes for the wrapper\n const hasChildren = item.hasChildren ? ' has-children' : '';\n const isLastChild = item._isLastChild ? ' is-last-child' : '';\n\n // Wrap in tree structure\n return `\n <div class=\"tree-item-wrapper${hasChildren}${isLastChild}\" data-tree-level=\"${item.level}\">\n <div class=\"tree-lines\">\n ${lineSegments}\n </div>\n <div class=\"tree-item-body flex-grow-1\">\n ${content}\n </div>\n </div>\n `;\n }\n\n /**\n * Get all root items\n */\n getRootItems() {\n return this.treeData;\n }\n\n /**\n * Get children of a specific node\n */\n getNodeChildren(nodeId) {\n const findNode = (nodes, targetId) => {\n for (const node of nodes) {\n if (node.id === targetId) {\n return node.children;\n }\n if (node.children.length > 0) {\n const found = findNode(node.children, targetId);\n if (found) return found;\n }\n }\n return null;\n };\n\n return findNode(this.treeData, nodeId) || [];\n }\n}\n\nexport default GroupSearchView;\n","/**\n * Sidebar - Simple sidebar navigation component for MOJO framework\n * Provides easy menu switching and dynamic configuration\n */\n\nimport View from '@core/View.js';\nimport GroupSearchView from './GroupSearchView.js';\nimport {GroupList, Group} from '@core/models/Group.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\n\n\nclass Sidebar extends View {\n constructor(options = {}) {\n super({\n tagName: 'nav',\n className: 'sidebar',\n id: 'sidebar',\n ...options\n });\n\n this.menus = new Map();\n this.activeMenuName = null;\n this.currentRoute = null;\n this.showToggle = options.showToggle; // Default to true\n this.isCollapsed = false;\n this.sidebarTheme = options.theme || 'sidebar-light';\n this.customView = null;\n if (this.options.groupHeader) this.groupHeader = this.options.groupHeader;\n\n // Group selector configuration\n // 'inline' (default) - replaces sidebar view\n // 'dialog' - opens in a modal dialog like TopNav\n this.groupSelectorMode = options.groupSelectorMode || 'inline';\n this.groupSelectorDialog = null;\n // Apply sidebar theme\n if (this.sidebarTheme) {\n this.addClass(this.sidebarTheme);\n }\n\n // Initialize menus\n this.initializeMenus(options);\n\n // Setup route change listeners like TopNav\n this.setupRouteListeners();\n\n // Auto-collapse on mobile if specified\n if (options.autoCollapseMobile !== false) {\n this.setupResponsiveBehavior();\n }\n }\n\n groupHeader = `\n {{#group.parent}}\n <div class=\"sidebar-parent-bar\" data-action=\"select-group-parent\">\n <div class=\"parent-info\">\n <span class=\"parent-label\">{{group.parent.kind}}:</span>\n <span class=\"parent-name collapsed-hidden\">{{group.parent.name}}</span>\n </div>\n <i class=\"bi bi-chevron-down parent-expand collapsed-hidden\"></i>\n </div>\n {{/group.parent}}\n <div class=\"sidebar-selected-group-row\" data-action=\"show-group-search\">\n <div class=\"selected-group-info\">\n <div class='selected-group-name collapsed-hidden'>{{group.name}}</div>\n <div class='selected-group-meta collapsed-hidden'>\n <span class=\"selected-group-kind\">{{group.kind}}</span>\n </div>\n </div>\n <i class=\"bi bi-chevron-down selected-group-chevron collapsed-hidden\"></i>\n </div>\n `;\n\n /**\n * Initialize sidebar and auto-switch to correct menu based on current route\n */\n async onInit() {\n await super.onInit();\n\n // Get current route from router\n const app = this.getApp();\n const router = app?.router;\n\n if (router) {\n const currentPath = router.getCurrentPath();\n if (currentPath) {\n this.autoSwitchToMenuForRoute(currentPath);\n }\n }\n\n // Initialize tooltips for nav items\n this.initializeTooltips();\n\n this.searchView = new GroupSearchView({\n noAppend: true,\n showExitButton: true,\n headerText: \"Select Group\",\n containerId: \"sidebar-search-container\",\n Collection: GroupList,\n itemTemplate: `\n <div class=\"p-3 border-bottom\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">#{{id}} {{kind}}</small>\n </div>\n `\n });\n this.addChild(this.searchView);\n this.searchView.on(\"item:selected\", (evt) => {\n console.log(evt);\n this.getApp().setActiveGroup(evt.model);\n });\n this.searchView.on(\"exit\", (item) => {\n console.log(item);\n this.hideGroupSearch();\n });\n }\n\n showGroupSearch() {\n if (this.groupSelectorMode === 'dialog') {\n this.showGroupSearchDialog();\n } else {\n // Inline mode (default)\n this.setClass('sidebar');\n this.showSearch = true;\n this.render();\n }\n }\n\n hideGroupSearch() {\n if (this.groupSelectorMode === 'dialog') {\n if (this.groupSelectorDialog) {\n this.groupSelectorDialog.hide();\n }\n } else {\n // Inline mode\n this.setClass('sidebar');\n this.showSearch = false;\n this.render();\n }\n }\n\n onActionShowGroupSearch() {\n this.showGroupSearch();\n }\n\n async onActionSelectGroupParent() {\n // select-group-parent\n const group = this.getApp().activeGroup;\n const result = await Dialog.confirm(`Are you sure you want to navigate to the '${group.get(\"parent.name\")}'?`);\n if (result) {\n this.getApp().showLoading();\n let parent = new Group({id: group.get(\"parent.id\")});\n await parent.fetch();\n this.getApp().setActiveGroup(parent);\n this.getApp().hideLoading();\n }\n }\n\n /**\n * Show group selector in a dialog (like TopNav)\n */\n async showGroupSearchDialog() {\n // Create or reuse collection instance (like GroupSelectorButton does)\n const collection = new GroupList();\n\n // Create GroupSearchView instance matching GroupSelectorButton pattern\n const searchView = new GroupSearchView({\n Collection: GroupList,\n collection: collection, // Pass the collection instance\n searchFields: ['name'],\n headerText: null,\n searchPlaceholder: \"Search groups...\",\n headerIcon: null,\n maxHeight: Math.min(600, window.innerHeight - 200),\n showExitButton: false,\n showKind: true, // Show kind badges (default: true)\n parentField: 'parent', // Field containing parent object\n kindField: 'kind', // Field containing kind/type\n autoExpandRoot: true, // Auto-expand root items (default: true)\n autoExpandAll: false, // Auto-expand all nodes (default: false)\n indentSize: 20, // Pixels per level (default: 20)\n showLines: true,\n });\n\n // Create dialog\n this.groupSelectorDialog = new Dialog({\n body: searchView,\n size: 'md',\n header: null,\n noBodyPadding: true,\n scrollable: false,\n buttons: [],\n closeButton: true\n });\n\n // Listen for item selection\n searchView.on('item:selected', (evt) => {\n console.log(evt);\n this.getApp().setActiveGroup(evt.model);\n if (this.groupSelectorDialog) {\n this.groupSelectorDialog.hide();\n }\n });\n\n // Clean up dialog reference when closed\n this.groupSelectorDialog.on('hidden', () => {\n this.groupSelectorDialog.destroy();\n this.groupSelectorDialog = null;\n });\n\n // Render and show the dialog\n await this.groupSelectorDialog.render(true, document.body);\n this.groupSelectorDialog.show();\n }\n\n /**\n * Find and switch to the menu that contains the given route\n */\n autoSwitchToMenuForRoute(route) {\n // Search through all menus to find one that contains this route\n for (const [menuName, menuConfig] of this.menus) {\n if (menuConfig.groupKind && !this.getApp().activeGroup)\n continue;\n if (this.menuContainsRoute(menuConfig, route)) {\n // Switch to this menu\n this._setActiveMenu(menuName);\n this.currentRoute = route;\n\n // Clear all active states and set new active item\n this.clearAllActiveStates();\n this.setActiveItemByRoute(route);\n\n // Re-render to show changes\n this.render();\n\n console.log(`Auto-switched to menu '${menuName}' for route '${route}'`);\n\n // Emit event for any listeners\n this.emit('menu-auto-switched', {\n menuName,\n route,\n config: menuConfig,\n sidebar: this\n });\n\n return true;\n }\n }\n\n return false; // No menu found for this route\n }\n\n /**\n * Clear active state from all menu items in all menus\n */\n clearAllActiveStates() {\n for (const [menuName, menuConfig] of this.menus) {\n for (const item of menuConfig.items || []) {\n item.active = false;\n if (item.children) {\n for (const child of item.children) {\n child.active = false;\n }\n }\n }\n }\n }\n\n /**\n * Set active state for item matching the given route\n */\n setActiveItemByRoute(route) {\n const normalizeRoute = (r) => {\n if (!r) return '/';\n // Decode URL-encoded characters (like %2F -> /)\n const decoded = decodeURIComponent(r);\n return decoded.startsWith('/') ? decoded : `/${decoded}`;\n };\n\n const targetRoute = normalizeRoute(route);\n\n // Search through all menus\n for (const [menuName, menuConfig] of this.menus) {\n if (menuConfig.groupKind && !this.getApp().activeGroup)\n continue;\n for (const item of menuConfig.items || []) {\n // Check main item\n if (item.route) {\n const itemRoute = normalizeRoute(item.route);\n if (this.routesMatch(targetRoute, itemRoute)) {\n item.active = true;\n this.activeMenuItem = item;\n return true;\n }\n }\n\n // Check children\n if (item.children) {\n for (const child of item.children) {\n if (child.route) {\n const childRoute = normalizeRoute(child.route);\n if (this.routesMatch(targetRoute, childRoute)) {\n child.active = true;\n item.active = true; // Parent also active\n return true;\n }\n }\n }\n }\n }\n }\n\n return false;\n }\n\n /**\n * Check if a menu contains a specific route in its items or children\n */\n menuContainsRoute(menuConfig, route) {\n const normalizeRoute = (r) => {\n if (!r) return '/';\n // Decode URL-encoded characters (like %2F -> /)\n const decoded = decodeURIComponent(r);\n return decoded.startsWith('/') ? decoded : `/${decoded}`;\n };\n\n const targetRoute = normalizeRoute(route);\n\n // Check each item in the menu\n for (const item of menuConfig.items || []) {\n // Check main item\n if (item.route) {\n const itemRoute = normalizeRoute(item.route);\n if (this.routesMatch(targetRoute, itemRoute)) {\n return true;\n }\n }\n\n // Check children\n if (item.children) {\n for (const child of item.children) {\n if (child.route) {\n const childRoute = normalizeRoute(child.route);\n if (this.routesMatch(targetRoute, childRoute)) {\n return true;\n }\n }\n }\n }\n }\n\n return false;\n }\n\n /**\n * Check if two routes match (using same logic as isItemActive)\n */\n routesMatch(currentRoute, itemRoute) {\n return this.getApp().router.doRoutesMatch(currentRoute, itemRoute);\n }\n\n getTemplate() {\n if (this.customView) {\n return '<div class=\"sidebar-container\" id=\"sidebar-custom-view-container\"></div>';\n }\n if (this.showSearch) return this.getSearchTemplate();\n return this.getMenuTemplate();\n }\n\n getSearchTemplate() {\n return `\n <div class=\"sidebar-container\" id=\"sidebar-search-container\">\n </div>\n `;\n }\n\n getMenuTemplate() {\n return `\n <div class=\"sidebar-container\">\n {{#data.currentMenu}}\n <!-- Header -->\n {{#header}}\n <div class=\"sidebar-header\">\n {{{header}}}\n {{#showToggle}}\n <button class=\"sidebar-toggle\" data-action=\"toggle-sidebar\"\n aria-label=\"Toggle Sidebar\">\n <i class=\"bi bi-chevron-left toggle-icon\"></i>\n <i class=\"bi bi-chevron-right toggle-icon\"></i>\n </button>\n {{/showToggle}}\n </div>\n {{/header}}\n\n <!-- Navigation Items -->\n <div class=\"sidebar-body\">\n <ul class=\"nav nav-pills flex-column sidebar-nav\" id=\"sidebar-nav-menu\">\n {{#items}}\n {{>nav-item}}\n {{/items}}\n </ul>\n </div>\n\n <!-- Footer -->\n {{#footer}}\n <div class=\"sidebar-footer\">\n {{{footer}}}\n </div>\n {{/footer}}\n {{/data.currentMenu}}\n\n {{^data.currentMenu}}\n <div class=\"sidebar-empty\">\n <p class=\"text-danger text-center\">No menu configured</p>\n </div>\n {{/data.currentMenu}}\n </div>\n `;\n }\n\n /**\n * Get template partials for rendering\n */\n getPartials() {\n return {\n 'nav-item': `\n {{#isDivider}}\n {{>nav-divider}}\n {{/isDivider}}\n {{#isSpacer}}\n {{>nav-spacer}}\n {{/isSpacer}}\n {{#isLabel}}\n {{>nav-label}}\n {{/isLabel}}\n\n {{^isDivider}}\n {{^isSpacer}}\n {{^isLabel}}\n <li class=\"nav-item\">\n {{#hasChildren}}\n <!-- Item with submenu -->\n <a class=\"nav-link {{#active}}active{{/active}} has-children collapsed\"\n data-bs-toggle=\"collapse\"\n href=\"#collapse-{{id}}\"\n role=\"button\"\n aria-expanded=\"{{#active}}true{{/active}}{{^active}}false{{/active}}\"\n data-action=\"toggle-submenu\">\n {{#icon}}<i class=\"{{icon}} me-2\"></i>{{/icon}}\n <span class=\"nav-text\">{{text}}</span>\n {{#badge}}\n <span class=\"{{badge.class}} ms-auto\">{{badge.text}}</span>\n {{/badge}}\n <i class=\"bi bi-chevron-down nav-arrow ms-auto\"></i>\n </a>\n <div class=\"collapse {{#active}}show{{/active}}\" id=\"collapse-{{id}}\" data-bs-parent=\"#sidebar-nav-menu\">\n <ul class=\"nav flex-column nav-submenu\">\n {{#children}}\n <li class=\"nav-item\">\n <a class=\"nav-link {{#active}}active{{/active}}\"\n {{#action}}data-action=\"{{action}}\"{{/action}}\n {{#href}}href=\"{{href}}\"{{/href}}>\n {{#icon}}<i class=\"{{icon}} me-2\"></i>{{/icon}}\n <span class=\"nav-text\">{{text}}</span>\n {{#badge}}\n <span class=\"{{badge.class}} ms-auto\">{{badge.text}}</span>\n {{/badge}}\n </a>\n </li>\n {{/children}}\n </ul>\n </div>\n {{/hasChildren}}\n {{^hasChildren}}\n <!-- Simple item -->\n <a class=\"nav-link {{#active}}active{{/active}} {{#disabled}}disabled{{/disabled}}\"\n {{#action}}{{^disabled}}data-action=\"{{action}}\"{{/disabled}}{{/action}}\n {{#href}}{{^disabled}}href=\"{{href}}\"{{/disabled}}{{/href}}>\n {{#icon}}<i class=\"{{icon}} me-2\"></i>{{/icon}}\n <span class=\"nav-text\">{{text}}</span>\n {{#badge}}\n <span class=\"{{badge.class}} ms-auto\">{{badge.text}}</span>\n {{/badge}}\n </a>\n {{/hasChildren}}\n </li>\n {{/isLabel}}\n {{/isSpacer}}\n {{/isDivider}}\n `,\n 'nav-divider': `\n <li class=\"nav-divider-item\">\n <hr class=\"nav-divider-line\">\n </li>\n `,\n 'nav-spacer': `\n <li class=\"nav-spacer-item\"></li>\n `,\n 'nav-label': `\n <li class=\"nav-item {{className}}\">\n <div class=\"nav-text px-3\">{{text}}</div>\n </li>\n `\n };\n }\n\n getGroupHeader() {\n return this.groupHeader;\n }\n\n /**\n * Add a menu configuration\n */\n addMenu(name, config) {\n if (config.groupKind && !config.header) {\n config.header = this.getGroupHeader();\n }\n\n this.menus.set(name, {\n name,\n groupKind: config.groupKind || null,\n header: config.header || null,\n footer: config.footer || null,\n items: config.items || [],\n data: config.data || {},\n className: config.className || \"sidebar sidebar-dark\"\n });\n\n\n // Set as active if it's the first menu\n if (!this.activeMenuName) {\n this._setActiveMenu(name);\n }\n\n return this;\n }\n\n _setActiveMenu(name) {\n this.showSearch = false;\n this.activeMenuName = name;\n const config = this.getCurrentMenuConfig();\n if (config.className) {\n this.setClass(config.className);\n } else {\n this.setClass('sidebar');\n }\n }\n\n /**\n * Set the active menu\n */\n async setActiveMenu(name) {\n if (!this.menus.has(name)) {\n console.warn(`Menu '${name}' not found`);\n return this;\n }\n\n const menuConfig = this.menus.get(name);\n\n if (menuConfig.groupKind) {\n this.lastGroupMenu = menuConfig;\n // Handle group kind logic here\n if (!this.getApp().activeGroup) {\n this.showGroupSearch();\n return;\n }\n }\n\n this._setActiveMenu(name);\n await this.render();\n // Emit event\n this.emit('menu-changed', {\n menuName: name,\n config: menuConfig,\n sidebar: this\n });\n\n return this;\n }\n\n getGroupMenu(group) {\n if (!group) {\n console.warn('No group provided');\n return null;\n }\n // Find menu by group.kind\n let targetMenu = this.lastGroupMenu;\n let anyGroupMenu = null;\n if (group._.kind) {\n for (const [menuName, menuConfig] of this.menus) {\n // Check if groupKind matches\n const matches = this._groupKindMatches(menuConfig.groupKind, group._.kind);\n\n if (matches) {\n targetMenu = menuConfig;\n break;\n } else if (menuConfig.groupKind === 'any') {\n anyGroupMenu = menuConfig;\n }\n }\n }\n\n if (!targetMenu) {\n return anyGroupMenu;\n }\n return targetMenu;\n }\n\n /**\n * Check if a groupKind matches the group's kind\n * Supports both single string and array of strings\n * @param {string|string[]} groupKind - Single kind or array of kinds\n * @param {string} kind - The group's kind to match\n * @returns {boolean} True if matches\n */\n _groupKindMatches(groupKind, kind) {\n if (!groupKind || !kind) return false;\n\n // Handle array of kinds\n if (Array.isArray(groupKind)) {\n return groupKind.includes(kind);\n }\n\n // Handle single kind string\n return groupKind === kind;\n }\n\n showMenuForGroup(group) {\n if (!group) {\n console.warn('No group provided');\n return;\n }\n // Find menu by group.kind\n let targetMenu = this.getGroupMenu(group);\n\n if (!targetMenu) {\n console.warn(`No menu found for group kind: ${group.kind}`);\n return;\n }\n this._setActiveMenu(targetMenu.name);\n this.render();\n // Emit event\n this.emit('menu-changed', {\n menuName: targetMenu.name,\n config: targetMenu,\n sidebar: this\n });\n return this;\n }\n\n /**\n * Get menu configuration\n */\n getMenuConfig(name) {\n return this.menus.get(name) || null;\n }\n\n /**\n * Get current active menu configuration\n */\n getCurrentMenuConfig() {\n return this.activeMenuName ? this.menus.get(this.activeMenuName) : null;\n }\n\n /**\n * Update menu configuration\n */\n updateMenu(name, updates) {\n const menu = this.menus.get(name);\n if (!menu) {\n console.warn(`Menu '${name}' not found`);\n return this;\n }\n\n // Deep merge updates\n Object.assign(menu, updates);\n\n // Re-render if this is the active menu\n if (this.activeMenuName === name) {\n this.render();\n }\n\n return this;\n }\n\n /**\n * Remove a menu\n */\n removeMenu(name) {\n this.menus.delete(name);\n\n // If this was the active menu, switch to another or clear\n if (this.activeMenuName === name) {\n const remainingMenus = Array.from(this.menus.keys());\n this.activeMenuName = remainingMenus.length > 0 ? remainingMenus[0] : null;\n this.render();\n }\n\n return this;\n }\n\n /**\n * Get view data for template rendering\n */\n async onBeforeRender() {\n const currentMenu = this.getCurrentMenuConfig();\n\n if (!currentMenu) {\n return { currentMenu: null };\n }\n\n let subData = {\n version: this.getApp().version || null,\n group: this.getApp().activeGroup || null,\n user: this.getApp.activeUser || null\n };\n // Process menu data through template if it contains handlebars\n this.data = {\n currentMenu: {\n header: this.renderTemplateString(currentMenu.header || '', subData),\n footer: this.renderTemplateString(currentMenu.footer || '', subData),\n items: this.processNavItems(currentMenu.items, currentMenu.groupKind),\n data: currentMenu.data,\n showToggle: this.showToggle\n }\n\n };\n }\n\n async onAfterRender() {\n // Re-initialize tooltips after render, but only if collapsed\n if (this.isCollapsedState()) {\n // Small delay to ensure DOM is fully rendered\n setTimeout(() => this.initializeTooltips(), 50);\n } else {\n this.destroyTooltips();\n }\n }\n\n setCustomView(view) {\n if (this.customView) {\n this.removeChild(this.customView.id);\n }\n this.customView = view;\n if (view) {\n view.containerId = 'sidebar-custom-view-container';\n this.addChild(view);\n }\n this.render();\n return this;\n }\n\n clearCustomView() {\n if (this.customView) {\n this.removeChild(this.customView.id);\n this.customView = null;\n }\n this.render();\n return this;\n }\n\n /**\n * Process navigation items - add IDs, active states, and proper hrefs\n */\n processNavItems(items, groupKind) {\n const app = this.getApp();\n const activeUser = app?.activeUser;\n const activeGroup = app?.activeGroup;\n\n // Helper function to normalize and update route with group parameter\n const updateRouteWithGroup = (route) => {\n let normalizedRoute = route;\n \n // Convert path format (/forms) to query string format (?page=forms)\n if (route.startsWith('/') && !route.includes('?')) {\n const pageName = route.substring(1) || 'home';\n normalizedRoute = `?page=${pageName}`;\n }\n \n // Add group parameter if needed\n if (groupKind && activeGroup && activeGroup.id) {\n const separator = normalizedRoute.includes('?') ? '&' : '?';\n return `${normalizedRoute}${separator}group=${activeGroup.id}`;\n }\n return normalizedRoute;\n };\n\n return items.map((item, index) => {\n // Handle divider items\n if (item === \"\" || (typeof item === 'object' && item.divider)) {\n return {\n isDivider: true,\n id: `divider-${index}`\n };\n }\n\n // Handle spacer items\n if (typeof item === 'object' && item.spacer) {\n return {\n isSpacer: true,\n id: `spacer-${index}`\n };\n }\n\n const processedItem = { ...item };\n\n // Check permissions - skip item if user doesn't have required permissions\n if (processedItem.permissions) {\n if (!activeUser || !activeUser.hasPermission(processedItem.permissions)) {\n return null; // Will be filtered out\n }\n }\n\n // Check requiresGroupKind - skip item if group kind doesn't match\n if (processedItem.requiresGroupKind) {\n const groupKind = activeGroup?._.kind || activeGroup?.kind;\n if (!groupKind || !this._groupKindMatches(processedItem.requiresGroupKind, groupKind)) {\n return null; // Will be filtered out\n }\n }\n\n if (processedItem.kind === 'label') {\n processedItem.isLabel = true;\n if (!processedItem.id) {\n processedItem.id = `nav-label-${index}`;\n }\n return processedItem;\n }\n\n // Generate ID if not provided\n if (!processedItem.id) {\n processedItem.id = `nav-${index}`;\n }\n\n // Use route directly as href (like TopNav does)\n if (processedItem.route) {\n processedItem.href = updateRouteWithGroup(processedItem.route);\n } else if (processedItem.page) {\n // If only page is provided, convert to route format\n const baseRoute = processedItem.page.startsWith('/') ? processedItem.page : `/${processedItem.page}`;\n processedItem.href = updateRouteWithGroup(baseRoute);\n processedItem.route = processedItem.href; // Store for active matching\n }\n\n // Active state is already set on the item object, no need to calculate\n\n // Process children\n if (processedItem.children) {\n processedItem.children = processedItem.children.map(child => {\n const processedChild = { ...child };\n\n // Check permissions for child items\n if (processedChild.permissions && activeUser) {\n if (!activeUser.hasPermission(processedChild.permissions)) {\n return null; // Will be filtered out\n }\n }\n\n // Check requiresGroupKind for child items\n if (processedChild.requiresGroupKind) {\n const groupKind = activeGroup?._.kind || activeGroup?.kind;\n if (!groupKind || !this._groupKindMatches(processedChild.requiresGroupKind, groupKind)) {\n return null; // Will be filtered out\n }\n }\n\n // Use route directly as href\n if (processedChild.route) {\n processedChild.href = updateRouteWithGroup(processedChild.route);\n } else if (processedChild.page) {\n const baseRoute = processedChild.page.startsWith('/') ? processedChild.page : `/${processedChild.page}`;\n processedChild.href = updateRouteWithGroup(baseRoute);\n processedChild.route = processedChild.href;\n }\n\n // Active state is already set on the child object\n return processedChild;\n }).filter(child => child !== null); // Filter out permission-denied children\n\n // Update hasChildren flag after filtering children\n processedItem.hasChildren = !!(processedItem.children && processedItem.children.length > 0);\n } else {\n // Add hasChildren flag for template logic\n processedItem.hasChildren = false;\n }\n\n return processedItem;\n }).filter(item => item !== null); // Filter out permission-denied items\n }\n\n\n\n\n /**\n * Check if navigation item should be active (similar to TopNav)\n */\n isItemActive(item) {\n if (!item.route || !this.currentRoute) {\n return false;\n }\n\n const normalizeRoute = (route) => {\n if (!route) return '/';\n // Decode URL-encoded characters (like %2F -> /)\n const decoded = decodeURIComponent(route);\n return decoded.startsWith('/') ? decoded : `/${decoded}`;\n };\n\n const itemRoute = normalizeRoute(item.route);\n const currentRoute = normalizeRoute(this.currentRoute);\n\n if (itemRoute === '/' && currentRoute === '/') {\n return true;\n }\n\n if (itemRoute !== '/' && currentRoute !== '/') {\n return currentRoute.startsWith(itemRoute) || currentRoute === itemRoute;\n }\n\n return false;\n }\n\n /**\n * Update active item based on current route (like TopNav)\n */\n async updateActiveItem(route) {\n this.currentRoute = route;\n\n // Clear all active states and set new active item\n this.clearAllActiveStates();\n this.setActiveItemByRoute(route);\n\n await this.render();\n return this;\n }\n\n /**\n * Action handler: Toggle submenu\n */\n async handleActionToggleSubmenu(event, element) {\n const arrow = element.querySelector('.nav-arrow');\n if (arrow) {\n arrow.classList.toggle('rotated');\n }\n }\n\n /**\n * Action handler: Toggle sidebar collapsed/expanded state\n */\n async handleActionToggleSidebar(event, element) {\n this.toggleSidebar();\n }\n\n onActionShowGroupMenu(action, event, el) {\n this.setActiveMenu(\"group_default\");\n\n return false;\n }\n\n async onActionDefault(action, event, el) {\n const config = this.getCurrentMenuConfig();\n if (!config) return;\n\n // Helper to recursively search for action in items and children\n const findAndExecuteHandler = (items) => {\n for (const item of items) {\n if ((item.action == action) && item.handler) {\n item.handler(action, event, el, this.getApp());\n return true;\n }\n // Check children recursively\n if (item.children && item.children.length > 0) {\n if (findAndExecuteHandler(item.children)) {\n return true;\n }\n }\n }\n return false;\n };\n\n return findAndExecuteHandler(config.items);\n }\n\n /**\n * Get all menu names\n */\n getMenuNames() {\n return Array.from(this.menus.keys());\n }\n\n /**\n * Check if menu exists\n */\n hasMenu(name) {\n return this.menus.has(name);\n }\n\n /**\n * Clear all menus\n */\n clearMenus() {\n this.menus.clear();\n this.activeMenuName = null;\n this.render();\n return this;\n }\n\n /**\n * Set data for current menu\n */\n setMenuData(data) {\n const currentMenu = this.getCurrentMenuConfig();\n if (currentMenu) {\n currentMenu.data = { ...currentMenu.data, ...data };\n this.render();\n }\n return this;\n }\n\n /**\n * Get data for current menu\n */\n getMenuData() {\n const currentMenu = this.getCurrentMenuConfig();\n return currentMenu ? currentMenu.data : {};\n }\n\n /**\n * Setup listeners for route change events (like TopNav)\n */\n setupRouteListeners() {\n const app = this.getApp();\n if (app && app.events) {\n app.events.on([\"page:showing\"], (data) => {\n this.onRouteChanged(data);\n });\n app.events.on(\"group:changed\", (data) => {\n this.showMenuForGroup(data.group);\n });\n app.events.on(\"portal:user-changed\", (data) => {\n this.render();\n });\n }\n }\n\n /**\n * Handle route changed event - auto-switch menu and update active item\n */\n /**\n * Handle route changed event.\n *\n * Resolution order for \"which menu to show\":\n * 1. The menu that contains the new route (autoSwitchToMenuForRoute).\n * 2. The menu named by `page.sidebarMenu` (declared on the Page class).\n * 3. The first non-group menu registered — prevents \"last-menu-wins\" default.\n *\n * Pages that don't belong to any menu are called \"homeless\" pages.\n * Declare `sidebarMenu = 'menuName'` on the Page class to pin them to a menu:\n *\n * class SettingsPage extends Page {\n * sidebarMenu = 'default'; // show 'default' sidebar on this page\n * }\n */\n onRouteChanged(data) {\n if (data.page && data.page.route) {\n const route = data.page.route;\n if (this.activeMenuItem && this.routesMatch(route, this.activeMenuItem.route)) {\n return;\n }\n\n // 1. Try to auto-switch to the menu that contains this route\n const switchedMenu = this.autoSwitchToMenuForRoute(route);\n\n if (switchedMenu) {\n console.log(`Route changed to '${route}', auto-switched menu`);\n return;\n }\n\n // 2. \"Homeless\" page — route is not in any menu.\n // Check if the page class declares a preferred sidebarMenu name.\n const preferredMenu = data.page.sidebarMenu || data.page.options?.sidebarMenu || null;\n if (preferredMenu && this.menus.has(preferredMenu)) {\n this._setActiveMenu(preferredMenu);\n this.clearAllActiveStates();\n this.render();\n console.log(`Homeless route '${route}' — switched to page-declared sidebarMenu '${preferredMenu}'`);\n return;\n }\n\n // 3. Fall back to the first non-group menu so we never\n // accidentally land on the last-registered menu.\n let fallbackMenu = null;\n for (const [menuName, menuConfig] of this.menus) {\n if (!menuConfig.groupKind) {\n fallbackMenu = menuName;\n break;\n }\n }\n\n if (fallbackMenu && this.activeMenuName !== fallbackMenu) {\n this._setActiveMenu(fallbackMenu);\n console.log(`Homeless route '${route}' — fell back to first non-group menu '${fallbackMenu}'`);\n }\n\n // Always clear active states and re-render for homeless pages\n this.clearAllActiveStates();\n this.updateActiveItem(route);\n this.render();\n }\n }\n\n /**\n * Toggle sidebar between collapsed and expanded states\n */\n toggleSidebar() {\n const portalContainer = document.querySelector('.portal-container');\n if (!portalContainer) return;\n\n // Hide any visible tooltips before state change\n this.hideAllTooltips();\n\n const isCurrentlyCollapsed = portalContainer.classList.contains('collapse-sidebar');\n const isCurrentlyHidden = portalContainer.classList.contains('hide-sidebar');\n\n if (isCurrentlyHidden) {\n // Hidden -> Normal\n portalContainer.classList.remove('hide-sidebar');\n this.isCollapsed = false;\n this.destroyTooltips();\n } else if (isCurrentlyCollapsed) {\n // Collapsed -> Normal\n portalContainer.classList.remove('collapse-sidebar');\n this.isCollapsed = false;\n this.destroyTooltips();\n } else {\n // Normal -> Collapsed\n portalContainer.classList.add('collapse-sidebar');\n this.isCollapsed = true;\n // Initialize tooltips with delay to ensure DOM is ready\n setTimeout(() => this.initializeTooltips(), 150);\n }\n\n return this;\n }\n\n /**\n * Set sidebar state programmatically\n */\n setSidebarState(state) {\n const portalContainer = document.querySelector('.portal-container');\n if (!portalContainer) return this;\n\n // Remove all state classes first\n portalContainer.classList.remove('collapse-sidebar', 'hide-sidebar');\n\n switch (state) {\n case 'collapsed':\n portalContainer.classList.add('collapse-sidebar');\n this.isCollapsed = true;\n break;\n case 'hidden':\n portalContainer.classList.add('hide-sidebar');\n this.isCollapsed = false;\n break;\n case 'normal':\n default:\n this.isCollapsed = false;\n break;\n }\n\n // Handle tooltips based on state\n if (this.isCollapsed) {\n // Hide any visible tooltips first\n this.hideAllTooltips();\n // Initialize tooltips when collapsed\n setTimeout(() => this.initializeTooltips(), 100);\n } else {\n // Destroy tooltips when not collapsed\n this.destroyTooltips();\n }\n\n return this;\n }\n\n /**\n * Initialize tooltips for nav items when sidebar is collapsed\n */\n initializeTooltips() {\n // Clean up existing tooltips first\n this.destroyTooltips();\n\n // Only initialize tooltips in collapsed state\n if (!this.isCollapsedState()) {\n return this;\n }\n\n // Auto-generate tooltips from nav-text content\n const navLinks = this.element.querySelectorAll('.sidebar-nav .nav-link');\n\n navLinks.forEach((link) => {\n const navText = link.querySelector('.nav-text');\n\n if (navText && navText.textContent.trim()) {\n const tooltipText = navText.textContent.trim();\n\n // Set Bootstrap tooltip attributes\n link.setAttribute('data-bs-toggle', 'tooltip');\n link.setAttribute('data-bs-placement', 'right');\n link.setAttribute('data-bs-title', tooltipText);\n link.setAttribute('data-bs-container', 'body');\n\n // Initialize Bootstrap tooltip with better config\n if (window.bootstrap && window.bootstrap.Tooltip) {\n // Extract custom theme/size if specified\n const theme = link.getAttribute('data-tooltip-theme');\n const size = link.getAttribute('data-tooltip-size');\n\n // Build custom class list\n let customClass = '';\n if (theme) customClass += `tooltip-${theme} `;\n if (size) customClass += `tooltip-${size}`;\n\n // Build options object\n const tooltipOptions = {\n placement: 'right',\n container: 'body',\n trigger: 'hover',\n delay: { show: 500, hide: 100 },\n fallbackPlacements: ['top', 'bottom', 'left']\n };\n\n // Only add customClass if it has a value\n const trimmedClass = customClass.trim();\n if (trimmedClass) {\n tooltipOptions.customClass = trimmedClass;\n }\n\n const tooltip = new window.bootstrap.Tooltip(link, tooltipOptions);\n\n // Store tooltip instance for better management\n link._tooltipInstance = tooltip;\n\n // Add event listeners to prevent stuck tooltips\n link.addEventListener('click', () => {\n tooltip.hide();\n });\n\n link.addEventListener('blur', () => {\n tooltip.hide();\n });\n }\n }\n });\n\n // Add global event listeners to hide tooltips\n this.addTooltipHideListeners();\n\n return this;\n }\n\n destroyTooltips() {\n // Remove global tooltip hide listeners\n this.removeTooltipHideListeners();\n\n const navLinks = this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle=\"tooltip\"]');\n\n navLinks.forEach((link) => {\n // Use stored instance first, then try to get it\n const tooltipInstance = link._tooltipInstance || window.bootstrap?.Tooltip?.getInstance(link);\n if (tooltipInstance) {\n // Force hide before dispose\n tooltipInstance.hide();\n tooltipInstance.dispose();\n }\n\n // Clean up stored reference\n delete link._tooltipInstance;\n\n // Remove tooltip attributes\n link.removeAttribute('data-bs-toggle');\n link.removeAttribute('data-bs-placement');\n link.removeAttribute('data-bs-title');\n link.removeAttribute('data-bs-container');\n });\n\n return this;\n }\n\n /**\n * Get current sidebar state\n */\n getSidebarState() {\n const portalContainer = document.querySelector('.portal-container');\n if (!portalContainer) return 'normal';\n\n if (portalContainer.classList.contains('hide-sidebar')) {\n return 'hidden';\n } else if (portalContainer.classList.contains('collapse-sidebar')) {\n return 'collapsed';\n } else {\n return 'normal';\n }\n }\n\n /**\n * Check if sidebar is collapsed\n */\n isCollapsedState() {\n return this.getSidebarState() === 'collapsed';\n }\n\n /**\n * Enable/disable toggle button\n */\n setToggleEnabled(enabled) {\n this.showToggle = enabled;\n this.render();\n return this;\n }\n\n /**\n * Initialize menus from options\n */\n initializeMenus(options) {\n if (options.menus) {\n for (const menu of options.menus) {\n this.addMenu(menu.name, menu);\n }\n } else if (options.menu) {\n options.menu.name = options.menu.name || \"default\";\n this.addMenu(options.menu.name, options.menu);\n }\n }\n\n /**\n * Add global listeners to hide tooltips when needed\n */\n addTooltipHideListeners() {\n // Hide tooltips on scroll\n this._tooltipScrollHandler = () => this.hideAllTooltips();\n this.element.addEventListener('scroll', this._tooltipScrollHandler, { passive: true });\n\n // Hide tooltips on route change\n this._tooltipRouteHandler = () => this.hideAllTooltips();\n const app = this.getApp();\n\n\n // Hide tooltips on window blur\n this._tooltipBlurHandler = () => this.hideAllTooltips();\n window.addEventListener('blur', this._tooltipBlurHandler);\n\n // Hide tooltips on escape key\n this._tooltipEscapeHandler = (e) => {\n if (e.key === 'Escape') {\n this.hideAllTooltips();\n }\n };\n document.addEventListener('keydown', this._tooltipEscapeHandler);\n }\n\n /**\n * Remove global tooltip hide listeners\n */\n removeTooltipHideListeners() {\n if (this._tooltipScrollHandler) {\n this.element.removeEventListener('scroll', this._tooltipScrollHandler);\n delete this._tooltipScrollHandler;\n }\n\n if (this._tooltipBlurHandler) {\n window.removeEventListener('blur', this._tooltipBlurHandler);\n delete this._tooltipBlurHandler;\n }\n\n if (this._tooltipEscapeHandler) {\n document.removeEventListener('keydown', this._tooltipEscapeHandler);\n delete this._tooltipEscapeHandler;\n }\n }\n\n /**\n * Force hide all visible tooltips\n */\n hideAllTooltips() {\n const navLinks = this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle=\"tooltip\"]');\n navLinks.forEach((link) => {\n const tooltip = link._tooltipInstance || window.bootstrap?.Tooltip?.getInstance(link);\n if (tooltip) {\n tooltip.hide();\n }\n });\n\n // Also hide any orphaned tooltips\n const visibleTooltips = document.querySelectorAll('.tooltip.show');\n visibleTooltips.forEach(tooltip => {\n tooltip.remove();\n });\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n // Clean up tooltips\n this.destroyTooltips();\n\n // Call parent cleanup\n await super.onBeforeDestroy();\n }\n\n /**\n * Setup responsive behavior for mobile\n */\n setupResponsiveBehavior() {\n const checkMobile = () => {\n const isMobile = window.innerWidth <= 768;\n const portalContainer = document.querySelector('.portal-container');\n\n if (portalContainer) {\n if (isMobile) {\n portalContainer.classList.add('sidebar-mobile');\n } else {\n portalContainer.classList.remove('sidebar-mobile', 'sidebar-open');\n }\n }\n };\n\n // Check on load and resize\n checkMobile();\n window.addEventListener('resize', checkMobile);\n }\n\n /**\n * Static method to create a sidebar with common configuration\n */\n static createDefault(options = {}) {\n return new Sidebar({\n theme: 'sidebar-clean',\n showToggle: true,\n autoCollapseMobile: true,\n ...options\n });\n }\n\n /**\n * Static method to create a minimal sidebar\n */\n static createMinimal(options = {}) {\n return new Sidebar({\n theme: 'sidebar-clean',\n showToggle: false,\n autoCollapseMobile: false,\n ...options\n });\n }\n\n /**\n * Set sidebar theme\n */\n setSidebarTheme(theme) {\n // Remove existing theme classes\n this.removeClass('sidebar-light sidebar-dark sidebar-clean');\n\n // Add new theme\n this.sidebarTheme = theme;\n this.addClass(theme);\n\n return this;\n }\n\n /**\n * Quick method to show/hide the sidebar\n */\n show() {\n return this.setSidebarState('normal');\n }\n\n hide() {\n return this.setSidebarState('hidden');\n }\n\n collapse() {\n return this.setSidebarState('collapsed');\n }\n\n expand() {\n return this.setSidebarState('normal');\n }\n\n /**\n * Add pulse effect to toggle button\n */\n pulseToggle() {\n const toggleButton = this.element.querySelector('.sidebar-toggle');\n if (toggleButton) {\n toggleButton.classList.add('pulse');\n\n // Remove pulse after 3 seconds or first click\n const removePulse = () => {\n toggleButton.classList.remove('pulse');\n toggleButton.removeEventListener('click', removePulse);\n };\n\n toggleButton.addEventListener('click', removePulse, { once: true });\n setTimeout(removePulse, 3000);\n }\n return this;\n }\n\n /**\n * Utility method to quickly add a simple menu item\n */\n addSimpleMenuItem(menuName, text, route, icon = 'bi-circle') {\n const menu = this.menus.get(menuName);\n if (menu) {\n menu.items = menu.items || [];\n menu.items.push({\n text: text,\n route: route,\n icon: icon\n });\n\n if (this.activeMenuName === menuName) {\n this.render();\n }\n }\n return this;\n }\n\n /**\n * Utility method to quickly create and set a simple menu\n */\n setSimpleMenu(name, header, items) {\n const menu = {\n name: name,\n header: header,\n items: items\n };\n\n this.addMenu(name, menu);\n this.setActiveMenu(name);\n\n return this;\n }\n\n}\n\nexport default Sidebar;\n","/**\n * PageHeader - Displays page title, description, and actions above page content\n * Used by PortalApp to show consistent page headers\n */\n\nimport View from '@core/View.js';\n\nclass PageHeader extends View {\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'page-header',\n ...options\n });\n\n // Configuration\n this.style = options.style || 'default'; // 'default' | 'minimal' | 'breadcrumb'\n this.size = options.size || 'md'; // 'sm' | 'md' | 'lg' | 'xl'\n this.showIcon = options.showIcon !== false;\n this.showDescription = options.showDescription !== false;\n this.showBreadcrumbs = options.showBreadcrumbs || false;\n \n // Current page reference\n this.currentPage = null;\n }\n\n async getTemplate() {\n if (this.style === 'minimal') {\n return this.getMinimalTemplate();\n } else if (this.style === 'breadcrumb') {\n return this.getBreadcrumbTemplate();\n }\n return this.getDefaultTemplate();\n }\n\n getDefaultTemplate() {\n return `\n {{#data.hasPage}}\n <div class=\"page-header-content page-header-{{data.size}}\">\n <div class=\"page-header-main\">\n <div class=\"page-header-info\">\n {{#data.showIcon}}\n {{#data.pageIcon}}\n <div class=\"page-icon\">\n <i class=\"{{data.pageIcon}}\"></i>\n </div>\n {{/data.pageIcon}}\n {{/data.showIcon}}\n \n <div class=\"page-title-group\">\n <h1 class=\"page-title\">{{data.pageTitle}}</h1>\n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class=\"page-description text-muted\">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n </div>\n\n {{#data.hasActions}}\n <div class=\"page-actions\">\n {{#data.actions}}\n <button class=\"btn {{buttonClass}}\" \n data-action=\"{{action}}\"\n type=\"button\">\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n </div>\n {{/data.hasPage}}\n `;\n }\n\n getMinimalTemplate() {\n return `\n {{#data.hasPage}}\n <div class=\"page-header-content page-header-minimal\">\n <h1 class=\"page-title\">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class=\"{{data.pageIcon}} me-2\"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n </div>\n {{/data.hasPage}}\n `;\n }\n\n getBreadcrumbTemplate() {\n return `\n {{#data.hasPage}}\n <div class=\"page-header-content page-header-breadcrumb\">\n {{#data.showBreadcrumbs}}\n <nav aria-label=\"breadcrumb\">\n <ol class=\"breadcrumb mb-2\">\n {{#data.breadcrumbs}}\n <li class=\"breadcrumb-item {{#active}}active{{/active}}\">\n {{#href}}<a href=\"{{href}}\">{{label}}</a>{{/href}}\n {{^href}}{{label}}{{/href}}\n </li>\n {{/data.breadcrumbs}}\n </ol>\n </nav>\n {{/data.showBreadcrumbs}}\n \n <div class=\"d-flex justify-content-between align-items-start\">\n <h1 class=\"page-title\">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class=\"{{data.pageIcon}} me-2\"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n \n {{#data.hasActions}}\n <div class=\"page-actions\">\n {{#data.actions}}\n <button class=\"btn {{buttonClass}}\" \n data-action=\"{{action}}\"\n type=\"button\">\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n \n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class=\"page-description text-muted mt-2\">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n {{/data.hasPage}}\n `;\n }\n\n async onBeforeRender() {\n await super.onBeforeRender();\n\n const page = this.currentPage;\n const hasPage = !!page;\n\n // Debug logging\n if (page) {\n console.log('PageHeader page:', {\n title: page.title,\n displayName: page.displayName,\n name: page.name,\n pageName: page.pageName,\n icon: page.icon,\n pageIcon: page.pageIcon,\n pageDescription: page.pageDescription,\n description: page.description\n });\n }\n\n // Get headerActions from page options, instance, or constructor.prototype\n const headerActions = page?.options?.headerActions ||\n page?.headerActions || \n page?.constructor?.prototype?.headerActions || \n [];\n\n this.data = {\n hasPage,\n pageTitle: page?.title || page?.displayName || page?.name || page?.pageName || '',\n pageIcon: page?.icon || page?.pageIcon || '',\n pageDescription: page?.pageDescription || page?.description || '',\n showIcon: this.showIcon,\n showDescription: this.showDescription,\n showBreadcrumbs: this.showBreadcrumbs,\n breadcrumbs: page?.options?.breadcrumbs || page?.breadcrumbs || [],\n actions: headerActions,\n hasActions: headerActions.length > 0,\n size: this.size\n };\n \n console.log('PageHeader data:', this.data);\n }\n\n /**\n * Set the current page to display\n */\n async setPage(page) {\n console.log('PageHeader.setPage called with:', page?.pageName || page?.name || 'no page');\n this.currentPage = page;\n // Always render if we have a page, even if not yet mounted\n // This handles the case where setPage is called during initial app setup\n if (page) {\n console.log('PageHeader.setPage calling render()');\n await this.render();\n console.log('PageHeader.setPage render() complete');\n }\n }\n\n /**\n * Get the current page\n */\n getPage() {\n return this.currentPage;\n }\n\n /**\n * Handle action clicks from page header buttons\n */\n async onActionDefault(action, event, element) {\n // Emit to page if it has a handler\n if (this.currentPage && typeof this.currentPage.onHeaderAction === 'function') {\n await this.currentPage.onHeaderAction(action, event, element);\n return true;\n }\n\n // Emit event for parent to handle\n this.emit('action', {\n action,\n event,\n element,\n page: this.currentPage\n });\n\n return false;\n }\n}\n\nexport default PageHeader;\n","/**\n * DeniedPage - Access Denied page for MOJO Framework\n * Displays when a user attempts to access a page without proper permissions\n */\n\nimport Page from '@core/Page.js';\n\nclass DeniedPage extends Page {\n constructor(options = {}) {\n super({\n pageName: 'Access Denied',\n route: '/denied',\n title: 'Access Denied',\n pageIcon: 'bi bi-shield-x',\n template: `\n <div class=\"container mt-5\">\n <div class=\"row justify-content-center\">\n <div class=\"col-md-8 col-lg-6\">\n <div class=\"text-center mb-4\">\n <i class=\"bi bi-shield-x text-muted\" style=\"font-size: 3rem;\"></i>\n <h2 class=\"mt-3 mb-2\">Access Denied</h2>\n <p class=\"text-muted\">You don't have permission to access this page.</p>\n </div>\n\n {{#deniedPage}}\n <div class=\"card border-0 shadow-sm mb-4\">\n <div class=\"card-body\">\n <h6 class=\"card-subtitle mb-2 text-muted\">Requested Page</h6>\n <h5 class=\"card-title\">\n <i class=\"{{pageIcon}} me-2\"></i>\n {{displayName}}\n </h5>\n {{#route}}\n <p class=\"card-text text-muted small\">{{route}}</p>\n {{/route}}\n {{#description}}\n <p class=\"card-text\">{{description}}</p>\n {{/description}}\n\n {{#requiredPermissions}}\n <div class=\"mt-3\">\n <h6 class=\"mb-2\">Required Permissions:</h6>\n {{#permissions}}\n <span class=\"badge bg-light text-dark me-1 mb-1\">{{.}}</span>\n {{/permissions}}\n {{^permissions}}\n <span class=\"text-muted small\">Authentication required</span>\n {{/permissions}}\n </div>\n {{/requiredPermissions}}\n </div>\n </div>\n {{/deniedPage}}\n\n <div class=\"d-grid gap-2 d-md-flex justify-content-md-center\">\n <button type=\"button\" class=\"btn btn-primary\" data-action=\"go-back\">\n <i class=\"bi bi-arrow-left me-1\"></i>\n Go Back\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" data-action=\"go-home\">\n <i class=\"bi bi-house me-1\"></i>\n Home\n </button>\n {{#showLogin}}\n <button type=\"button\" class=\"btn btn-outline-primary\" data-action=\"login\">\n <i class=\"bi bi-box-arrow-in-right me-1\"></i>\n Login\n </button>\n {{/showLogin}}\n </div>\n\n {{#currentUser}}\n <div class=\"text-center mt-4\">\n <small class=\"text-muted\">\n Logged in as <strong>{{username}}</strong>\n </small>\n </div>\n {{/currentUser}}\n </div>\n </div>\n </div>\n `,\n ...options\n });\n\n // Store the denied page instance\n this.deniedPage = null;\n this.deniedPageOptions = null;\n }\n\n /**\n * Handle route parameters - expect denied page info\n */\n async onParams(params = {}, query = {}) {\n await super.onParams(params, query);\n\n // If page info is passed in params\n if (params.page) {\n this.deniedPage = params.page;\n this.deniedPageOptions = params.page.options || params.page.pageOptions || {};\n } else if (query.page) {\n // Handle page name from query string\n this.deniedPageName = query.page;\n }\n }\n\n /**\n * Set the denied page instance\n */\n setDeniedPage(pageInstance) {\n this.deniedPage = pageInstance;\n this.deniedPageOptions = pageInstance?.options || pageInstance?.pageOptions || {};\n return this;\n }\n\n /**\n * Get view data for template rendering\n */\n async getViewData() {\n const app = this.getApp();\n\n // Get current user info\n const currentUser = app?.activeUser || app?.getCurrentUser?.() || null;\n\n // Process denied page info\n let deniedPageInfo = null;\n if (this.deniedPage) {\n const permissions = this.deniedPageOptions?.permissions ||\n this.deniedPage.options?.permissions ||\n this.deniedPage.pageOptions?.permissions;\n\n deniedPageInfo = {\n displayName: this.deniedPage.displayName || this.deniedPage.pageName || this.deniedPage.title || 'Unknown Page',\n pageName: this.deniedPage.pageName,\n route: this.deniedPage.route,\n description: this.deniedPage.pageDescription || this.deniedPage.description,\n pageIcon: this.deniedPage.pageIcon || 'bi bi-file-text',\n requiredPermissions: permissions ? {\n permissions: Array.isArray(permissions) ? permissions : [permissions]\n } : null\n };\n } else if (this.deniedPageName) {\n deniedPageInfo = {\n displayName: this.deniedPageName,\n pageName: this.deniedPageName,\n pageIcon: 'bi bi-file-text'\n };\n }\n\n return {\n deniedPage: deniedPageInfo,\n currentUser: currentUser ? {\n username: currentUser.username || currentUser.name || currentUser.email || 'Unknown User',\n name: currentUser.name,\n email: currentUser.email\n } : null,\n showLogin: !currentUser // Show login button if not authenticated\n };\n }\n\n /**\n * Handle going back to previous page\n */\n async handleActionGoBack(event, element) {\n event.preventDefault();\n\n // Try to go back in browser history\n if (window.history.length > 1) {\n window.history.back();\n } else {\n // Fallback to home page\n await this.handleActionGoHome(event, element);\n }\n }\n\n /**\n * Handle navigation to home page\n */\n async handleActionGoHome(event, element) {\n event.preventDefault();\n\n const app = this.getApp();\n if (app) {\n await app.navigateToDefault();\n } else {\n // Fallback navigation\n window.location.href = '/';\n }\n }\n\n /**\n * Handle login action\n */\n async handleActionLogin(event, element) {\n event.preventDefault();\n\n const app = this.getApp();\n\n // Try to navigate to login page\n if (app) {\n try {\n await app.showPage('login');\n } catch (error) {\n // If login page doesn't exist, try auth route\n try {\n await app.navigate('/login');\n } catch (navError) {\n // Fallback - emit login required event\n this.emit('login-required', {\n returnUrl: this.deniedPage?.route || window.location.pathname\n });\n\n // Show message if no handlers\n setTimeout(() => {\n app?.showInfo?.('Please contact your administrator for access.');\n }, 100);\n }\n }\n }\n }\n\n /**\n * Called when entering this page\n */\n async onEnter() {\n await super.onEnter();\n\n // Set appropriate page title\n const pageName = this.deniedPage?.pageName || this.deniedPageName;\n if (pageName) {\n this.setMeta({\n title: `Access Denied - ${pageName}`\n });\n }\n\n // Log access denial for security monitoring\n console.warn('Access denied to page:', {\n page: this.deniedPage?.pageName || this.deniedPageName,\n route: this.deniedPage?.route,\n permissions: this.deniedPageOptions?.permissions,\n timestamp: new Date().toISOString()\n });\n }\n\n /**\n * Static helper to show access denied for a specific page\n */\n static showForPage(app, pageInstance) {\n const deniedPage = new DeniedPage();\n deniedPage.setDeniedPage(pageInstance);\n return app.showPage(deniedPage);\n }\n}\n\nexport default DeniedPage;\n","/**\n * NotFoundPage - 404 Not Found page for MOJO Framework\n * Displays when a user attempts to access a non-existent page or route\n */\n\nimport Page from '@core/Page.js';\n\nclass NotFoundPage extends Page {\n constructor(options = {}) {\n super({\n pageName: '404',\n route: '/404',\n title: '404 - Page Not Found',\n pageIcon: 'bi bi-search',\n template: `\n <div class=\"container mt-5\">\n <div class=\"row justify-content-center\">\n <div class=\"col-md-8 col-lg-6\">\n <div class=\"text-center mb-4\">\n <i class=\"bi bi-search text-muted\" style=\"font-size: 3rem;\"></i>\n <h2 class=\"mt-3 mb-2\">Page Not Found</h2>\n <p class=\"text-muted\">The page you're looking for doesn't exist.</p>\n </div>\n\n {{#path}}\n <div class=\"card border-0 shadow-sm mb-4\">\n <div class=\"card-body text-center\">\n <h6 class=\"card-subtitle mb-2 text-muted\">Requested Path</h6>\n <code class=\"text-primary\">{{path}}</code>\n </div>\n </div>\n {{/path}}\n\n <div class=\"d-grid gap-2 d-md-flex justify-content-md-center\">\n <button type=\"button\" class=\"btn btn-primary\" data-action=\"go-back\">\n <i class=\"bi bi-arrow-left me-1\"></i>\n Go Back\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" data-action=\"go-home\">\n <i class=\"bi bi-house me-1\"></i>\n Home\n </button>\n </div>\n </div>\n </div>\n </div>\n `,\n ...options\n });\n\n // Store the not found path\n this.path = null;\n }\n\n /**\n * Handle route parameters\n */\n async onParams(params = {}, query = {}) {\n await super.onParams(params, query);\n\n // Store path from params or query\n if (params.path) {\n this.path = params.path;\n }\n if (query.path) {\n this.path = query.path;\n }\n }\n\n /**\n * Set not found path\n */\n setInfo(path) {\n this.path = path || null;\n return this;\n }\n\n /**\n * Handle going back to previous page\n */\n async handleActionGoBack(event, _element) {\n event.preventDefault();\n\n // Try to go back in browser history\n if (window.history.length > 1) {\n window.history.back();\n } else {\n // Fallback to home page\n await this.handleActionGoHome(event, _element);\n }\n }\n\n /**\n * Handle navigation to home page\n */\n async handleActionGoHome(event, _element) {\n event.preventDefault();\n\n const app = this.getApp();\n if (app) {\n await app.navigateToDefault();\n } else {\n // Fallback navigation\n window.location.href = '/';\n }\n }\n\n /**\n * Called when entering this page\n */\n async onEnter() {\n await super.onEnter();\n\n // Set appropriate page title\n if (this.path) {\n this.setMeta({\n title: `404 - ${this.path} Not Found`\n });\n }\n\n // Log 404 for analytics\n console.warn('404 Not Found:', {\n path: this.path,\n timestamp: new Date().toISOString()\n });\n }\n\n /**\n * Static helper to show 404 for a specific path\n */\n static showForPath(app, path) {\n const notFoundPage = new NotFoundPage();\n notFoundPage.setInfo(path);\n return notFoundPage.render(); // Just render, don't navigate\n }\n}\n\nexport default NotFoundPage;\n","/**\n * PortalApp - Complete portal application extending WebApp\n * Provides built-in navigation, sidebar, and content management\n * Clean, simple implementation that reuses WebApp and View logic\n */\n\nimport WebApp from '@core/WebApp.js';\nimport TopNav from '@core/views/navigation/TopNav.js';\nimport Sidebar from '@core/views/navigation/Sidebar.js';\nimport PageHeader from '@core/views/navigation/PageHeader.js';\nimport DeniedPage from '@core/pages/DeniedPage.js';\nimport TokenManager from '@core/services/TokenManager.js';\nimport {User} from '@core/models/User.js';\nimport {Group } from '@core/models/Group.js';\nimport {Member} from '@core/models/Member.js';\nimport NotFoundPage from '@core/pages/NotFoundPage.js';\nimport ToastService from '@core/services/ToastService.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\n\nexport default class PortalApp extends WebApp {\n constructor(config = {}) {\n // Pass core WebApp config through\n super(config);\n\n this.sidebarConfig = config.sidebar;\n // Portal-specific configuration (clean flat structure)\n // if (config.sidebar && config.sidebar.menus) {\n // this.sidebarConfig.menus = config.sidebar.menus;\n // this.sidebarConfig.groupSelectorMode = config.sidebar.groupSelectorMode || \"inline\";\n // if (config.sidebar.groupHeader) {\n // this.sidebarConfig.groupHeader = config.sidebar.groupHeader;\n // }\n // } else if (config.sidebar.menu) {\n // this.sidebarConfig.menu = config.sidebar.menu;\n // } else if (config.sidebar.items) {\n // this.sidebarConfig.menu = config.sidebar;\n // }\n\n this.topbarConfig = config.topbar || {};\n\n // Legacy support - topnav -> topbar\n if (config.topnav && !config.topbar) {\n this.topbarConfig = config.topnav;\n }\n\n // Page header configuration\n this.showPageHeader = config.showPageHeader || false;\n this.pageHeaderConfig = config.pageHeader || {};\n\n // Portal components\n this.sidebar = null;\n this.topbar = null;\n this.topnav = null; // Legacy reference\n this.pageHeader = null;\n this.tokenManager = new TokenManager();\n\n // Active group management\n this.activeGroup = null;\n // Portal state - Load from localStorage first, then fallback to config\n if (!this.isMobile()) {\n this.sidebarCollapsed = this.loadSidebarState() ??\n (this.sidebarConfig.defaultCollapsed || false);\n } else {\n this.sidebarCollapsed = this.sidebarConfig.defaultCollapsed || false;\n }\n this.setupPageContainer();\n\n this.toast = new ToastService();\n this.Dialog = Dialog;\n\n this.registerPage(\"denied\", DeniedPage);\n this.registerPage(\"404\", NotFoundPage);\n }\n\n /**\n * Override WebApp start to setup portal layout\n */\n async start() {\n // Call parent start (handles router, error handling, etc.)\n // Setup router\n await this.checkAuthStatus();\n\n this.events.on('auth:unauthorized', () => {\n this.tokenManager.clearTokens();\n this.rest.clearAuth();\n this.setActiveUser(null);\n return;\n });\n\n this.events.on('auth:logout', () => {\n this.tokenManager.clearTokens();\n this.rest.clearAuth();\n this.setActiveUser(null);\n return;\n });\n\n this.events.on(\"browser:focus\", () => {\n if (!this.activeUser) return;\n this.tokenManager.checkAndRefreshTokens(this);\n });\n\n this.events.on('portal:action', this.onPortalAction.bind(this));\n\n if (this.activeUser) {\n // Check and load active group after auth\n await this.checkActiveGroup();\n }\n\n\n await this.setupRouter();\n\n // Mark as started\n this.isStarted = true;\n\n // Emit app ready event\n this.events.emit('app:ready', { app: this });\n\n\n }\n\n async checkAuthStatus() {\n const tokenStatus = this.tokenManager.checkTokenStatus();\n\n // Handle logout scenarios\n if (tokenStatus.action === 'logout') {\n this.events.emit('auth:unauthorized', { app: this });\n return false;\n }\n\n // Handle refresh scenarios - attempt refresh if needed\n if (tokenStatus.action === 'refresh') {\n const refreshed = await this.tokenManager.checkAndRefreshTokens(this);\n if (!refreshed) {\n // If refresh failed, checkAndRefreshTokens already handled logout\n return false;\n }\n }\n\n // At this point we have a valid token\n const token = this.tokenManager.getTokenInstance();\n\n // If user already loaded, just start auto-refresh and return\n if (this.activeUser) {\n this.tokenManager.startAutoRefresh(this);\n return true;\n }\n\n // Load user data\n this.rest.setAuthToken(token.token);\n const user = new User({ id: token.getUserId() });\n const resp = await user.fetch();\n if (!resp.success) {\n this.tokenManager.clearTokens();\n this.events.emit('auth:unauthorized', { app: this, error: resp.error });\n return false;\n }\n\n this.setActiveUser(user);\n this.tokenManager.startAutoRefresh(this);\n return true;\n }\n\n /**\n * Check and load active group from storage\n */\n async checkActiveGroup() {\n // First check URL search params for group parameter\n const urlParams = new URLSearchParams(window.location.search);\n const urlGroupId = urlParams.get('group');\n\n // Determine which group ID to use: URL param takes priority\n const groupId = urlGroupId || this.loadActiveGroupId();\n\n if (groupId) {\n try {\n const group = new Group({ id: groupId });\n const resp = await group.fetch();\n if (!resp.success || !resp.data.status) {\n this.clearActiveGroup();\n console.warn('Failed to load active group:', resp.statusText);\n return;\n }\n\n this.activeGroup = group;\n // If we got the group from URL, save it as the new active group\n if (urlGroupId) {\n this.saveActiveGroupId(groupId);\n }\n\n if (this.activeUser) {\n this.activeUser.member = new Member();\n await this.activeUser.member.fetchForGroup(group.id);\n }\n\n // Emit event that group was loaded\n this.events.emit('group:loaded', { group: this.activeGroup });\n\n\n } catch (error) {\n console.warn('Failed to load active group:', error);\n // If URL group failed, try to clear it and fall back to stored group\n if (urlGroupId && !this.loadActiveGroupId()) {\n // URL group failed and no stored group, clear everything\n this.clearActiveGroupId();\n } else if (urlGroupId) {\n // URL group failed but we have a stored group, try that instead\n const storedGroupId = this.loadActiveGroupId();\n if (storedGroupId && storedGroupId !== urlGroupId) {\n try {\n const fallbackGroup = new Group({ id: storedGroupId });\n await fallbackGroup.fetch();\n this.activeGroup = fallbackGroup;\n this.events.emit('group:loaded', { group: this.activeGroup });\n\n } catch (fallbackError) {\n console.warn('Fallback to stored group also failed:', fallbackError);\n this.clearActiveGroupId();\n }\n }\n }\n }\n }\n }\n\n\n /**\n * Set the active group\n */\n async setActiveGroup(group) {\n const previousGroup = this.activeGroup;\n this.activeGroup = group;\n\n // Save to storage\n if (group && group.get('id')) {\n this.saveActiveGroupId(group.get('id'));\n } else {\n this.clearActiveGroupId();\n }\n\n if (this.activeUser) {\n this.activeUser.member = new Member();\n await this.activeUser.member.fetchForGroup(group.id);\n }\n\n // Emit event\n this.events.emit('group:changed', {\n group,\n previousGroup,\n app: this\n });\n\n const page = this.getCurrentPage();\n if (page) {\n page.onGroupChange(group);\n }\n\n this.router.updateUrl({group:group.id}, { replace: true });\n\n return this;\n }\n\n /**\n * Get the active group\n */\n getActiveGroup() {\n return this.activeGroup;\n }\n\n /**\n * Clear the active group\n */\n async clearActiveGroup() {\n const previousGroup = this.activeGroup;\n this.activeGroup = null;\n this.clearActiveGroupId();\n // Emit event\n this.events.emit('group:cleared', {\n previousGroup,\n app: this\n });\n return this;\n }\n\n /**\n * Save active group ID to localStorage\n */\n saveActiveGroupId(groupId) {\n try {\n const key = this.getActiveGroupStorageKey();\n localStorage.setItem(key, groupId.toString());\n } catch (error) {\n console.warn('Failed to save active group ID:', error);\n }\n }\n\n /**\n * Load active group ID from localStorage\n */\n loadActiveGroupId() {\n try {\n const key = this.getActiveGroupStorageKey();\n return localStorage.getItem(key);\n } catch (error) {\n console.warn('Failed to load active group ID:', error);\n return null;\n }\n }\n\n /**\n * Clear active group ID from localStorage\n */\n clearActiveGroupId() {\n try {\n const key = this.getActiveGroupStorageKey();\n localStorage.removeItem(key);\n } catch (error) {\n console.warn('Failed to clear active group ID:', error);\n }\n }\n\n /**\n * Get storage key for active group ID\n */\n getActiveGroupStorageKey() {\n return `active_group_id`;\n }\n\n /**\n * Set portal profile to localStorage\n */\n setPortalProfile(profile) {\n try {\n localStorage.setItem('portal_profile', profile);\n } catch (error) {\n console.warn('Failed to save portal profile:', error);\n }\n }\n\n /**\n * Check if user needs to select a group\n */\n needsGroupSelection() {\n return !this.activeGroup;\n }\n\n /**\n * Setup layout based on configuration\n */\n setupPageContainer() {\n const container = typeof this.container === 'string'\n ? document.querySelector(this.container)\n : this.container;\n\n if (!container) {\n throw new Error(`Portal container not found: ${this.container}`);\n }\n\n // Create clean portal layout\n const showSidebar = this.sidebarConfig && Object.keys(this.sidebarConfig).length > 0;\n const showTopbar = this.topbarConfig && Object.keys(this.topbarConfig).length > 0;\n\n // If page header is enabled, wrap content in two containers\n const contentMarkup = this.showPageHeader ? `\n <div class=\"portal-content\" id=\"portal-content\">\n <div id=\"page-header\"></div>\n <div id=\"page-container\">\n <!-- Pages render here -->\n </div>\n </div>\n ` : `\n <div class=\"portal-content\" id=\"page-container\">\n <!-- Pages render here -->\n </div>\n `;\n\n container.innerHTML = `\n <div class=\"portal-layout hide-sidebar\">\n ${showSidebar ? '<div id=\"portal-sidebar\"></div>' : ''}\n <div class=\"portal-body\">\n ${showTopbar ? '<div id=\"portal-topnav\"></div>' : ''}\n ${contentMarkup}\n </div>\n </div>\n `;\n\n // Set page container for WebApp\n this.pageContainer = '#page-container';\n\n // Add portal CSS classes and apply saved state\n container.classList.add('portal-container');\n\n // Setup page container\n this.setupPortalComponents();\n\n // Apply the saved sidebar state\n this.applySidebarState(container);\n }\n\n /**\n * Setup portal components\n */\n async setupPortalComponents() {\n await this.setupSidebar();\n await this.setupTopbar();\n await this.setupPageHeader();\n this.setupPortalEvents();\n }\n\n /**\n * Setup sidebar component\n */\n async setupSidebar() {\n if (!this.sidebarConfig || Object.keys(this.sidebarConfig).length === 0) return;\n\n this.sidebar = new Sidebar({\n containerId: 'portal-sidebar',\n ...this.sidebarConfig\n });\n\n await this.sidebar.render();\n }\n\n /**\n * Setup topbar component\n */\n async setupTopbar() {\n if (!this.topbarConfig || Object.keys(this.topbarConfig).length === 0) return;\n\n // Map config to TopNav format\n this.topbar = new TopNav({\n containerId: \"portal-topnav\",\n brandText: this.topbarConfig.brand || this.brand || this.title,\n brandRoute: this.topbarConfig.brandRoute || '/',\n brandIcon: this.topbarConfig.brandIcon || this.brandIcon,\n navItems: this.topbarConfig.leftItems || [],\n rightItems: this.topbarConfig.rightItems || [],\n displayMode: this.topbarConfig.displayMode || 'both',\n showSidebarToggle: this.topbarConfig.showSidebarToggle || false,\n ...this.topbarConfig\n });\n\n await this.topbar.render();\n\n // Legacy support\n this.topnav = this.topbar;\n }\n\n /**\n * Setup page header component\n */\n async setupPageHeader() {\n if (!this.showPageHeader) return;\n\n this.pageHeader = new PageHeader({\n containerId: 'page-header',\n style: this.pageHeaderConfig.style || 'default',\n showIcon: this.pageHeaderConfig.showIcon !== false,\n showDescription: this.pageHeaderConfig.showDescription !== false,\n showBreadcrumbs: this.pageHeaderConfig.showBreadcrumbs || false,\n ...this.pageHeaderConfig\n });\n\n // Render into the portal-content container\n const headerContainer = document.getElementById('page-header');\n if (headerContainer) {\n await this.pageHeader.render(true, headerContainer);\n }\n }\n\n /**\n * Setup portal event handling\n */\n setupPortalEvents() {\n // Handle sidebar toggle via event delegation\n document.addEventListener('click', (event) => {\n if (event.target.closest('[data-action=\"toggle-sidebar\"]')) {\n event.preventDefault();\n this.toggleSidebar();\n }\n });\n\n // Handle responsive changes\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n this.handleResponsive();\n });\n resizeObserver.observe(document.body);\n this._resizeObserver = resizeObserver;\n } else {\n // Fallback for older browsers\n this._resizeHandler = () => this.handleResponsive();\n window.addEventListener('resize', this._resizeHandler);\n }\n\n // Initial responsive setup\n this.handleResponsive();\n }\n\n /**\n * Toggle sidebar state\n */\n toggleSidebar() {\n if (!this.sidebar) return;\n\n const container = document.querySelector('.portal-container');\n const isMobile = this.isMobile();\n\n if (isMobile) {\n container.classList.toggle('hide-sidebar');\n } else {\n container.classList.toggle('collapse-sidebar');\n this.sidebarCollapsed = !this.sidebarCollapsed;\n\n // Save the new state\n this.saveSidebarState(this.sidebarCollapsed);\n }\n\n this.events.emit('sidebar:toggled', {\n collapsed: this.sidebarCollapsed,\n mobile: isMobile\n });\n }\n\n /**\n * Handle responsive layout\n */\n handleResponsive() {\n const container = document.querySelector('.portal-container');\n if (!container) return;\n const isMobile = this.isMobile();\n\n if (isMobile) {\n container.classList.add('mobile-layout');\n if (!container.classList.contains('hide-sidebar')) {\n container.classList.add('hide-sidebar');\n }\n } else {\n container.classList.remove('mobile-layout', 'hide-sidebar');\n }\n\n this.events.emit('responsive:changed', { mobile: isMobile });\n }\n\n getPortalContainer() {\n return document.querySelector('.portal-container');\n }\n\n isMobile() {\n return window.innerWidth < 768;\n }\n\n hasMobileLayout() {\n return this.getPortalContainer().classList.contains('mobile-layout');\n }\n\n /**\n * Override showPage to update navigation\n */\n async showPage(page, query = {}, params = {}, options = {}) {\n const result = await super.showPage(page, query, params, options);\n\n if (this.hasMobileLayout()) {\n this.getPortalContainer().classList.add('hide-sidebar');\n }\n\n if (this.currentPage) {\n this.updateNavigation(this.currentPage);\n }\n\n return result;\n }\n\n /**\n * Update navigation active states\n */\n updateNavigation(page) {\n // Update sidebar active state\n if (this.sidebar && this.sidebar.setActivePage) {\n this.sidebar.setActivePage(page.route);\n }\n\n // Update topbar active state\n if (this.topbar && this.topbar.setActivePage) {\n this.topbar.setActivePage(page.route);\n }\n\n // Update page header\n if (this.pageHeader) {\n this.pageHeader.setPage(page);\n }\n\n this.events.emit('portal:page-changed', { page });\n }\n\n /**\n * Set active user\n */\n setActiveUser(user) {\n this.activeUser = user;\n\n if (this.topbar) {\n this.topbar.setUser(user);\n }\n\n\n this.events.emit('portal:user-changed', { user });\n }\n\n /**\n * Get the active user (for backward compatibility)\n */\n getActiveUser() {\n return this.activeUser;\n }\n\n /**\n * Save sidebar state to localStorage\n */\n saveSidebarState(collapsed) {\n try {\n const key = this.getSidebarStorageKey();\n localStorage.setItem(key, JSON.stringify(collapsed));\n } catch (error) {\n console.warn('Failed to save sidebar state:', error);\n }\n }\n\n /**\n * Load sidebar state from localStorage\n */\n loadSidebarState() {\n try {\n const key = this.getSidebarStorageKey();\n const saved = localStorage.getItem(key);\n return saved !== null ? JSON.parse(saved) : null;\n } catch (error) {\n console.warn('Failed to load sidebar state:', error);\n return null;\n }\n }\n\n /**\n * Get storage key for sidebar state (allows multiple apps on same domain)\n */\n getSidebarStorageKey() {\n // Use app title/name to create unique key\n const appKey = this.title ? this.title.replace(/\\s+/g, '_').toLowerCase() : 'portal_app';\n return `${appKey}_sidebar_collapsed`;\n }\n\n /**\n * Apply saved sidebar state to the UI\n */\n applySidebarState(container = null) {\n if (!container) {\n container = document.querySelector('.portal-container');\n }\n\n if (!container) return;\n\n if (this.sidebarCollapsed) {\n container.classList.add('collapse-sidebar');\n } else {\n container.classList.remove('collapse-sidebar');\n }\n }\n\n /**\n * Clear saved sidebar state\n */\n clearSidebarState() {\n try {\n const key = this.getSidebarStorageKey();\n localStorage.removeItem(key);\n } catch (error) {\n console.warn('Failed to clear sidebar state:', error);\n }\n }\n\n async changePassword() {\n const data = await this.showForm({\n title: \"Change Password\",\n fields: [\n {\n name: 'current_password', type: 'password',\n label: 'Current Password', required: true,\n showToggle: true, // default, can omit\n strengthMeter: true,\n capsLockWarning: true,\n\n },\n {\n name: 'new_password', type: 'password', label: 'New Password', required: true,\n showToggle: true,\n passwordUsage: 'new', // sets autocomplete to 'new-password'\n\n strengthMeter: true,\n capsLockWarning: true,\n attributes: {\n // optional, override autocomplete if needed\n autocomplete: 'new-password'\n }\n },\n {\n name: 'confirm_password', type: 'password', label: 'Confirm Password', required: true,\n showToggle: true,\n passwordUsage: 'new', // sets autocomplete to 'new-password'\n\n strengthMeter: true,\n capsLockWarning: true,\n attributes: {\n // optional, override autocomplete if needed\n // autocomplete: 'new-password'\n }\n }\n ],\n submitLabel: 'Change Password'\n });\n if (data) {\n if (data.new_password === data.confirm_password) {\n // Perform password change logic here\n const resp = await this.activeUser.save(data);\n if (resp.status === 200) {\n this.toast.success('Password changed successfully');\n } else {\n this.toast.error('Failed to change password');\n }\n } else {\n this.toast.error('Passwords do not match');\n }\n }\n }\n\n onPortalAction(action) {\n switch (action.action) {\n case 'logout':\n this.tokenManager.clearTokens();\n this.rest.clearAuth();\n this.setActiveUser(null);\n break;\n case 'profile':\n this.showProfile();\n break;\n case 'change-password':\n this.changePassword();\n break;\n default:\n console.warn(`Unknown portal action: ${action}`);\n }\n }\n\n async showProfile() {\n if (!this.activeUser) {\n this.showError(\"No user is currently logged in\");\n return;\n }\n\n try {\n\n\n // Show profile edit form with automatic model saving\n const result = await Dialog.showModelForm({\n title: 'Edit Profile',\n size: 'lg',\n fileHandling: 'base64',\n model: this.activeUser,\n fields: [\n // Profile Header\n {\n type: 'header',\n text: 'Profile Information',\n level: 4,\n class: 'text-primary mb-3'\n },\n\n // Avatar and Basic Info\n {\n type: 'group',\n columns: { xs: 12, md: 4 },\n title: 'Avatar',\n fields: [\n {\n type: 'image',\n name: 'avatar',\n size: 'lg',\n imageSize: { width: 200, height: 200 },\n placeholder: 'Upload your avatar',\n help: 'Square images work best'\n }\n ]\n },\n\n // Profile Details\n {\n type: 'group',\n columns: { xs: 12, md: 8 },\n title: 'Details',\n fields: [\n {\n type: 'text',\n name: 'display_name',\n label: 'Display Name',\n required: true,\n columns: 12,\n placeholder: 'Enter first name'\n },\n {\n type: 'email',\n name: 'email',\n label: 'Email Address',\n required: true,\n columns: 8,\n placeholder: 'your.email@example.com'\n },\n {\n type: 'tel',\n name: 'phone_number',\n label: 'Phone Number',\n columns: 4,\n placeholder: '(555) 123-4567'\n },\n ]\n },\n\n // Account Settings\n {\n type: 'group',\n columns: 12,\n title: 'Account Settings',\n class: \"pt-3\",\n fields: [\n {\n type: 'select',\n name: 'timezone',\n label: 'Timezone',\n columns: 6,\n options: [\n { value: 'America/New_York', text: 'Eastern Time' },\n { value: 'America/Chicago', text: 'Central Time' },\n { value: 'America/Denver', text: 'Mountain Time' },\n { value: 'America/Los_Angeles', text: 'Pacific Time' },\n { value: 'UTC', text: 'UTC' }\n ]\n },\n {\n type: 'select',\n name: 'language',\n label: 'Language',\n columns: 6,\n options: [\n { value: 'en', text: 'English' },\n { value: 'es', text: 'Spanish' },\n { value: 'fr', text: 'French' },\n { value: 'de', text: 'German' }\n ]\n },\n {\n type: 'switch',\n name: 'email_notifications',\n label: 'Email Notifications',\n columns: 4\n },\n {\n type: 'switch',\n name: 'two_factor_enabled',\n label: 'Two-Factor Authentication',\n columns: 4\n },\n {\n type: 'switch',\n name: 'profile_public',\n label: 'Public Profile',\n columns: 4\n }\n ]\n }\n ],\n submitText: 'Save Profile',\n cancelText: 'Cancel'\n });\n\n if (result && result.success) {\n\n\n // Update active user with new data from model\n // (model should already be updated by save operation)\n\n // Show success message\n this.showSuccess('Profile updated successfully!');\n } else if (result && !result.success) {\n // Error case - already handled by Dialog.showForm\n\n }\n\n } catch (error) {\n console.error('Error showing profile form:', error);\n this.showError('Failed to load profile form');\n }\n }\n\n /**\n * Clean up portal resources\n */\n async destroy() {\n // Clean up event listeners\n // Clear active group\n this.activeGroup = null;\n\n // Clean up portal resources\n if (this._resizeObserver) {\n this._resizeObserver.disconnect();\n }\n if (this._resizeHandler) {\n window.removeEventListener('resize', this._resizeHandler);\n }\n\n // Destroy components using View lifecycle\n if (this.topbar) {\n await this.topbar.destroy();\n this.topbar = null;\n this.topnav = null;\n }\n\n if (this.sidebar) {\n await this.sidebar.destroy();\n this.sidebar = null;\n }\n\n // Call parent destroy\n await super.destroy();\n }\n\n /**\n * Static factory method\n */\n static create(config = {}) {\n return new PortalApp(config);\n }\n}\n","import FormView from './FormView.js';\nimport Page from '@core/Page.js';\n\nexport default class FormPage extends Page {\n constructor(options = {}) {\n super({\n title: 'Form Page',\n description: 'A page for submitting forms',\n icon: 'form',\n fields: [],\n template: '<div data-container=\"form-view-container\"></div>',\n className: \"form-page container-sm\",\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n await this.recreateFormView();\n }\n\n async onEnter() {\n await super.onEnter();\n if (this.formView) {\n // Recreate formView to ensure clean slate with new model\n await this.recreateFormView();\n }\n }\n\n async onGroupChange(group) {\n if (this.formView) {\n // Recreate formView to ensure clean slate with new model\n await this.recreateFormView();\n }\n }\n\n async getModel() {\n if (this.model) {\n return this.model;\n } else if (this.getApp().activeGroup) {\n return this.getApp().activeGroup;\n }\n return null;\n }\n\n async recreateFormView() {\n // Destroy old formView\n if (this.formView) {\n await this.formView.destroy();\n this.removeChild(this.formView);\n }\n\n // Create new formView with current model\n this.formView = new FormView({\n containerId: 'form-view-container',\n fields: this.options.fields,\n autosaveModelField: true\n });\n this.addChild(this.formView);\n\n const model = await this.getModel();\n if (model) {\n this.formView.setModel(model);\n }\n }\n}\n","/**\n * MustacheFormatter - Mustache wrapper for MOJO Framework\n * \n * This is now a thin wrapper around Mustache.js since pipe formatting\n * is handled directly in Model.get() and View.get() via MOJOUtils.\n * \n * The wrapper is maintained for backward compatibility and to provide\n * a consistent API for template rendering throughout the framework.\n */\n\nimport mustache from './mustache.js';\nimport dataFormatter from './DataFormatter.js';\n\nclass MustacheFormatter {\n constructor() {\n this.formatter = dataFormatter;\n this.compiledTemplates = new Map();\n }\n\n /**\n * Render template with data\n * Pipes are now handled by Model.get() and View.get() automatically\n * \n * @param {string} template - Mustache template\n * @param {object} data - Data to render (View, Model, or plain object)\n * @param {object} partials - Mustache partials\n * @returns {string} Rendered template\n */\n render(template, data, partials = {}) {\n // Simply pass through to Mustache\n // If data has a get() method, Mustache will use it automatically\n return mustache.render(template, data, partials);\n }\n\n /**\n * Compile template for reuse\n * @param {string} template - Template to compile\n * @returns {object} Compiled template tokens\n */\n compile(template) {\n const compiled = mustache.parse(template);\n this.compiledTemplates.set(template, compiled);\n return compiled;\n }\n\n /**\n * Render with compiled template\n * @param {object} compiled - Compiled template tokens\n * @param {object} data - Data to render\n * @param {object} partials - Mustache partials\n * @returns {string} Rendered template\n */\n renderCompiled(compiled, data, partials = {}) {\n return mustache.render(compiled, data, partials);\n }\n\n /**\n * Clear compiled template cache\n */\n clearCache() {\n this.compiledTemplates.clear();\n mustache.clearCache();\n }\n\n /**\n * Process and cache a template\n * @param {string} key - Cache key\n * @param {string} template - Template to cache\n * @returns {object} Cached template info\n */\n cache(key, template) {\n const compiled = this.compile(template);\n return { key, template, compiled };\n }\n\n /**\n * Get cached template\n * @param {string} key - Cache key\n * @returns {object|null} Cached template info or null\n */\n getCached(key) {\n for (const [template, compiled] of this.compiledTemplates) {\n if (template === key || compiled === key) {\n return { key, template, compiled };\n }\n }\n return null;\n }\n\n /**\n * Register a custom formatter with DataFormatter\n * @param {string} name - Formatter name\n * @param {function} formatter - Formatter function\n * @returns {MustacheFormatter} This instance for chaining\n */\n registerFormatter(name, formatter) {\n this.formatter.register(name, formatter);\n return this;\n }\n\n /**\n * Check if a string contains pipe syntax\n * @param {string} template - Template string to check\n * @returns {boolean} True if contains pipes\n */\n hasPipes(template) {\n return /\\{\\{[{]?[^}|]+\\|[^}]+\\}[}]?\\}/.test(template);\n }\n\n /**\n * Pre-process data with pipe formatters\n * This is now handled automatically by get() methods, but kept for backward compatibility\n * \n * @param {object} data - Data object\n * @param {object} pipes - Object mapping keys to pipe strings\n * @returns {object} Processed data\n */\n processData(data, pipes) {\n const processed = { ...data };\n \n for (const [key, pipeString] of Object.entries(pipes)) {\n // If data has a get method, use it (which will handle pipes)\n if (data && typeof data.get === 'function') {\n processed[key] = data.get(`${key}|${pipeString}`);\n } else {\n // For plain objects, apply formatter directly\n const value = this.getValueFromPath(data, key);\n processed[key] = this.formatter.pipe(value, pipeString);\n }\n }\n \n return processed;\n }\n\n /**\n * Get value from object using dot notation path\n * Kept for backward compatibility, but MOJOUtils.getContextData is preferred\n * \n * @param {object} obj - Source object\n * @param {string} path - Dot notation path\n * @returns {*} Value at path\n */\n getValueFromPath(obj, path) {\n if (!obj || !path) return undefined;\n \n // If obj has a get method, use it\n if (obj && typeof obj.get === 'function') {\n return obj.get(path);\n }\n \n // Otherwise use standard property navigation\n const keys = path.split('.');\n let current = obj;\n \n for (const key of keys) {\n if (current === null || current === undefined) {\n return undefined;\n }\n \n // Handle array index\n if (!isNaN(key) && Array.isArray(current)) {\n current = current[parseInt(key)];\n } else {\n current = current[key];\n }\n }\n \n return current;\n }\n\n /**\n * Process template to handle pipe formatters\n * @deprecated Pipes are now handled by get() methods automatically\n * @param {string} template - Original template\n * @param {object} data - Original data\n * @returns {object} {template: processedTemplate, data: processedData}\n */\n processTemplate(template, data) {\n // For backward compatibility, just return as-is\n // Pipes will be handled by get() methods during Mustache rendering\n return { template, data };\n }\n}\n\n// Create singleton instance\nconst mustacheFormatter = new MustacheFormatter();\n\n// Export both class and instance\nexport { MustacheFormatter };\nexport default mustacheFormatter;","/**\n * MOJO Framework - Core Entry (2.1.0)\n */\n\n// Bundle core CSS\nimport '@core/css/core.css';\nimport '@core/css/portal.css';\nimport '@core/css/table.css';\nimport '@core/css/toast.css';\nimport '@core/css/chat.css';\n\nimport ConsoleSilencer from '@core/utils/ConsoleSilencer.js';\n// Reduce console noise globally: errors only by default (suppress logs, info, and warnings)\nConsoleSilencer.install({ level: 'warn' });\n\n// Version info\nexport {\n VERSION_INFO,\n VERSION,\n VERSION_MAJOR,\n VERSION_MINOR,\n VERSION_REVISION,\n BUILD_TIME\n} from './version.js';\n\n// Core runtime\nexport { default as View } from '@core/View.js';\nexport { default as Page } from '@core/Page.js';\nexport { default as Router } from '@core/Router.js';\nexport { default as Model } from '@core/Model.js';\nexport { default as Collection } from '@core/Collection.js';\nexport { default as Rest } from '@core/Rest.js';\n\n// Core Models - re-export everything from models/\nexport * from '@core/models/AWS.js';\nexport * from '@core/models/Email.js';\nexport * from '@core/models/Files.js';\nexport * from '@core/models/Group.js';\nexport * from '@core/models/Incident.js';\nexport * from '@core/models/Job.js';\nexport * from '@core/models/JobRunner.js';\nexport * from '@core/models/Log.js';\nexport * from '@core/models/Member.js';\nexport * from '@core/models/Metrics.js';\nexport * from '@core/models/Push.js';\nexport * from '@core/models/System.js';\nexport * from '@core/models/Tickets.js';\nexport * from '@core/models/User.js';\n\n// App classes\nexport { default as WebApp } from '@core/WebApp.js';\nexport { default as PortalApp } from '@core/PortalApp.js';\n\n// UI helper\nexport { default as Dialog } from '@core/views/feedback/Dialog.js';\n\n// Selected views (curated for tree-shaking)\nexport { default as TableView } from '@core/views/table/TableView.js';\nexport { default as TableRow } from '@core/views/table/TableRow.js';\nexport { default as TablePage } from '@core/pages/TablePage.js';\nexport { default as ListView } from '@core/views/list/ListView.js';\nexport { default as ListViewItem } from '@core/views/list/ListViewItem.js';\nexport { default as TopNav } from '@core/views/navigation/TopNav.js';\nexport { default as Sidebar } from '@core/views/navigation/Sidebar.js';\nexport { default as TabView } from '@core/views/navigation/TabView.js';\nexport { default as SimpleSearchView } from '@core/views/navigation/SimpleSearchView.js';\nexport { default as DataView } from '@core/views/data/DataView.js';\nexport { default as FormView } from '@core/forms/FormView.js';\nexport { default as FormPage } from '@core/forms/FormPage.js';\nexport { default as FilePreviewView } from '@core/views/data/FilePreviewView.js';\nexport { default as ChatView } from '@core/views/chat/ChatView.js';\nexport { default as ChatMessageView } from '@core/views/chat/ChatMessageView.js';\nexport { default as ChatInputView } from '@core/views/chat/ChatInputView.js';\n\n// Services, utils, mixins\nexport { default as FileUpload } from '@core/services/FileUpload.js';\nexport { default as applyFileDropMixin } from '@core/mixins/FileDropMixin.js';\nexport { default as TokenManager } from '@core/services/TokenManager.js';\nexport { default as ToastService } from '@core/services/ToastService.js';\nexport { default as WebSocketClient } from '@core/services/WebSocketClient.js';\nexport { default as EventDelegate } from '@core/mixins/EventDelegate.js';\nexport { default as EventBus } from '@core/utils/EventBus.js';\nexport { default as dataFormatter } from '@core/utils/DataFormatter.js';\nexport { default as MustacheFormatter } from '@core/utils/MustacheFormatter.js';\nexport { default as MOJOUtils, DataWrapper } from '@core/utils/MOJOUtils.js';\nexport { default as ConsoleSilencer } from '@core/utils/ConsoleSilencer.js';\nexport { installConsoleSilencer } from '@core/utils/ConsoleSilencer.js';\nexport { default as DjangoLookups, parseFilterKey, formatFilterDisplay, LOOKUPS } from '@core/utils/DjangoLookups.js';\n\n// Additional views\nexport { default as ProgressView } from '@core/views/feedback/ProgressView.js';\nexport { default as ContextMenu } from '@core/views/feedback/ContextMenu.js';\n\n// Names\nexport const FRAMEWORK_NAME = 'MOJO';\nexport const PACKAGE_NAME = 'web-mojo';\n\nexport default {\n FRAMEWORK_NAME,\n PACKAGE_NAME,\n};\n"],"names":["LEVELS","Object","freeze","silent","error","warn","info","log","debug","trace","all","isDev","__vite_import_meta_env__","globalThis","__DEV__","process","env","NODE_ENV","isBrowser","window","document","GLOBAL","global","ORIGINAL_CONSOLE","console","ORIGINALS","INSTALLED","CURRENT_LEVEL","parseLevel","level","min","max","Math","key","toLowerCase","prototype","hasOwnProperty","call","makeWrapper","methodName","methodLevel","original","args","apply","ConsoleSilencer","install","options","this","setLevel","persist","explicitLevel","explicit","urlLevel","location","search","params","URLSearchParams","keys","k","v","get","parsed","getUrlLogLevel","storedLevel","localStorage","getItem","getStoredLogLevel","determineInitialLevel","patched","methodLevels","dir","table","group","groupCollapsed","groupEnd","time","timeEnd","timeLog","name","assert","condition","makeAssertWrapper","buildPatchedConsole","MOJOConsoleSilencer","uninstall","levelNumberOrName","entries","find","num","setItem","removeItem","storeLogLevel","getLevel","getLevelName","entry","criticalOnly","errorsOnly","verbose","allowAll","withTemporaryLevel","fn","prev","installConsoleSilencer","GroupSearchView","SimpleSearchView","constructor","super","className","trim","showKind","parentField","kindField","treeData","flattenedItems","showLines","buildTreeHierarchy","items","length","itemsById","Map","forEach","item","has","id","set","children","hasChildren","parentObj","rootItems","treeItem","itemId","originalItem","i","parentId","parent","push","calculateLevels","nodes","node","sort","a","b","localeCompare","flattenTree","result","ancestorLastFlags","index","_isLastChild","_ancestorLastFlags","allAncestorsLast","every","flag","_isLastDescendant","newFlags","computeVerticalLines","flatList","_continueVertical","s","foundSibling","j","futureItem","updateFilteredItems","collection","filteredItems","toJSON","updateResultsView","getDefaultItemTemplate","processItemTemplate","content","itemTemplate","replace","match","prop","getNestedValue","lineSegments","getRootItems","getNodeChildren","nodeId","findNode","targetId","found","Sidebar","View","tagName","menus","activeMenuName","currentRoute","showToggle","isCollapsed","sidebarTheme","theme","customView","groupHeader","groupSelectorMode","groupSelectorDialog","addClass","initializeMenus","setupRouteListeners","autoCollapseMobile","setupResponsiveBehavior","onInit","app","getApp","router","currentPath","getCurrentPath","autoSwitchToMenuForRoute","initializeTooltips","searchView","noAppend","showExitButton","headerText","containerId","Collection","GroupList","addChild","on","evt","setActiveGroup","model","hideGroupSearch","showGroupSearch","showGroupSearchDialog","setClass","showSearch","render","hide","onActionShowGroupSearch","onActionSelectGroupParent","activeGroup","Dialog","confirm","showLoading","Group","fetch","hideLoading","searchFields","searchPlaceholder","headerIcon","maxHeight","innerHeight","autoExpandRoot","autoExpandAll","indentSize","body","size","header","noBodyPadding","scrollable","buttons","closeButton","destroy","show","route","menuName","menuConfig","groupKind","menuContainsRoute","_setActiveMenu","clearAllActiveStates","setActiveItemByRoute","emit","config","sidebar","active","child","normalizeRoute","r","decoded","decodeURIComponent","startsWith","targetRoute","itemRoute","routesMatch","activeMenuItem","childRoute","doRoutesMatch","getTemplate","getSearchTemplate","getMenuTemplate","getPartials","getGroupHeader","addMenu","footer","data","getCurrentMenuConfig","setActiveMenu","lastGroupMenu","getGroupMenu","targetMenu","anyGroupMenu","_","kind","_groupKindMatches","Array","isArray","includes","showMenuForGroup","getMenuConfig","updateMenu","updates","menu","assign","removeMenu","delete","remainingMenus","from","onBeforeRender","currentMenu","subData","version","user","activeUser","renderTemplateString","processNavItems","onAfterRender","isCollapsedState","setTimeout","destroyTooltips","setCustomView","view","removeChild","clearCustomView","updateRouteWithGroup","normalizedRoute","substring","separator","map","divider","isDivider","spacer","isSpacer","processedItem","permissions","hasPermission","requiresGroupKind","isLabel","href","page","baseRoute","processedChild","filter","isItemActive","updateActiveItem","handleActionToggleSubmenu","event","element","arrow","querySelector","classList","toggle","handleActionToggleSidebar","toggleSidebar","onActionShowGroupMenu","action","el","onActionDefault","findAndExecuteHandler","handler","getMenuNames","hasMenu","clearMenus","clear","setMenuData","getMenuData","events","onRouteChanged","preferredMenu","sidebarMenu","fallbackMenu","portalContainer","hideAllTooltips","isCurrentlyCollapsed","contains","remove","add","setSidebarState","state","querySelectorAll","link","navText","textContent","tooltipText","setAttribute","bootstrap","Tooltip","getAttribute","customClass","tooltipOptions","placement","container","trigger","delay","fallbackPlacements","trimmedClass","tooltip","_tooltipInstance","addEventListener","addTooltipHideListeners","removeTooltipHideListeners","tooltipInstance","getInstance","dispose","removeAttribute","getSidebarState","setToggleEnabled","enabled","_tooltipScrollHandler","passive","_tooltipRouteHandler","_tooltipBlurHandler","_tooltipEscapeHandler","e","removeEventListener","onBeforeDestroy","checkMobile","isMobile","innerWidth","createDefault","createMinimal","setSidebarTheme","removeClass","collapse","expand","pulseToggle","toggleButton","removePulse","once","addSimpleMenuItem","text","icon","setSimpleMenu","PageHeader","style","showIcon","showDescription","showBreadcrumbs","currentPage","getMinimalTemplate","getBreadcrumbTemplate","getDefaultTemplate","hasPage","title","displayName","pageName","pageIcon","pageDescription","description","headerActions","pageTitle","breadcrumbs","actions","hasActions","setPage","getPage","onHeaderAction","DeniedPage","Page","template","deniedPage","deniedPageOptions","onParams","query","pageOptions","deniedPageName","setDeniedPage","pageInstance","getViewData","currentUser","getCurrentUser","deniedPageInfo","requiredPermissions","username","email","showLogin","handleActionGoBack","preventDefault","history","back","handleActionGoHome","navigateToDefault","handleActionLogin","showPage","navigate","navError","returnUrl","pathname","showInfo","onEnter","setMeta","timestamp","Date","toISOString","showForPage","NotFoundPage","path","setInfo","_element","showForPath","notFoundPage","PortalApp","WebApp","sidebarConfig","topbarConfig","topbar","topnav","showPageHeader","pageHeaderConfig","pageHeader","tokenManager","TokenManager","sidebarCollapsed","defaultCollapsed","loadSidebarState","setupPageContainer","toast","ToastService","registerPage","start","checkAuthStatus","clearTokens","rest","clearAuth","setActiveUser","checkAndRefreshTokens","onPortalAction","bind","checkActiveGroup","setupRouter","isStarted","tokenStatus","checkTokenStatus","token","getTokenInstance","startAutoRefresh","setAuthToken","User","getUserId","resp","success","urlGroupId","groupId","loadActiveGroupId","status","clearActiveGroup","statusText","saveActiveGroupId","member","Member","fetchForGroup","clearActiveGroupId","storedGroupId","fallbackGroup","fallbackError","previousGroup","getCurrentPage","onGroupChange","updateUrl","getActiveGroup","getActiveGroupStorageKey","toString","setPortalProfile","profile","needsGroupSelection","Error","showSidebar","showTopbar","contentMarkup","innerHTML","pageContainer","setupPortalComponents","applySidebarState","setupSidebar","setupTopbar","setupPageHeader","setupPortalEvents","TopNav","brandText","brand","brandRoute","brandIcon","navItems","leftItems","rightItems","displayMode","showSidebarToggle","headerContainer","getElementById","target","closest","ResizeObserver","resizeObserver","handleResponsive","observe","_resizeObserver","_resizeHandler","saveSidebarState","collapsed","mobile","getPortalContainer","hasMobileLayout","updateNavigation","setActivePage","setUser","getActiveUser","getSidebarStorageKey","JSON","stringify","saved","parse","clearSidebarState","changePassword","showForm","fields","type","label","required","strengthMeter","capsLockWarning","passwordUsage","attributes","autocomplete","submitLabel","new_password","confirm_password","save","showProfile","showModelForm","fileHandling","class","columns","xs","md","imageSize","width","height","placeholder","help","value","submitText","cancelText","showSuccess","showError","disconnect","create","FormPage","recreateFormView","formView","getModel","FormView","autosaveModelField","setModel","mustacheFormatter","formatter","dataFormatter","compiledTemplates","partials","mustache","compile","compiled","renderCompiled","clearCache","cache","getCached","registerFormatter","register","hasPipes","test","processData","pipes","processed","pipeString","getValueFromPath","pipe","obj","split","current","isNaN","parseInt","processTemplate","FRAMEWORK_NAME","PACKAGE_NAME"],"mappings":"62DAiCMA,GAASC,OAAOC,OAAO,CAC3BC,OAAQ,EACRC,MAAO,EACPC,KAAM,EACNC,KAAM,EACNC,IAAK,EACLC,MAAO,EACPC,MAAO,EACPC,IAAK,IAKDC,SAEJ,IAEE,QAA2B,8BAA8BC,GACvD,OAAO,CAEX,CAAA,MAEA,CAGA,GAA0B,oBAAfC,iBAA4D,IAAvBA,WAAWC,QACzD,IACE,QAASD,WAAWC,OACtB,CAAA,MAEA,CAKF,QADsC,oBAAZC,UAA2BA,SAAkC,iBAAhBA,QAAQC,KAC7B,iBAAzBD,QAAQC,IAAIC,WACH,eAAzBF,QAAQC,IAAIC,QAIvB,KAEMC,GAA8B,oBAAXC,QAA8C,oBAAbC,SACpDC,GAA+B,oBAAfR,WAA6BA,WAAgC,oBAAXM,OAAyBA,OAASG,OAGpGC,GAAmBF,GAAOG,SAAW,CAAA,EACrCC,GAAY,CAAA,EAClB,IAAIC,IAAY,EACZC,GAAgB,KAOpB,SAASC,GAAWC,GAClB,GAAqB,iBAAVA,EAAoB,CAE7B,MAAMC,EAAM9B,GAAOG,OACb4B,EAAM/B,GAAOS,MACnB,OAAOuB,KAAKF,IAAIE,KAAKD,IAAIF,EAAOC,GAAMC,EACxC,CACA,GAAqB,iBAAVF,EAAoB,CAC7B,MAAMI,EAAMJ,EAAMK,cAClB,GAAIjC,OAAOkC,UAAUC,eAAeC,KAAKrC,GAAQiC,GAC/C,OAAOjC,GAAOiC,EAElB,CACA,OAAO,IACT,CAuDA,SAASK,GAAYC,EAAYC,GAC/B,MAAMC,EAAWhB,GAAUc,IAAehB,GAAiBgB,UAAuB,GAClF,OAAO,YAAiCG,GAEtC,GAAIf,IAAiBa,EACnB,OAAOC,EAASE,MAAMpB,GAAkBmB,EAI5C,CACF,CA4EA,MAAME,GAAkB,CAEtB,OAAAC,CAAQC,EAAU,IAChB,GAAIpB,GAKF,OAHIoB,QAAoC,IAAlBA,EAAQjB,OAC5BkB,KAAKC,SAASF,EAAQjB,MAAO,CAAEoB,UAAWH,EAAQG,UAE7CF,KAGT,IAAK1B,KAAWE,GAGd,OADAG,IAAY,EACLqB,KAGTpB,GA3EJ,SAA+BuB,GAE7B,MAAMC,EAAWvB,GAAWsB,GAC5B,GAAiB,OAAbC,EAAmB,OAAOA,EAG9B,MAAMC,EAtFR,WACE,IAAKlC,IAAiC,oBAAbmC,WAA6BA,SAASC,OAAQ,OAAO,KAC9E,IACE,MAAMC,EAAS,IAAIC,gBAAgBH,SAASC,QACtCG,EAAO,CAAC,WAAY,WAAY,WACtC,IAAA,MAAWC,KAAKD,EAAM,CACpB,MAAME,EAAIJ,EAAOK,IAAIF,GACrB,GAAS,MAALC,EAAW,CACb,MAAME,EAASjC,GAAW+B,GAC1B,GAAe,OAAXE,EAAiB,OAAOA,CAC9B,CACF,CACF,CAAA,MAEA,CACA,OAAO,IACT,CAsEmBC,GACjB,GAAiB,OAAbV,EAAmB,OAAOA,EAG9B,MAAMW,EAvER,WACE,IAAK7C,MAAe,iBAAkBG,IAAS,OAAO,KACtD,IACE,MAAMsC,EAAItC,GAAO2C,aAAaC,QAAQ,kBACtC,GAAS,MAALN,EAAW,CACb,MAAME,EAASjC,GAAW+B,GAC1B,GAAe,OAAXE,EAAiB,OAAOA,CAC9B,CACF,CAAA,MAEA,CACA,OAAO,IACT,CA2DsBK,GACpB,OAAoB,OAAhBH,EAA6BA,EAG1BnC,GAAWjB,GAnHM,QACC,OAmH3B,CA4DoBwD,CAAsBrB,EAAQjB,OAE9C,MAAMuC,EA3DV,WACE,MAAMA,EAAU,IAAK7C,IAGf8C,EAAe,CAEnBjE,MAAOJ,GAAOI,MACdC,KAAML,GAAOK,KAGbC,KAAMN,GAAOM,KACbC,IAAKP,GAAOM,KACZgE,IAAKtE,GAAOM,KACZiE,MAAOvE,GAAOM,KAGdE,MAAOR,GAAOQ,MACdgE,MAAOxE,GAAOQ,MACdiE,eAAgBzE,GAAOQ,MACvBkE,SAAU1E,GAAOQ,MACjBmE,KAAM3E,GAAOQ,MACboE,QAAS5E,GAAOQ,MAChBqE,QAAS7E,GAAOQ,MAChBC,MAAOT,GAAOS,OAIhB,IAAA,MAAWqE,KAAQ7E,OAAOwD,KAAKY,GAC7B5C,GAAUqD,GAAQvD,GAAiBuD,UAAiB,GACpDV,EAAQU,GAAQxC,GAAYwC,EAAMT,EAAaS,IAQjD,OAJArD,GAAUsD,OAASxD,GAAiBwD,QAAA,MAAkB,GACtDX,EAAQW,OAnEV,WACE,MAAMtC,EAAWhB,GAAUsD,QAAUxD,GAAiBwD,cAAkB,GACxE,OAAO,SAAuBC,KAActC,GAE1C,IAAKsC,EACH,OAAIrD,IAAiB3B,GAAOI,MACnBqC,EAASE,MAAMpB,GAAkB,CAACyD,KAActC,SAEzD,CAIJ,CACF,CAsDmBuC,GAGVb,CACT,CAqBoBc,GAShB,OAPA7D,GAAOG,QAAU4C,EAEjB1C,IAAY,EAGZL,GAAO8D,oBAAsBpC,KAEtBA,IACT,EAGA,SAAAqC,GACE,IAAK1D,GAAW,OAAOqB,KAEvB,IACE1B,GAAOG,QAAUD,EACnB,CAAA,MAEA,CAEA,OADAG,IAAY,EACLqB,IACT,EAIA,QAAAC,CAASnB,GAAOoB,QAAEA,GAAU,GAAU,CAAA,GACpC,MAAMY,EAASjC,GAAWC,GAC1B,OAAe,OAAXgC,IACJlC,GAAgBkC,EACZZ,GA3JR,SAAuBoC,GACrB,GAAKnE,IAAe,iBAAkBG,GACtC,IACE,MAAMY,EAAmC,iBAAtBoD,EACfA,EACsB,OAAtBA,EACE,KACApF,OAAOqF,QAAQtF,IAAQuF,KAAK,GAAIC,KAASA,IAAQH,KAAqB,IAAM,KAC9EpD,EACFZ,GAAO2C,aAAayB,QAAQ,iBAAkBxD,GAE9CZ,GAAO2C,aAAa0B,WAAW,iBAEnC,CAAA,MAEA,CACF,CA4IMC,CAAc9D,IAHYkB,IAM9B,EAGA6C,SAAA,IACSjE,GAIT,YAAAkE,GACE,MAAMC,EAAQ7F,OAAOqF,QAAQtF,IAAQuF,KAAK,GAAIC,KAASA,IAAQ7D,IAC/D,OAAOmE,EAAQA,EAAM,GAAK,IAC5B,EAGA,YAAAC,EAAa9C,QAAEA,GAAU,GAAU,CAAA,GACjC,OAAOF,KAAKC,SAAS,OAAQ,CAAEC,WACjC,EAEA,UAAA+C,EAAW/C,QAAEA,GAAU,GAAU,CAAA,GAC/B,OAAOF,KAAKC,SAAS,QAAS,CAAEC,WAClC,EAEA,MAAA9C,EAAO8C,QAAEA,GAAU,GAAU,CAAA,GAC3B,OAAOF,KAAKC,SAAS,SAAU,CAAEC,WACnC,EAEA,OAAAgD,EAAQhD,QAAEA,GAAU,GAAU,CAAA,GAC5B,OAAOF,KAAKC,SAASrC,GAAQ,QAAU,OAAQ,CAAEsC,WACnD,EAEA,QAAAiD,EAASjD,QAAEA,GAAU,GAAU,CAAA,GAC7B,OAAOF,KAAKC,SAAS,QAAS,CAAEC,WAClC,EAGA,kBAAAkD,CAAmBtE,EAAOuE,GACxB,MAAMC,EAAO1E,GACPkC,EAASjC,GAAWC,GAC1B,GAAe,OAAXgC,GAAiC,mBAAPuC,SAA0BA,MACxDzE,GAAgBkC,EAChB,IACE,OAAOuC,GACT,CAAA,QACEzE,GAAgB0E,CAClB,CACF,EAGArG,WAKWsG,GAA0BxD,GAAYF,GAAgBC,QAAQC,GCvV3E,MAAMyD,wBAAwBC,GAC1B,WAAAC,CAAY3D,EAAU,IAClB4D,MAAM,IACC5D,EACH6D,UAAW,qBAAqB7D,EAAQ6D,WAAa,KAAKC,SAI9D7D,KAAK8D,cAAgC,IAArB/D,EAAQ+D,UAAyB/D,EAAQ+D,SACzD9D,KAAK+D,YAAchE,EAAQgE,aAAe,SAC1C/D,KAAKgE,UAAYjE,EAAQiE,WAAa,OACtChE,KAAKiE,SAAW,GAChBjE,KAAKkE,eAAiB,GAGtBlE,KAAKmE,eAAkC,IAAtBpE,EAAQoE,WAA0BpE,EAAQoE,SAC/D,CAKA,kBAAAC,CAAmBC,GACf,IAAKA,GAA0B,IAAjBA,EAAMC,OAChB,MAAO,GAIX,MAAMC,qBAAgBC,IAGtBH,EAAMI,QAAQC,IACLH,EAAUI,IAAID,EAAKE,KACpBL,EAAUM,IAAIH,EAAKE,GAAI,IAChBF,EACHI,SAAU,GACVhG,MAAO,EACPiG,aAAa,IAKrB,MAAMC,EAAYN,EAAK1E,KAAK+D,aACxBiB,GAAaA,EAAUJ,KAAOL,EAAUI,IAAIK,EAAUJ,KACtDL,EAAUM,IAAIG,EAAUJ,GAAI,IACrBI,EACHF,SAAU,GACVhG,MAAO,EACPiG,aAAa,MAKzB,MAAME,EAAY,GAGlBV,EAAUE,QAAQ,CAACS,EAAUC,KACzB,MAAMC,EAAef,EAAM7B,KAAK6C,GAAKA,EAAET,KAAOO,IAAWD,EACnDI,EAAWF,EAAapF,KAAK+D,cAAca,GAEjD,GAAIU,GAAYf,EAAUI,IAAIW,GAAW,CACrC,MAAMC,EAAShB,EAAU1D,IAAIyE,GAC7BC,EAAOT,SAASU,KAAKN,GACrBK,EAAOR,aAAc,CACzB,MACIE,EAAUO,KAAKN,KAKvB,MAAMO,EAAkB,CAACC,EAAO5G,EAAQ,KACpC4G,EAAMjB,QAAQkB,IACVA,EAAK7G,MAAQA,EACT6G,EAAKb,SAASR,OAAS,IACvBqB,EAAKb,SAASc,KAAK,CAACC,EAAGC,KAAOD,EAAE9D,MAAQ,IAAIgE,cAAcD,EAAE/D,MAAQ,KACpE0D,EAAgBE,EAAKb,SAAUhG,EAAQ,OAQnD,OAHAmG,EAAUW,KAAK,CAACC,EAAGC,KAAOD,EAAE9D,MAAQ,IAAIgE,cAAcD,EAAE/D,MAAQ,KAChE0D,EAAgBR,GAETA,CACX,CAMA,WAAAe,CAAYN,EAAOO,EAAS,GAAIC,EAAoB,IAmBhD,OAlBAR,EAAMjB,QAAQ,CAACkB,EAAMQ,KACjBR,EAAKS,aAAeD,IAAUT,EAAMpB,OAAS,EAC7CqB,EAAKU,mBAAqB,IAAIH,GAI9B,MAAMI,EAAmBJ,EAAkBK,MAAMC,GAAQA,GAMzD,GALAb,EAAKc,kBAAoBH,GAAoBX,EAAKS,gBAC5CT,EAAKb,UAAqC,IAAzBa,EAAKb,SAASR,QAErC2B,EAAOT,KAAKG,GAERA,EAAKb,UAAYa,EAAKb,SAASR,OAAS,EAAG,CAC3C,MAAMoC,EAAW,IAAIR,EAAmBP,EAAKS,cAC7CpG,KAAKgG,YAAYL,EAAKb,SAAUmB,EAAQS,EAC5C,IAGGT,CACX,CAQA,oBAAAU,CAAqBC,GACjB,IAAA,IAASvB,EAAI,EAAGA,EAAIuB,EAAStC,OAAQe,IAAK,CACtC,MAAMX,EAAOkC,EAASvB,GACtBX,EAAKmC,kBAAoB,GAEzB,IAAA,IAASC,EAAI,EAAGA,EAAIpC,EAAK5F,MAAOgI,IAAK,CAEjC,IAAIC,GAAe,EACnB,IAAA,IAASC,EAAI3B,EAAI,EAAG2B,EAAIJ,EAAStC,OAAQ0C,IAAK,CAC1C,MAAMC,EAAaL,EAASI,GAC5B,GAAIC,EAAWnI,QAAUgI,EAAI,EAAG,CAE5BC,GAAe,EACf,KACJ,CAAA,GAAWE,EAAWnI,OAASgI,EAE3B,KAGR,CACApC,EAAKmC,kBAAkBC,GAAKC,CAChC,CACJ,CACJ,CAKA,mBAAAG,GACI,IAAKlH,KAAKmH,WAIN,OAHAnH,KAAKoH,cAAgB,GACrBpH,KAAKiE,SAAW,QAChBjE,KAAKkE,eAAiB,IAK1B,MAAMG,EAAQrE,KAAKmH,WAAWE,SAC9BrH,KAAKiE,SAAWjE,KAAKoE,mBAAmBC,GACxCrE,KAAKkE,eAAiBlE,KAAKgG,YAAYhG,KAAKiE,UAG5CjE,KAAK2G,qBAAqB3G,KAAKkE,gBAG/BlE,KAAKoH,cAAgBpH,KAAKkE,eAE1BlE,KAAKsH,mBACT,CAKA,sBAAAC,GACI,MAAO,mQAQX,CAKA,mBAAAC,CAAoB9C,GAEhB,IAAI+C,EAAUzH,KAAK0H,aACnBD,EAAUA,EAAQE,QAAQ,iBAAkB,CAACC,EAAOC,IACnC,aAATA,EACO7H,KAAK8D,SAAW,OAAS,GAE7B9D,KAAK8H,eAAepD,EAAMmD,IAAS,IAK1CJ,EADAzH,KAAK8D,SACK2D,EAAQE,QAAQ,6CAA8C,MAE9DF,EAAQE,QAAQ,2CAA4C,IAK1E,IAAII,EAAe,GACnB,GAAI/H,KAAKmE,WAAaO,EAAK5F,MAAQ,EAC/B,IAAA,IAASuG,EAAI,EAAGA,EAAIX,EAAK5F,MAAOuG,IACLA,IAAMX,EAAK5F,MAAQ,EAMtCiJ,GAAgB,gBADCrD,EAAK0B,aAAe,yBAA2B,mCAM5D2B,GAFqBrD,EAAKmC,mBAAqBnC,EAAKmC,kBAAkBxB,GAEtD,+CAEA,iCAWhC,MAAO,8CAJaX,EAAKK,YAAc,gBAAkB,KACrCL,EAAK0B,aAAe,iBAAmB,wBAIuB1B,EAAK5F,0EAEzEiJ,4GAGAN,yDAIlB,CAKA,YAAAO,GACI,OAAOhI,KAAKiE,QAChB,CAKA,eAAAgE,CAAgBC,GACZ,MAAMC,EAAW,CAACzC,EAAO0C,KACrB,IAAA,MAAWzC,KAAQD,EAAO,CACtB,GAAIC,EAAKf,KAAOwD,EACZ,OAAOzC,EAAKb,SAEhB,GAAIa,EAAKb,SAASR,OAAS,EAAG,CAC1B,MAAM+D,EAAQF,EAASxC,EAAKb,SAAUsD,GACtC,GAAIC,EAAO,OAAOA,CACtB,CACJ,CACA,OAAO,MAGX,OAAOF,EAASnI,KAAKiE,SAAUiE,IAAW,EAC9C,ECxQJ,MAAMI,gBAAgBC,EAClB,WAAA7E,CAAY3D,EAAU,IAClB4D,MAAM,CACF6E,QAAS,MACT5E,UAAW,UACXgB,GAAI,aACD7E,IAGPC,KAAKyI,yBAAYjE,IACjBxE,KAAK0I,eAAiB,KACtB1I,KAAK2I,aAAe,KACpB3I,KAAK4I,WAAa7I,EAAQ6I,WAC1B5I,KAAK6I,aAAc,EACnB7I,KAAK8I,aAAe/I,EAAQgJ,OAAS,gBACrC/I,KAAKgJ,WAAa,KACdhJ,KAAKD,QAAQkJ,cAAajJ,KAAKiJ,YAAcjJ,KAAKD,QAAQkJ,aAK9DjJ,KAAKkJ,kBAAoBnJ,EAAQmJ,mBAAqB,SACtDlJ,KAAKmJ,oBAAsB,KAEvBnJ,KAAK8I,cACL9I,KAAKoJ,SAASpJ,KAAK8I,cAIvB9I,KAAKqJ,gBAAgBtJ,GAGrBC,KAAKsJ,uBAG8B,IAA/BvJ,EAAQwJ,oBACRvJ,KAAKwJ,yBAEb,CAEAP,YAAc,88BAwBd,YAAMQ,SACI9F,MAAM8F,SAGZ,MAAMC,EAAM1J,KAAK2J,SACXC,EAASF,GAAKE,OAEpB,GAAIA,EAAQ,CACR,MAAMC,EAAcD,EAAOE,iBACvBD,GACA7J,KAAK+J,yBAAyBF,EAEtC,CAGA7J,KAAKgK,qBAELhK,KAAKiK,WAAa,IAAIzG,gBAAgB,CAClC0G,UAAU,EACVC,gBAAgB,EAChBC,WAAY,eACZC,YAAa,2BACbC,WAAYC,EACZ7C,aAAc,4NAOlB1H,KAAKwK,SAASxK,KAAKiK,YACnBjK,KAAKiK,WAAWQ,GAAG,gBAAkBC,IAEjC1K,KAAK2J,SAASgB,eAAeD,EAAIE,SAErC5K,KAAKiK,WAAWQ,GAAG,OAAS/F,IAExB1E,KAAK6K,mBAEb,CAEA,eAAAC,GACmC,WAA3B9K,KAAKkJ,kBACLlJ,KAAK+K,yBAGL/K,KAAKgL,SAAS,WACdhL,KAAKiL,YAAa,EAClBjL,KAAKkL,SAEb,CAEA,eAAAL,GACmC,WAA3B7K,KAAKkJ,kBACDlJ,KAAKmJ,qBACLnJ,KAAKmJ,oBAAoBgC,QAI7BnL,KAAKgL,SAAS,WACdhL,KAAKiL,YAAa,EAClBjL,KAAKkL,SAEb,CAEA,uBAAAE,GACIpL,KAAK8K,iBACT,CAEA,+BAAMO,GAEF,MAAM5J,EAAQzB,KAAK2J,SAAS2B,YAE5B,SADqBC,GAAOC,QAAQ,6CAA6C/J,EAAMZ,IAAI,oBAC/E,CACRb,KAAK2J,SAAS8B,cACd,IAAIlG,EAAS,IAAImG,EAAM,CAAC9G,GAAInD,EAAMZ,IAAI,qBAChC0E,EAAOoG,QACb3L,KAAK2J,SAASgB,eAAepF,GAC7BvF,KAAK2J,SAASiC,aAClB,CACJ,CAKA,2BAAMb,GAEF,MAAM5D,EAAa,IAAIoD,EAGjBN,EAAa,IAAIzG,gBAAgB,CACnC8G,WAAYC,EACZpD,aACA0E,aAAc,CAAC,QACfzB,WAAY,KACZ0B,kBAAmB,mBACnBC,WAAY,KACZC,UAAW/M,KAAKF,IAAI,IAAKX,OAAO6N,YAAc,KAC9C9B,gBAAgB,EAChBrG,UAAU,EACVC,YAAa,SACbC,UAAW,OACXkI,gBAAgB,EAChBC,eAAe,EACfC,WAAY,GACZjI,WAAW,IAIfnE,KAAKmJ,oBAAsB,IAAIoC,GAAO,CAClCc,KAAMpC,EACNqC,KAAM,KACNC,OAAQ,KACRC,eAAe,EACfC,YAAY,EACZC,QAAS,GACTC,aAAa,IAIjB1C,EAAWQ,GAAG,gBAAkBC,IAE5B1K,KAAK2J,SAASgB,eAAeD,EAAIE,OAC7B5K,KAAKmJ,qBACLnJ,KAAKmJ,oBAAoBgC,SAKjCnL,KAAKmJ,oBAAoBsB,GAAG,SAAU,KAClCzK,KAAKmJ,oBAAoByD,UACzB5M,KAAKmJ,oBAAsB,aAIzBnJ,KAAKmJ,oBAAoB+B,QAAO,EAAM7M,SAASgO,MACrDrM,KAAKmJ,oBAAoB0D,MAC7B,CAKA,wBAAA9C,CAAyB+C,GAErB,IAAA,MAAYC,EAAUC,KAAehN,KAAKyI,MACtC,KAAIuE,EAAWC,WAAcjN,KAAK2J,SAAS2B,cAEvCtL,KAAKkN,kBAAkBF,EAAYF,GAsBnC,OApBA9M,KAAKmN,eAAeJ,GACpB/M,KAAK2I,aAAemE,EAGpB9M,KAAKoN,uBACLpN,KAAKqN,qBAAqBP,GAG1B9M,KAAKkL,SAKLlL,KAAKsN,KAAK,qBAAsB,CAC5BP,WACAD,QACAS,OAAQP,EACRQ,QAASxN,QAGN,EAIf,OAAO,CACX,CAKA,oBAAAoN,GACI,IAAA,MAAYL,EAAUC,KAAehN,KAAKyI,MACtC,IAAA,MAAW/D,KAAQsI,EAAW3I,OAAS,GAEnC,GADAK,EAAK+I,QAAS,EACV/I,EAAKI,SACL,IAAA,MAAW4I,KAAShJ,EAAKI,SACrB4I,EAAMD,QAAS,CAKnC,CAKA,oBAAAJ,CAAqBP,GACjB,MAAMa,EAAkBC,IACpB,IAAKA,EAAG,MAAO,IAEf,MAAMC,EAAUC,mBAAmBF,GACnC,OAAOC,EAAQE,WAAW,KAAOF,EAAU,IAAIA,KAG7CG,EAAcL,EAAeb,GAGnC,IAAA,MAAYC,EAAUC,KAAehN,KAAKyI,MACtC,IAAIuE,EAAWC,WAAcjN,KAAK2J,SAAS2B,YAE3C,IAAA,MAAW5G,KAAQsI,EAAW3I,OAAS,GAAI,CAEvC,GAAIK,EAAKoI,MAAO,CACZ,MAAMmB,EAAYN,EAAejJ,EAAKoI,OACtC,GAAI9M,KAAKkO,YAAYF,EAAaC,GAG9B,OAFAvJ,EAAK+I,QAAS,EACdzN,KAAKmO,eAAiBzJ,GACf,CAEf,CAGA,GAAIA,EAAKI,SACL,IAAA,MAAW4I,KAAShJ,EAAKI,SACrB,GAAI4I,EAAMZ,MAAO,CACb,MAAMsB,EAAaT,EAAeD,EAAMZ,OACxC,GAAI9M,KAAKkO,YAAYF,EAAaI,GAG9B,OAFAV,EAAMD,QAAS,EACf/I,EAAK+I,QAAS,GACP,CAEf,CAGZ,CAGJ,OAAO,CACX,CAKA,iBAAAP,CAAkBF,EAAYF,GAC1B,MAAMa,EAAkBC,IACpB,IAAKA,EAAG,MAAO,IAEf,MAAMC,EAAUC,mBAAmBF,GACnC,OAAOC,EAAQE,WAAW,KAAOF,EAAU,IAAIA,KAG7CG,EAAcL,EAAeb,GAGnC,IAAA,MAAWpI,KAAQsI,EAAW3I,OAAS,GAAI,CAEvC,GAAIK,EAAKoI,MAAO,CACZ,MAAMmB,EAAYN,EAAejJ,EAAKoI,OACtC,GAAI9M,KAAKkO,YAAYF,EAAaC,GAC9B,OAAO,CAEf,CAGA,GAAIvJ,EAAKI,SACL,IAAA,MAAW4I,KAAShJ,EAAKI,SACrB,GAAI4I,EAAMZ,MAAO,CACb,MAAMsB,EAAaT,EAAeD,EAAMZ,OACxC,GAAI9M,KAAKkO,YAAYF,EAAaI,GAC9B,OAAO,CAEf,CAGZ,CAEA,OAAO,CACX,CAKA,WAAAF,CAAYvF,EAAcsF,GACtB,OAAOjO,KAAK2J,SAASC,OAAOyE,cAAc1F,EAAcsF,EAC5D,CAEA,WAAAK,GACI,OAAItO,KAAKgJ,WACE,2EAEPhJ,KAAKiL,WAAmBjL,KAAKuO,oBAC1BvO,KAAKwO,iBAChB,CAEA,iBAAAD,GACI,MAAO,2GAIX,CAEA,eAAAC,GACI,MAAO,u/CAyCX,CAKA,WAAAC,GACI,MAAO,CACH,WAAY,orGAiEZ,cAAe,0IAKf,aAAc,oEAGd,YAAa,4JAMrB,CAEA,cAAAC,GACI,OAAO1O,KAAKiJ,WAChB,CAKA,OAAA0F,CAAQ5M,EAAMwL,GAqBV,OApBIA,EAAON,YAAcM,EAAOhB,SAC5BgB,EAAOhB,OAASvM,KAAK0O,kBAGzB1O,KAAKyI,MAAM5D,IAAI9C,EAAM,CACjBA,OACAkL,UAAWM,EAAON,WAAa,KAC/BV,OAAQgB,EAAOhB,QAAU,KACzBqC,OAAQrB,EAAOqB,QAAU,KACzBvK,MAAOkJ,EAAOlJ,OAAS,GACvBwK,KAAMtB,EAAOsB,MAAQ,CAAA,EACrBjL,UAAW2J,EAAO3J,WAAa,yBAK9B5D,KAAK0I,gBACN1I,KAAKmN,eAAepL,GAGjB/B,IACX,CAEA,cAAAmN,CAAepL,GACX/B,KAAKiL,YAAa,EAClBjL,KAAK0I,eAAiB3G,EACtB,MAAMwL,EAASvN,KAAK8O,uBAChBvB,EAAO3J,UACP5D,KAAKgL,SAASuC,EAAO3J,WAErB5D,KAAKgL,SAAS,UAEtB,CAKA,mBAAM+D,CAAchN,GAChB,IAAK/B,KAAKyI,MAAM9D,IAAI5C,GAEhB,OADAtD,QAAQnB,KAAK,SAASyE,gBACf/B,KAGX,MAAMgN,EAAahN,KAAKyI,MAAM5H,IAAIkB,GAElC,IAAIiL,EAAWC,YACXjN,KAAKgP,cAAgBhC,EAEhBhN,KAAK2J,SAAS2B,aAevB,OATAtL,KAAKmN,eAAepL,SACd/B,KAAKkL,SAEXlL,KAAKsN,KAAK,eAAgB,CACtBP,SAAUhL,EACVwL,OAAQP,EACRQ,QAASxN,OAGNA,KAdCA,KAAK8K,iBAejB,CAEA,YAAAmE,CAAaxN,GACT,IAAKA,EAED,OADAhD,QAAQnB,KAAK,qBACN,KAGX,IAAI4R,EAAalP,KAAKgP,cAClBG,EAAe,KACnB,GAAI1N,EAAM2N,EAAEC,KACR,IAAA,MAAYtC,EAAUC,KAAehN,KAAKyI,MAAO,CAI7C,GAFgBzI,KAAKsP,kBAAkBtC,EAAWC,UAAWxL,EAAM2N,EAAEC,MAExD,CACTH,EAAalC,EACb,KACJ,CAAoC,QAAzBA,EAAWC,YAClBkC,EAAenC,EAEvB,CAGJ,OAAKkC,GACMC,CAGf,CASA,iBAAAG,CAAkBrC,EAAWoC,GACzB,SAAKpC,IAAcoC,KAGfE,MAAMC,QAAQvC,GACPA,EAAUwC,SAASJ,GAIvBpC,IAAcoC,EACzB,CAEA,gBAAAK,CAAiBjO,GACb,IAAKA,EAED,YADAhD,QAAQnB,KAAK,qBAIjB,IAAI4R,EAAalP,KAAKiP,aAAaxN,GAEnC,GAAKyN,EAYL,OARAlP,KAAKmN,eAAe+B,EAAWnN,MAC/B/B,KAAKkL,SAELlL,KAAKsN,KAAK,eAAgB,CACtBP,SAAUmC,EAAWnN,KACrBwL,OAAQ2B,EACR1B,QAASxN,OAENA,KAXHvB,QAAQnB,KAAK,iCAAiCmE,EAAM4N,OAY5D,CAKA,aAAAM,CAAc5N,GACV,OAAO/B,KAAKyI,MAAM5H,IAAIkB,IAAS,IACnC,CAKA,oBAAA+M,GACI,OAAO9O,KAAK0I,eAAiB1I,KAAKyI,MAAM5H,IAAIb,KAAK0I,gBAAkB,IACvE,CAKA,UAAAkH,CAAW7N,EAAM8N,GACb,MAAMC,EAAO9P,KAAKyI,MAAM5H,IAAIkB,GAC5B,OAAK+N,GAML5S,OAAO6S,OAAOD,EAAMD,GAGhB7P,KAAK0I,iBAAmB3G,GACxB/B,KAAKkL,SAGFlL,OAZHvB,QAAQnB,KAAK,SAASyE,gBACf/B,KAYf,CAKA,UAAAgQ,CAAWjO,GAIP,GAHA/B,KAAKyI,MAAMwH,OAAOlO,GAGd/B,KAAK0I,iBAAmB3G,EAAM,CAC9B,MAAMmO,EAAiBX,MAAMY,KAAKnQ,KAAKyI,MAAM/H,QAC7CV,KAAK0I,eAAiBwH,EAAe5L,OAAS,EAAI4L,EAAe,GAAK,KACtElQ,KAAKkL,QACT,CAEA,OAAOlL,IACX,CAKA,oBAAMoQ,GACF,MAAMC,EAAcrQ,KAAK8O,uBAEzB,IAAKuB,EACD,MAAO,CAAEA,YAAa,MAG1B,IAAIC,EAAU,CACVC,QAASvQ,KAAK2J,SAAS4G,SAAW,KAClC9O,MAAOzB,KAAK2J,SAAS2B,aAAe,KACpCkF,KAAMxQ,KAAK2J,OAAO8G,YAAc,MAGpCzQ,KAAK6O,KAAO,CACRwB,YAAa,CACT9D,OAAQvM,KAAK0Q,qBAAqBL,EAAY9D,QAAU,GAAI+D,GAC5D1B,OAAQ5O,KAAK0Q,qBAAqBL,EAAYzB,QAAU,GAAI0B,GAC5DjM,MAAOrE,KAAK2Q,gBAAgBN,EAAYhM,MAAOgM,EAAYpD,WAC3D4B,KAAMwB,EAAYxB,KAClBjG,WAAY5I,KAAK4I,YAI7B,CAEA,mBAAMgI,GAEE5Q,KAAK6Q,mBAELC,WAAW,IAAM9Q,KAAKgK,qBAAsB,IAE5ChK,KAAK+Q,iBAEb,CAEA,aAAAC,CAAcC,GAUV,OATIjR,KAAKgJ,YACLhJ,KAAKkR,YAAYlR,KAAKgJ,WAAWpE,IAErC5E,KAAKgJ,WAAaiI,EACdA,IACAA,EAAK5G,YAAc,gCACnBrK,KAAKwK,SAASyG,IAElBjR,KAAKkL,SACElL,IACX,CAEA,eAAAmR,GAMI,OALInR,KAAKgJ,aACLhJ,KAAKkR,YAAYlR,KAAKgJ,WAAWpE,IACjC5E,KAAKgJ,WAAa,MAEtBhJ,KAAKkL,SACElL,IACX,CAKC,eAAA2Q,CAAgBtM,EAAO4I,GACnB,MAAMvD,EAAM1J,KAAK2J,SACX8G,EAAa/G,GAAK+G,WAClBnF,EAAc5B,GAAK4B,YAGnB8F,EAAwBtE,IAC1B,IAAIuE,EAAkBvE,EAStB,GANIA,EAAMiB,WAAW,OAASjB,EAAM2C,SAAS,OAEzC4B,EAAkB,SADDvE,EAAMwE,UAAU,IAAM,UAKvCrE,GAAa3B,GAAeA,EAAY1G,GAAI,CAC5C,MAAM2M,EAAYF,EAAgB5B,SAAS,KAAO,IAAM,IACxD,MAAO,GAAG4B,IAAkBE,UAAkBjG,EAAY1G,IAC9D,CACA,OAAOyM,GAGX,OAAOhN,EAAMmN,IAAI,CAAC9M,EAAMyB,KAEpB,GAAa,KAATzB,GAAgC,iBAATA,GAAqBA,EAAK+M,QACjD,MAAO,CACHC,WAAW,EACX9M,GAAI,WAAWuB,KAKvB,GAAoB,iBAATzB,GAAqBA,EAAKiN,OACjC,MAAO,CACHC,UAAU,EACVhN,GAAI,UAAUuB,KAItB,MAAM0L,EAAgB,IAAKnN,GAG3B,GAAImN,EAAcC,eACTrB,IAAeA,EAAWsB,cAAcF,EAAcC,cACvD,OAAO,KAKf,GAAID,EAAcG,kBAAmB,CACjC,MAAM/E,EAAY3B,GAAa8D,EAAEC,MAAQ/D,GAAa+D,KACtD,IAAKpC,IAAcjN,KAAKsP,kBAAkBuC,EAAcG,kBAAmB/E,GACvE,OAAO,IAEf,CAEA,GAA2B,UAAvB4E,EAAcxC,KAKf,OAJAwC,EAAcI,SAAU,EACnBJ,EAAcjN,KACfiN,EAAcjN,GAAK,aAAauB,KAE7B0L,EASV,GALKA,EAAcjN,KACfiN,EAAcjN,GAAK,OAAOuB,KAI1B0L,EAAc/E,MACd+E,EAAcK,KAAOd,EAAqBS,EAAc/E,YAC5D,GAAW+E,EAAcM,KAAM,CAE3B,MAAMC,EAAYP,EAAcM,KAAKpE,WAAW,KAAO8D,EAAcM,KAAO,IAAIN,EAAcM,OAC9FN,EAAcK,KAAOd,EAAqBgB,GAC1CP,EAAc/E,MAAQ+E,EAAcK,IACxC,CA4CA,OAvCIL,EAAc/M,UACd+M,EAAc/M,SAAW+M,EAAc/M,SAAS0M,IAAI9D,IAChD,MAAM2E,EAAiB,IAAK3E,GAG5B,GAAI2E,EAAeP,aAAerB,IACzBA,EAAWsB,cAAcM,EAAeP,aACzC,OAAO,KAKf,GAAIO,EAAeL,kBAAmB,CAClC,MAAM/E,EAAY3B,GAAa8D,EAAEC,MAAQ/D,GAAa+D,KACtD,IAAKpC,IAAcjN,KAAKsP,kBAAkB+C,EAAeL,kBAAmB/E,GACxE,OAAO,IAEf,CAGA,GAAIoF,EAAevF,MACfuF,EAAeH,KAAOd,EAAqBiB,EAAevF,YAC9D,GAAWuF,EAAeF,KAAM,CAC5B,MAAMC,EAAYC,EAAeF,KAAKpE,WAAW,KAAOsE,EAAeF,KAAO,IAAIE,EAAeF,OACjGE,EAAeH,KAAOd,EAAqBgB,GAC3CC,EAAevF,MAAQuF,EAAeH,IAC1C,CAGA,OAAOG,IACRC,OAAO5E,GAAmB,OAAVA,GAGnBmE,EAAc9M,eAAiB8M,EAAc/M,UAAY+M,EAAc/M,SAASR,OAAS,IAGzFuN,EAAc9M,aAAc,EAGzB8M,IACRS,OAAO5N,GAAiB,OAATA,EACtB,CAQD,YAAA6N,CAAa7N,GACT,IAAKA,EAAKoI,QAAU9M,KAAK2I,aACrB,OAAO,EAGX,MAAMgF,EAAkBb,IACpB,IAAKA,EAAO,MAAO,IAEnB,MAAMe,EAAUC,mBAAmBhB,GACnC,OAAOe,EAAQE,WAAW,KAAOF,EAAU,IAAIA,KAG7CI,EAAYN,EAAejJ,EAAKoI,OAChCnE,EAAegF,EAAe3N,KAAK2I,cAEzC,MAAkB,MAAdsF,GAAsC,MAAjBtF,GAIP,MAAdsF,GAAsC,MAAjBtF,IACdA,EAAaoF,WAAWE,IAActF,IAAiBsF,EAItE,CAKA,sBAAMuE,CAAiB1F,GAQnB,OAPA9M,KAAK2I,aAAemE,EAGpB9M,KAAKoN,uBACLpN,KAAKqN,qBAAqBP,SAEpB9M,KAAKkL,SACJlL,IACX,CAKA,+BAAMyS,CAA0BC,EAAOC,GACnC,MAAMC,EAAQD,EAAQE,cAAc,cAChCD,GACAA,EAAME,UAAUC,OAAO,UAE/B,CAKA,+BAAMC,CAA0BN,EAAOC,GACnC3S,KAAKiT,eACT,CAEA,qBAAAC,CAAsBC,EAAQT,EAAOU,GAGjC,OAFApT,KAAK+O,cAAc,kBAEZ,CACX,CAEA,qBAAMsE,CAAgBF,EAAQT,EAAOU,GACjC,MAAM7F,EAASvN,KAAK8O,uBACpB,IAAKvB,EAAQ,OAGb,MAAM+F,EAAyBjP,IAC3B,IAAA,MAAWK,KAAQL,EAAO,CACtB,GAAKK,EAAKyO,QAAUA,GAAWzO,EAAK6O,QAEhC,OADA7O,EAAK6O,QAAQJ,EAAQT,EAAOU,EAAIpT,KAAK2J,WAC9B,EAGX,GAAIjF,EAAKI,UAAYJ,EAAKI,SAASR,OAAS,GACpCgP,EAAsB5O,EAAKI,UAC3B,OAAO,CAGnB,CACA,OAAO,GAGX,OAAOwO,EAAsB/F,EAAOlJ,MACxC,CAKA,YAAAmP,GACI,OAAOjE,MAAMY,KAAKnQ,KAAKyI,MAAM/H,OACjC,CAKA,OAAA+S,CAAQ1R,GACJ,OAAO/B,KAAKyI,MAAM9D,IAAI5C,EAC1B,CAKA,UAAA2R,GAII,OAHA1T,KAAKyI,MAAMkL,QACX3T,KAAK0I,eAAiB,KACtB1I,KAAKkL,SACElL,IACX,CAKA,WAAA4T,CAAY/E,GACR,MAAMwB,EAAcrQ,KAAK8O,uBAKzB,OAJIuB,IACAA,EAAYxB,KAAO,IAAKwB,EAAYxB,QAASA,GAC7C7O,KAAKkL,UAEFlL,IACX,CAKA,WAAA6T,GACI,MAAMxD,EAAcrQ,KAAK8O,uBACzB,OAAOuB,EAAcA,EAAYxB,KAAO,CAAA,CAC5C,CAKA,mBAAAvF,GACI,MAAMI,EAAM1J,KAAK2J,SACbD,GAAOA,EAAIoK,SACXpK,EAAIoK,OAAOrJ,GAAG,CAAC,gBAAkBoE,IAC7B7O,KAAK+T,eAAelF,KAExBnF,EAAIoK,OAAOrJ,GAAG,gBAAkBoE,IAC5B7O,KAAK0P,iBAAiBb,EAAKpN,SAE/BiI,EAAIoK,OAAOrJ,GAAG,sBAAwBoE,IAClC7O,KAAKkL,WAGjB,CAoBA,cAAA6I,CAAelF,GACX,GAAIA,EAAKsD,MAAQtD,EAAKsD,KAAKrF,MAAO,CAC9B,MAAMA,EAAQ+B,EAAKsD,KAAKrF,MACxB,GAAI9M,KAAKmO,gBAAkBnO,KAAKkO,YAAYpB,EAAO9M,KAAKmO,eAAerB,OACnE,OAMJ,GAFqB9M,KAAK+J,yBAAyB+C,GAI/C,OAKJ,MAAMkH,EAAgBnF,EAAKsD,KAAK8B,aAAepF,EAAKsD,KAAKpS,SAASkU,aAAe,KACjF,GAAID,GAAiBhU,KAAKyI,MAAM9D,IAAIqP,GAKhC,OAJAhU,KAAKmN,eAAe6G,GACpBhU,KAAKoN,4BACLpN,KAAKkL,SAOT,IAAIgJ,EAAe,KACnB,IAAA,MAAYnH,EAAUC,KAAehN,KAAKyI,MACtC,IAAKuE,EAAWC,UAAW,CACvBiH,EAAenH,EACf,KACJ,CAGAmH,GAAgBlU,KAAK0I,iBAAmBwL,GACxClU,KAAKmN,eAAe+G,GAKxBlU,KAAKoN,uBACLpN,KAAKwS,iBAAiB1F,GACtB9M,KAAKkL,QACT,CACJ,CAKI,aAAA+H,GACI,MAAMkB,EAAkB9V,SAASwU,cAAc,qBAC/C,IAAKsB,EAAiB,OAGtBnU,KAAKoU,kBAEL,MAAMC,EAAuBF,EAAgBrB,UAAUwB,SAAS,oBAqBhE,OApB0BH,EAAgBrB,UAAUwB,SAAS,iBAIzDH,EAAgBrB,UAAUyB,OAAO,gBACjCvU,KAAK6I,aAAc,EACnB7I,KAAK+Q,mBACEsD,GAEPF,EAAgBrB,UAAUyB,OAAO,oBACjCvU,KAAK6I,aAAc,EACnB7I,KAAK+Q,oBAGLoD,EAAgBrB,UAAU0B,IAAI,oBAC9BxU,KAAK6I,aAAc,EAEnBiI,WAAW,IAAM9Q,KAAKgK,qBAAsB,MAGzChK,IACX,CAKA,eAAAyU,CAAgBC,GACZ,MAAMP,EAAkB9V,SAASwU,cAAc,qBAC/C,IAAKsB,EAAiB,OAAOnU,KAK7B,OAFAmU,EAAgBrB,UAAUyB,OAAO,mBAAoB,gBAE7CG,GACJ,IAAK,YACDP,EAAgBrB,UAAU0B,IAAI,oBAC9BxU,KAAK6I,aAAc,EACnB,MACJ,IAAK,SACDsL,EAAgBrB,UAAU0B,IAAI,gBAC9BxU,KAAK6I,aAAc,EACnB,MAEJ,QACI7I,KAAK6I,aAAc,EAe3B,OAVI7I,KAAK6I,aAEL7I,KAAKoU,kBAELtD,WAAW,IAAM9Q,KAAKgK,qBAAsB,MAG5ChK,KAAK+Q,kBAGF/Q,IACX,CAKA,kBAAAgK,GAKI,OAHAhK,KAAK+Q,kBAGA/Q,KAAK6Q,oBAKO7Q,KAAK2S,QAAQgC,iBAAiB,0BAEtClQ,QAASmQ,IACd,MAAMC,EAAUD,EAAK/B,cAAc,aAEnC,GAAIgC,GAAWA,EAAQC,YAAYjR,OAAQ,CACvC,MAAMkR,EAAcF,EAAQC,YAAYjR,OASxC,GANA+Q,EAAKI,aAAa,iBAAkB,WACpCJ,EAAKI,aAAa,oBAAqB,SACvCJ,EAAKI,aAAa,gBAAiBD,GACnCH,EAAKI,aAAa,oBAAqB,QAGnC5W,OAAO6W,WAAa7W,OAAO6W,UAAUC,QAAS,CAE9C,MAAMnM,EAAQ6L,EAAKO,aAAa,sBAC1B7I,EAAOsI,EAAKO,aAAa,qBAG/B,IAAIC,EAAc,GACdrM,IAAOqM,GAAe,WAAWrM,MACjCuD,IAAM8I,GAAe,WAAW9I,KAGpC,MAAM+I,EAAiB,CACnBC,UAAW,QACXC,UAAW,OACXC,QAAS,QACTC,MAAO,CAAE5I,KAAM,IAAK1B,KAAM,KAC1BuK,mBAAoB,CAAC,MAAO,SAAU,SAIpCC,EAAeP,EAAYvR,OAC7B8R,IACAN,EAAeD,YAAcO,GAGjC,MAAMC,EAAU,IAAIxX,OAAO6W,UAAUC,QAAQN,EAAMS,GAGnDT,EAAKiB,iBAAmBD,EAGxBhB,EAAKkB,iBAAiB,QAAS,KAC3BF,EAAQzK,SAGZyJ,EAAKkB,iBAAiB,OAAQ,KAC1BF,EAAQzK,QAEhB,CACJ,IAIJnL,KAAK+V,0BAEE/V,MAhEIA,IAiEf,CAEA,eAAA+Q,GAyBI,OAvBA/Q,KAAKgW,6BAEYhW,KAAK2S,QAAQgC,iBAAiB,oDAEtClQ,QAASmQ,IAEd,MAAMqB,EAAkBrB,EAAKiB,kBAAoBzX,OAAO6W,WAAWC,SAASgB,YAAYtB,GACpFqB,IAEAA,EAAgB9K,OAChB8K,EAAgBE,kBAIbvB,EAAKiB,iBAGZjB,EAAKwB,gBAAgB,kBACrBxB,EAAKwB,gBAAgB,qBACrBxB,EAAKwB,gBAAgB,iBACrBxB,EAAKwB,gBAAgB,uBAGlBpW,IACX,CAKA,eAAAqW,GACI,MAAMlC,EAAkB9V,SAASwU,cAAc,qBAC/C,OAAKsB,EAEDA,EAAgBrB,UAAUwB,SAAS,gBAC5B,SACAH,EAAgBrB,UAAUwB,SAAS,oBACnC,YAEA,SAPkB,QASjC,CAKA,gBAAAzD,GACI,MAAkC,cAA3B7Q,KAAKqW,iBAChB,CAKA,gBAAAC,CAAiBC,GAGb,OAFAvW,KAAK4I,WAAa2N,EAClBvW,KAAKkL,SACElL,IACX,CAKJ,eAAAqJ,CAAgBtJ,GACZ,GAAIA,EAAQ0I,MACR,IAAA,MAAWqH,KAAQ/P,EAAQ0I,MACvBzI,KAAK2O,QAAQmB,EAAK/N,KAAM+N,QAErB/P,EAAQ+P,OACf/P,EAAQ+P,KAAK/N,KAAOhC,EAAQ+P,KAAK/N,MAAQ,UACzC/B,KAAK2O,QAAQ5O,EAAQ+P,KAAK/N,KAAMhC,EAAQ+P,MAEhD,CAKA,uBAAAiG,GAEI/V,KAAKwW,sBAAwB,IAAMxW,KAAKoU,kBACxCpU,KAAK2S,QAAQmD,iBAAiB,SAAU9V,KAAKwW,sBAAuB,CAAEC,SAAS,IAG/EzW,KAAK0W,qBAAuB,IAAM1W,KAAKoU,kBAC3BpU,KAAK2J,SAIjB3J,KAAK2W,oBAAsB,IAAM3W,KAAKoU,kBACtChW,OAAO0X,iBAAiB,OAAQ9V,KAAK2W,qBAGrC3W,KAAK4W,sBAAyBC,IACZ,WAAVA,EAAE3X,KACFc,KAAKoU,mBAGb/V,SAASyX,iBAAiB,UAAW9V,KAAK4W,sBAC9C,CAKA,0BAAAZ,GACQhW,KAAKwW,wBACLxW,KAAK2S,QAAQmE,oBAAoB,SAAU9W,KAAKwW,8BACzCxW,KAAKwW,uBAGZxW,KAAK2W,sBACLvY,OAAO0Y,oBAAoB,OAAQ9W,KAAK2W,4BACjC3W,KAAK2W,qBAGZ3W,KAAK4W,wBACLvY,SAASyY,oBAAoB,UAAW9W,KAAK4W,8BACtC5W,KAAK4W,sBAEpB,CAKA,eAAAxC,GACqBpU,KAAK2S,QAAQgC,iBAAiB,oDACtClQ,QAASmQ,IACd,MAAMgB,EAAUhB,EAAKiB,kBAAoBzX,OAAO6W,WAAWC,SAASgB,YAAYtB,GAC5EgB,GACAA,EAAQzK,SAKQ9M,SAASsW,iBAAiB,iBAClClQ,QAAQmR,IACpBA,EAAQrB,UAEhB,CAKA,qBAAMwC,GAEF/W,KAAK+Q,wBAGCpN,MAAMoT,iBAChB,CAKA,uBAAAvN,GACI,MAAMwN,EAAc,KAChB,MAAMC,EAAW7Y,OAAO8Y,YAAc,IAChC/C,EAAkB9V,SAASwU,cAAc,qBAE3CsB,IACI8C,EACA9C,EAAgBrB,UAAU0B,IAAI,kBAE9BL,EAAgBrB,UAAUyB,OAAO,iBAAkB,kBAM/DyC,IACA5Y,OAAO0X,iBAAiB,SAAUkB,EACtC,CAKA,oBAAOG,CAAcpX,EAAU,IAC3B,OAAO,IAAIuI,QAAQ,CACfS,MAAO,gBACPH,YAAY,EACZW,oBAAoB,KACjBxJ,GAEX,CAKA,oBAAOqX,CAAcrX,EAAU,IAC3B,OAAO,IAAIuI,QAAQ,CACfS,MAAO,gBACPH,YAAY,EACZW,oBAAoB,KACjBxJ,GAEX,CAKA,eAAAsX,CAAgBtO,GAQZ,OANA/I,KAAKsX,YAAY,4CAGjBtX,KAAK8I,aAAeC,EACpB/I,KAAKoJ,SAASL,GAEP/I,IACX,CAKA,IAAA6M,GACI,OAAO7M,KAAKyU,gBAAgB,SAChC,CAEA,IAAAtJ,GACI,OAAOnL,KAAKyU,gBAAgB,SAChC,CAEA,QAAA8C,GACI,OAAOvX,KAAKyU,gBAAgB,YAChC,CAEA,MAAA+C,GACI,OAAOxX,KAAKyU,gBAAgB,SAChC,CAKA,WAAAgD,GACI,MAAMC,EAAe1X,KAAK2S,QAAQE,cAAc,mBAChD,GAAI6E,EAAc,CACdA,EAAa5E,UAAU0B,IAAI,SAG3B,MAAMmD,EAAc,KAChBD,EAAa5E,UAAUyB,OAAO,SAC9BmD,EAAaZ,oBAAoB,QAASa,IAG9CD,EAAa5B,iBAAiB,QAAS6B,EAAa,CAAEC,MAAM,IAC5D9G,WAAW6G,EAAa,IAC5B,CACA,OAAO3X,IACX,CAKA,iBAAA6X,CAAkB9K,EAAU+K,EAAMhL,EAAOiL,EAAO,aAC5C,MAAMjI,EAAO9P,KAAKyI,MAAM5H,IAAIkM,GAa5B,OAZI+C,IACAA,EAAKzL,MAAQyL,EAAKzL,OAAS,GAC3ByL,EAAKzL,MAAMmB,KAAK,CACZsS,OACAhL,QACAiL,SAGA/X,KAAK0I,iBAAmBqE,GACxB/M,KAAKkL,UAGNlL,IACX,CAKA,aAAAgY,CAAcjW,EAAMwK,EAAQlI,GACxB,MAAMyL,EAAO,CACT/N,OACAwK,SACAlI,SAMJ,OAHArE,KAAK2O,QAAQ5M,EAAM+N,GACnB9P,KAAK+O,cAAchN,GAEZ/B,IACX,EC//CJ,MAAMiY,mBAAmB1P,EACrB,WAAA7E,CAAY3D,EAAU,IAClB4D,MAAM,CACF6E,QAAS,MACT5E,UAAW,iBACR7D,IAIPC,KAAKkY,MAAQnY,EAAQmY,OAAS,UAC9BlY,KAAKsM,KAAOvM,EAAQuM,MAAQ,KAC5BtM,KAAKmY,UAAgC,IAArBpY,EAAQoY,SACxBnY,KAAKoY,iBAA8C,IAA5BrY,EAAQqY,gBAC/BpY,KAAKqY,gBAAkBtY,EAAQsY,kBAAmB,EAGlDrY,KAAKsY,YAAc,IACvB,CAEA,iBAAMhK,GACF,MAAmB,YAAftO,KAAKkY,MACElY,KAAKuY,qBACU,eAAfvY,KAAKkY,MACLlY,KAAKwY,wBAETxY,KAAKyY,oBAChB,CAEA,kBAAAA,GACI,MAAO,irDAuCX,CAEA,kBAAAF,GACI,MAAO,ybAYX,CAEA,qBAAAC,GACI,MAAO,o/DA8CX,CAEA,oBAAMpI,SACIzM,MAAMyM,iBAEZ,MAAM+B,EAAOnS,KAAKsY,YACZI,IAAYvG,EAGdA,IAEWA,EAAKwG,MACCxG,EAAKyG,YACZzG,EAAKpQ,KACDoQ,EAAK0G,SACT1G,EAAK4F,KACD5F,EAAK2G,SACE3G,EAAK4G,gBACT5G,EAAK6G,aAK1B,MAAMC,EAAgB9G,GAAMpS,SAASkZ,eAChB9G,GAAM8G,eACN9G,GAAMzO,aAAatE,WAAW6Z,eAC9B,GAErBjZ,KAAK6O,KAAO,CACR6J,UACAQ,UAAW/G,GAAMwG,OAASxG,GAAMyG,aAAezG,GAAMpQ,MAAQoQ,GAAM0G,UAAY,GAC/EC,SAAU3G,GAAM4F,MAAQ5F,GAAM2G,UAAY,GAC1CC,gBAAiB5G,GAAM4G,iBAAmB5G,GAAM6G,aAAe,GAC/Db,SAAUnY,KAAKmY,SACfC,gBAAiBpY,KAAKoY,gBACtBC,gBAAiBrY,KAAKqY,gBACtBc,YAAahH,GAAMpS,SAASoZ,aAAehH,GAAMgH,aAAe,GAChEC,QAASH,EACTI,WAAYJ,EAAc3U,OAAS,EACnCgI,KAAMtM,KAAKsM,MAGiBtM,KAAK6O,IACzC,CAKA,aAAMyK,CAAQnH,GAEVnS,KAAKsY,YAAcnG,EAGfA,SAEMnS,KAAKkL,QAGnB,CAKA,OAAAqO,GACI,OAAOvZ,KAAKsY,WAChB,CAKA,qBAAMjF,CAAgBF,EAAQT,EAAOC,GAEjC,OAAI3S,KAAKsY,aAA0D,mBAApCtY,KAAKsY,YAAYkB,sBACtCxZ,KAAKsY,YAAYkB,eAAerG,EAAQT,EAAOC,IAC9C,IAIX3S,KAAKsN,KAAK,SAAU,CAChB6F,SACAT,QACAC,UACAR,KAAMnS,KAAKsY,eAGR,EACX,EC1NJ,MAAMmB,mBAAmBC,EACvB,WAAAhW,CAAY3D,EAAU,IACpB4D,MAAM,CACJkV,SAAU,gBACV/L,MAAO,UACP6L,MAAO,gBACPG,SAAU,iBACVa,SAAU,uuFAoEP5Z,IAILC,KAAK4Z,WAAa,KAClB5Z,KAAK6Z,kBAAoB,IAC3B,CAKA,cAAMC,CAAStZ,EAAS,GAAIuZ,EAAQ,CAAA,SAC5BpW,MAAMmW,SAAStZ,EAAQuZ,GAGzBvZ,EAAO2R,MACTnS,KAAK4Z,WAAapZ,EAAO2R,KACzBnS,KAAK6Z,kBAAoBrZ,EAAO2R,KAAKpS,SAAWS,EAAO2R,KAAK6H,aAAe,CAAA,GAClED,EAAM5H,OAEfnS,KAAKia,eAAiBF,EAAM5H,KAEhC,CAKA,aAAA+H,CAAcC,GAGZ,OAFAna,KAAK4Z,WAAaO,EAClBna,KAAK6Z,kBAAoBM,GAAcpa,SAAWoa,GAAcH,aAAe,CAAA,EACxEha,IACT,CAKA,iBAAMoa,GACJ,MAAM1Q,EAAM1J,KAAK2J,SAGX0Q,EAAc3Q,GAAK+G,YAAc/G,GAAK4Q,oBAAsB,KAGlE,IAAIC,EAAiB,KACrB,GAAIva,KAAK4Z,WAAY,CACnB,MAAM9H,EAAc9R,KAAK6Z,mBAAmB/H,aACzB9R,KAAK4Z,WAAW7Z,SAAS+R,aACzB9R,KAAK4Z,WAAWI,aAAalI,YAEhDyI,EAAiB,CACf3B,YAAa5Y,KAAK4Z,WAAWhB,aAAe5Y,KAAK4Z,WAAWf,UAAY7Y,KAAK4Z,WAAWjB,OAAS,eACjGE,SAAU7Y,KAAK4Z,WAAWf,SAC1B/L,MAAO9M,KAAK4Z,WAAW9M,MACvBkM,YAAahZ,KAAK4Z,WAAWb,iBAAmB/Y,KAAK4Z,WAAWZ,YAChEF,SAAU9Y,KAAK4Z,WAAWd,UAAY,kBACtC0B,oBAAqB1I,EAAc,CACjCA,YAAavC,MAAMC,QAAQsC,GAAeA,EAAc,CAACA,IACvD,KAER,MAAW9R,KAAKia,iBACdM,EAAiB,CACf3B,YAAa5Y,KAAKia,eAClBpB,SAAU7Y,KAAKia,eACfnB,SAAU,oBAId,MAAO,CACLc,WAAYW,EACZF,YAAaA,EAAc,CACzBI,SAAUJ,EAAYI,UAAYJ,EAAYtY,MAAQsY,EAAYK,OAAS,eAC3E3Y,KAAMsY,EAAYtY,KAClB2Y,MAAOL,EAAYK,OACjB,KACJC,WAAYN,EAEhB,CAKA,wBAAMO,CAAmBlI,EAAOC,GAC9BD,EAAMmI,iBAGFzc,OAAO0c,QAAQxW,OAAS,EAC1BlG,OAAO0c,QAAQC,aAGT/a,KAAKgb,mBAAmBtI,EAAOC,EAEzC,CAKA,wBAAMqI,CAAmBtI,EAAOC,GAC9BD,EAAMmI,iBAEN,MAAMnR,EAAM1J,KAAK2J,SACbD,QACIA,EAAIuR,oBAGV7c,OAAOkC,SAAS4R,KAAO,GAE3B,CAKA,uBAAMgJ,CAAkBxI,EAAOC,GAC7BD,EAAMmI,iBAEN,MAAMnR,EAAM1J,KAAK2J,SAGjB,GAAID,EACF,UACQA,EAAIyR,SAAS,QACrB,OAAS9d,GAEP,UACQqM,EAAI0R,SAAS,SACrB,OAASC,GAEPrb,KAAKsN,KAAK,iBAAkB,CAC1BgO,UAAWtb,KAAK4Z,YAAY9M,OAAS1O,OAAOkC,SAASib,WAIvDzK,WAAW,KACTpH,GAAK8R,WAAW,kDACf,IACL,CACF,CAEJ,CAKA,aAAMC,SACE9X,MAAM8X,UAGZ,MAAM5C,EAAW7Y,KAAK4Z,YAAYf,UAAY7Y,KAAKia,eAC/CpB,GACF7Y,KAAK0b,QAAQ,CACX/C,MAAO,mBAAmBE,MAK9Bpa,QAAQnB,KAAK,yBAA0B,CACrC6U,KAAMnS,KAAK4Z,YAAYf,UAAY7Y,KAAKia,eACxCnN,MAAO9M,KAAK4Z,YAAY9M,MACxBgF,YAAa9R,KAAK6Z,mBAAmB/H,YACrC6J,0BAAA,IAAeC,MAAOC,eAE1B,CAKA,kBAAOC,CAAYpS,EAAKyQ,GACtB,MAAMP,EAAa,IAAIH,WAEvB,OADAG,EAAWM,cAAcC,GAClBzQ,EAAIyR,SAASvB,EACtB,ECpPF,MAAMmC,qBAAqBrC,EACzB,WAAAhW,CAAY3D,EAAU,IACpB4D,MAAM,CACJkV,SAAU,MACV/L,MAAO,OACP6L,MAAO,uBACPG,SAAU,eACVa,SAAU,s0CAiCP5Z,IAILC,KAAKgc,KAAO,IACd,CAKA,cAAMlC,CAAStZ,EAAS,GAAIuZ,EAAQ,CAAA,SAC5BpW,MAAMmW,SAAStZ,EAAQuZ,GAGzBvZ,EAAOwb,OACThc,KAAKgc,KAAOxb,EAAOwb,MAEjBjC,EAAMiC,OACRhc,KAAKgc,KAAOjC,EAAMiC,KAEtB,CAKA,OAAAC,CAAQD,GAEN,OADAhc,KAAKgc,KAAOA,GAAQ,KACbhc,IACT,CAKA,wBAAM4a,CAAmBlI,EAAOwJ,GAC9BxJ,EAAMmI,iBAGFzc,OAAO0c,QAAQxW,OAAS,EAC1BlG,OAAO0c,QAAQC,aAGT/a,KAAKgb,mBAAmBtI,EAAOwJ,EAEzC,CAKA,wBAAMlB,CAAmBtI,EAAOwJ,GAC9BxJ,EAAMmI,iBAEN,MAAMnR,EAAM1J,KAAK2J,SACbD,QACIA,EAAIuR,oBAGV7c,OAAOkC,SAAS4R,KAAO,GAE3B,CAKA,aAAMuJ,SACE9X,MAAM8X,UAGRzb,KAAKgc,MACPhc,KAAK0b,QAAQ,CACX/C,MAAO,SAAS3Y,KAAKgc,mBAKzBvd,QAAQnB,KAAK,iBAAkB,CAC7B0e,KAAMhc,KAAKgc,KACXL,0BAAA,IAAeC,MAAOC,eAE1B,CAKA,kBAAOM,CAAYzS,EAAKsS,GACtB,MAAMI,EAAe,IAAIL,aAEzB,OADAK,EAAaH,QAAQD,GACdI,EAAalR,QACtB,ECnHa,MAAMmR,kBAAkBC,EACnC,WAAA5Y,CAAY6J,EAAS,IAEjB5J,MAAM4J,GAENvN,KAAKuc,cAAgBhP,EAAOC,QAc5BxN,KAAKwc,aAAejP,EAAOkP,QAAU,CAAA,EAGjClP,EAAOmP,SAAWnP,EAAOkP,SACzBzc,KAAKwc,aAAejP,EAAOmP,QAI/B1c,KAAK2c,eAAiBpP,EAAOoP,iBAAkB,EAC/C3c,KAAK4c,iBAAmBrP,EAAOsP,YAAc,CAAA,EAG7C7c,KAAKwN,QAAU,KACfxN,KAAKyc,OAAS,KACdzc,KAAK0c,OAAS,KACd1c,KAAK6c,WAAa,KAClB7c,KAAK8c,aAAe,IAAIC,GAGxB/c,KAAKsL,YAAc,KAEdtL,KAAKiX,WAINjX,KAAKgd,iBAAmBhd,KAAKuc,cAAcU,mBAAoB,EAH/Djd,KAAKgd,iBAAmBhd,KAAKkd,qBACxBld,KAAKuc,cAAcU,mBAAoB,GAIhDjd,KAAKmd,qBAELnd,KAAKod,MAAQ,IAAIC,EACjBrd,KAAKuL,OAASA,GAEdvL,KAAKsd,aAAa,SAAU7D,YAC5BzZ,KAAKsd,aAAa,MAAOvB,aAC7B,CAKA,WAAMwB,SAGIvd,KAAKwd,kBAEXxd,KAAK8T,OAAOrJ,GAAG,oBAAqB,KAChCzK,KAAK8c,aAAaW,cAClBzd,KAAK0d,KAAKC,YACV3d,KAAK4d,cAAc,QAIvB5d,KAAK8T,OAAOrJ,GAAG,cAAe,KAC1BzK,KAAK8c,aAAaW,cAClBzd,KAAK0d,KAAKC,YACV3d,KAAK4d,cAAc,QAIvB5d,KAAK8T,OAAOrJ,GAAG,gBAAiB,KACvBzK,KAAKyQ,YACVzQ,KAAK8c,aAAae,sBAAsB7d,QAG5CA,KAAK8T,OAAOrJ,GAAG,gBAAiBzK,KAAK8d,eAAeC,KAAK/d,OAErDA,KAAKyQ,kBAECzQ,KAAKge,yBAIThe,KAAKie,cAGXje,KAAKke,WAAY,EAGjBle,KAAK8T,OAAOxG,KAAK,YAAa,CAAE5D,IAAK1J,MAGzC,CAEA,qBAAMwd,GACF,MAAMW,EAAcne,KAAK8c,aAAasB,mBAGtC,GAA2B,WAAvBD,EAAYhL,OAEZ,OADAnT,KAAK8T,OAAOxG,KAAK,oBAAqB,CAAE5D,IAAK1J,QACtC,EAIX,GAA2B,YAAvBme,EAAYhL,gBACYnT,KAAK8c,aAAae,sBAAsB7d,OAG5D,OAAO,EAKf,MAAMqe,EAAQre,KAAK8c,aAAawB,mBAGhC,GAAIte,KAAKyQ,WAEL,OADAzQ,KAAK8c,aAAayB,iBAAiBve,OAC5B,EAIXA,KAAK0d,KAAKc,aAAaH,EAAMA,OAC7B,MAAM7N,EAAO,IAAIiO,EAAK,CAAE7Z,GAAIyZ,EAAMK,cAC5BC,QAAanO,EAAK7E,QACxB,OAAKgT,EAAKC,SAMV5e,KAAK4d,cAAcpN,GACnBxQ,KAAK8c,aAAayB,iBAAiBve,OAC5B,IAPHA,KAAK8c,aAAaW,cAClBzd,KAAK8T,OAAOxG,KAAK,oBAAqB,CAAE5D,IAAK1J,KAAM3C,MAAOshB,EAAKthB,SACxD,EAMf,CAKA,sBAAM2gB,GAEF,MACMa,EADY,IAAIpe,gBAAgBrC,OAAOkC,SAASC,QACzBM,IAAI,SAG3Bie,EAAUD,GAAc7e,KAAK+e,oBAEnC,GAAID,EACA,IACI,MAAMrd,EAAQ,IAAIiK,EAAM,CAAE9G,GAAIka,IACxBH,QAAald,EAAMkK,QACzB,IAAKgT,EAAKC,UAAYD,EAAK9P,KAAKmQ,OAG5B,OAFAhf,KAAKif,wBACLxgB,QAAQnB,KAAK,+BAAgCqhB,EAAKO,YAItDlf,KAAKsL,YAAc7J,EAEfod,GACA7e,KAAKmf,kBAAkBL,GAGvB9e,KAAKyQ,aACLzQ,KAAKyQ,WAAW2O,OAAS,IAAIC,QACvBrf,KAAKyQ,WAAW2O,OAAOE,cAAc7d,EAAMmD,KAIrD5E,KAAK8T,OAAOxG,KAAK,eAAgB,CAAE7L,MAAOzB,KAAKsL,aAGnD,OAASjO,GAGL,GAFAoB,QAAQnB,KAAK,+BAAgCD,GAEzCwhB,IAAe7e,KAAK+e,oBAEpB/e,KAAKuf,6BACEV,EAAY,CAEnB,MAAMW,EAAgBxf,KAAK+e,oBAC3B,GAAIS,GAAiBA,IAAkBX,EACnC,IACI,MAAMY,EAAgB,IAAI/T,EAAM,CAAE9G,GAAI4a,UAChCC,EAAc9T,QACpB3L,KAAKsL,YAAcmU,EACnBzf,KAAK8T,OAAOxG,KAAK,eAAgB,CAAE7L,MAAOzB,KAAKsL,aAEnD,OAASoU,GACLjhB,QAAQnB,KAAK,wCAAyCoiB,GACtD1f,KAAKuf,oBACT,CAER,CACJ,CAER,CAMA,oBAAM5U,CAAelJ,GACjB,MAAMke,EAAgB3f,KAAKsL,YAC3BtL,KAAKsL,YAAc7J,EAGfA,GAASA,EAAMZ,IAAI,MACnBb,KAAKmf,kBAAkB1d,EAAMZ,IAAI,OAEjCb,KAAKuf,qBAGLvf,KAAKyQ,aACLzQ,KAAKyQ,WAAW2O,OAAS,IAAIC,QACvBrf,KAAKyQ,WAAW2O,OAAOE,cAAc7d,EAAMmD,KAIrD5E,KAAK8T,OAAOxG,KAAK,gBAAiB,CAC9B7L,QACAke,gBACAjW,IAAK1J,OAGT,MAAMmS,EAAOnS,KAAK4f,iBAOlB,OANIzN,GACAA,EAAK0N,cAAcpe,GAGvBzB,KAAK4J,OAAOkW,UAAU,CAACre,MAAMA,EAAMmD,IAAK,CAAE+C,SAAS,IAE5C3H,IACX,CAKA,cAAA+f,GACI,OAAO/f,KAAKsL,WAChB,CAKA,sBAAM2T,GACF,MAAMU,EAAgB3f,KAAKsL,YAQ3B,OAPAtL,KAAKsL,YAAc,KACnBtL,KAAKuf,qBAELvf,KAAK8T,OAAOxG,KAAK,gBAAiB,CAC9BqS,gBACAjW,IAAK1J,OAEFA,IACX,CAKA,iBAAAmf,CAAkBL,GACd,IACI,MAAM5f,EAAMc,KAAKggB,2BACjB/e,aAAayB,QAAQxD,EAAK4f,EAAQmB,WACtC,OAAS5iB,GACLoB,QAAQnB,KAAK,kCAAmCD,EACpD,CACJ,CAKA,iBAAA0hB,GACI,IACI,MAAM7f,EAAMc,KAAKggB,2BACjB,OAAO/e,aAAaC,QAAQhC,EAChC,OAAS7B,GAEL,OADAoB,QAAQnB,KAAK,kCAAmCD,GACzC,IACX,CACJ,CAKA,kBAAAkiB,GACI,IACI,MAAMrgB,EAAMc,KAAKggB,2BACjB/e,aAAa0B,WAAWzD,EAC5B,OAAS7B,GACLoB,QAAQnB,KAAK,mCAAoCD,EACrD,CACJ,CAKA,wBAAA2iB,GACI,MAAO,iBACX,CAKA,gBAAAE,CAAiBC,GACb,IACIlf,aAAayB,QAAQ,iBAAkByd,EAC3C,OAAS9iB,GACLoB,QAAQnB,KAAK,iCAAkCD,EACnD,CACJ,CAKA,mBAAA+iB,GACI,OAAQpgB,KAAKsL,WACjB,CAKA,kBAAA6R,GACI,MAAM5H,EAAsC,iBAAnBvV,KAAKuV,UACxBlX,SAASwU,cAAc7S,KAAKuV,WAC5BvV,KAAKuV,UAEX,IAAKA,EACD,MAAM,IAAI8K,MAAM,+BAA+BrgB,KAAKuV,aAIxD,MAAM+K,EAActgB,KAAKuc,eAAiBrf,OAAOwD,KAAKV,KAAKuc,eAAejY,OAAS,EAC7Eic,EAAavgB,KAAKwc,cAAgBtf,OAAOwD,KAAKV,KAAKwc,cAAclY,OAAS,EAG1Ekc,EAAgBxgB,KAAK2c,eAAiB,sQAOxC,iJAMJpH,EAAUkL,UAAY,2EAEZH,EAAc,kCAAoC,sEAE9CC,EAAa,iCAAmC,2BAChDC,0DAMdxgB,KAAK0gB,cAAgB,kBAGrBnL,EAAUzC,UAAU0B,IAAI,oBAGxBxU,KAAK2gB,wBAGL3gB,KAAK4gB,kBAAkBrL,EAC3B,CAKA,2BAAMoL,SACI3gB,KAAK6gB,qBACL7gB,KAAK8gB,oBACL9gB,KAAK+gB,kBACX/gB,KAAKghB,mBACT,CAKA,kBAAMH,GACG7gB,KAAKuc,eAA4D,IAA3Crf,OAAOwD,KAAKV,KAAKuc,eAAejY,SAE3DtE,KAAKwN,QAAU,IAAIlF,QAAQ,CACvB+B,YAAa,oBACVrK,KAAKuc,sBAGNvc,KAAKwN,QAAQtC,SACvB,CAKA,iBAAM4V,GACG9gB,KAAKwc,cAA0D,IAA1Ctf,OAAOwD,KAAKV,KAAKwc,cAAclY,SAGzDtE,KAAKyc,OAAS,IAAIwE,GAAO,CACrB5W,YAAa,gBACb6W,UAAWlhB,KAAKwc,aAAa2E,OAASnhB,KAAKmhB,OAASnhB,KAAK2Y,MACzDyI,WAAYphB,KAAKwc,aAAa4E,YAAc,IAC5CC,UAAWrhB,KAAKwc,aAAa6E,WAAarhB,KAAKqhB,UAC/CC,SAAUthB,KAAKwc,aAAa+E,WAAa,GACzCC,WAAYxhB,KAAKwc,aAAagF,YAAc,GAC5CC,YAAazhB,KAAKwc,aAAaiF,aAAe,OAC9CC,kBAAmB1hB,KAAKwc,aAAakF,oBAAqB,KACvD1hB,KAAKwc,qBAGNxc,KAAKyc,OAAOvR,SAGlBlL,KAAK0c,OAAS1c,KAAKyc,OACvB,CAKA,qBAAMsE,GACF,IAAK/gB,KAAK2c,eAAgB,OAE1B3c,KAAK6c,WAAa,IAAI5E,WAAW,CAC7B5N,YAAa,cACb6N,MAAOlY,KAAK4c,iBAAiB1E,OAAS,UACtCC,UAA6C,IAAnCnY,KAAK4c,iBAAiBzE,SAChCC,iBAA2D,IAA1CpY,KAAK4c,iBAAiBxE,gBACvCC,gBAAiBrY,KAAK4c,iBAAiBvE,kBAAmB,KACvDrY,KAAK4c,mBAIZ,MAAM+E,EAAkBtjB,SAASujB,eAAe,eAC5CD,SACM3hB,KAAK6c,WAAW3R,QAAO,EAAMyW,EAE3C,CAKA,iBAAAX,GAUI,GARA3iB,SAASyX,iBAAiB,QAAUpD,IAC5BA,EAAMmP,OAAOC,QAAQ,oCACrBpP,EAAMmI,iBACN7a,KAAKiT,mBAKT7U,OAAO2jB,eAAgB,CACvB,MAAMC,EAAiB,IAAID,eAAe,KACtC/hB,KAAKiiB,qBAETD,EAAeE,QAAQ7jB,SAASgO,MAChCrM,KAAKmiB,gBAAkBH,CAC3B,MAEIhiB,KAAKoiB,eAAiB,IAAMpiB,KAAKiiB,mBACjC7jB,OAAO0X,iBAAiB,SAAU9V,KAAKoiB,gBAI3CpiB,KAAKiiB,kBACT,CAKA,aAAAhP,GACI,IAAKjT,KAAKwN,QAAS,OAEnB,MAAM+H,EAAYlX,SAASwU,cAAc,qBACnCoE,EAAWjX,KAAKiX,WAElBA,EACA1B,EAAUzC,UAAUC,OAAO,iBAE3BwC,EAAUzC,UAAUC,OAAO,oBAC3B/S,KAAKgd,kBAAoBhd,KAAKgd,iBAG9Bhd,KAAKqiB,iBAAiBriB,KAAKgd,mBAG/Bhd,KAAK8T,OAAOxG,KAAK,kBAAmB,CAChCgV,UAAWtiB,KAAKgd,iBAChBuF,OAAQtL,GAEhB,CAKA,gBAAAgL,GACI,MAAM1M,EAAYlX,SAASwU,cAAc,qBACzC,IAAK0C,EAAW,OAChB,MAAM0B,EAAWjX,KAAKiX,WAElBA,GACA1B,EAAUzC,UAAU0B,IAAI,iBACnBe,EAAUzC,UAAUwB,SAAS,iBAC9BiB,EAAUzC,UAAU0B,IAAI,iBAG5Be,EAAUzC,UAAUyB,OAAO,gBAAiB,gBAGhDvU,KAAK8T,OAAOxG,KAAK,qBAAsB,CAAEiV,OAAQtL,GACrD,CAEA,kBAAAuL,GACI,OAAOnkB,SAASwU,cAAc,oBAClC,CAEA,QAAAoE,GACI,OAAO7Y,OAAO8Y,WAAa,GAC/B,CAEA,eAAAuL,GACI,OAAOziB,KAAKwiB,qBAAqB1P,UAAUwB,SAAS,gBACxD,CAKA,cAAM6G,CAAShJ,EAAM4H,EAAQ,CAAA,EAAIvZ,EAAS,CAAA,EAAIT,EAAU,IACpD,MAAMkG,QAAetC,MAAMwX,SAAShJ,EAAM4H,EAAOvZ,EAAQT,GAUzD,OARIC,KAAKyiB,mBACLziB,KAAKwiB,qBAAqB1P,UAAU0B,IAAI,gBAGxCxU,KAAKsY,aACLtY,KAAK0iB,iBAAiB1iB,KAAKsY,aAGxBrS,CACX,CAKA,gBAAAyc,CAAiBvQ,GAETnS,KAAKwN,SAAWxN,KAAKwN,QAAQmV,eAC7B3iB,KAAKwN,QAAQmV,cAAcxQ,EAAKrF,OAIhC9M,KAAKyc,QAAUzc,KAAKyc,OAAOkG,eAC3B3iB,KAAKyc,OAAOkG,cAAcxQ,EAAKrF,OAI/B9M,KAAK6c,YACL7c,KAAK6c,WAAWvD,QAAQnH,GAG5BnS,KAAK8T,OAAOxG,KAAK,sBAAuB,CAAE6E,QAC9C,CAKA,aAAAyL,CAAcpN,GACVxQ,KAAKyQ,WAAaD,EAEdxQ,KAAKyc,QACLzc,KAAKyc,OAAOmG,QAAQpS,GAIxBxQ,KAAK8T,OAAOxG,KAAK,sBAAuB,CAAEkD,QAC9C,CAKA,aAAAqS,GACI,OAAO7iB,KAAKyQ,UAChB,CAKA,gBAAA4R,CAAiBC,GACb,IACI,MAAMpjB,EAAMc,KAAK8iB,uBACjB7hB,aAAayB,QAAQxD,EAAK6jB,KAAKC,UAAUV,GAC7C,OAASjlB,GACLoB,QAAQnB,KAAK,gCAAiCD,EAClD,CACJ,CAKA,gBAAA6f,GACI,IACI,MAAMhe,EAAMc,KAAK8iB,uBACXG,EAAQhiB,aAAaC,QAAQhC,GACnC,OAAiB,OAAV+jB,EAAiBF,KAAKG,MAAMD,GAAS,IAChD,OAAS5lB,GAEL,OADAoB,QAAQnB,KAAK,gCAAiCD,GACvC,IACX,CACJ,CAKA,oBAAAylB,GAGI,MAAO,GADQ9iB,KAAK2Y,MAAQ3Y,KAAK2Y,MAAMhR,QAAQ,OAAQ,KAAKxI,cAAgB,gCAEhF,CAKA,iBAAAyhB,CAAkBrL,EAAY,MACrBA,IACDA,EAAYlX,SAASwU,cAAc,sBAGlC0C,IAEDvV,KAAKgd,iBACLzH,EAAUzC,UAAU0B,IAAI,oBAExBe,EAAUzC,UAAUyB,OAAO,oBAEnC,CAKA,iBAAA4O,GACI,IACI,MAAMjkB,EAAMc,KAAK8iB,uBACjB7hB,aAAa0B,WAAWzD,EAC5B,OAAS7B,GACLoB,QAAQnB,KAAK,iCAAkCD,EACnD,CACJ,CAEA,oBAAM+lB,GACF,MAAMvU,QAAa7O,KAAKqjB,SAAS,CAC7B1K,MAAO,kBACP2K,OAAQ,CACJ,CACIvhB,KAAM,mBAAoBwhB,KAAM,WAChCC,MAAO,mBAAoBC,UAAU,EACrC7a,YAAY,EACZ8a,eAAe,EACfC,iBAAiB,GAGrB,CACI5hB,KAAM,eAAgBwhB,KAAM,WAAYC,MAAO,eAAgBC,UAAU,EACzE7a,YAAY,EACZgb,cAAe,MAEfF,eAAe,EACfC,iBAAiB,EACjBE,WAAY,CAEVC,aAAc,iBAGpB,CACI/hB,KAAM,mBAAoBwhB,KAAM,WAAYC,MAAO,mBAAoBC,UAAU,EACjF7a,YAAY,EACZgb,cAAe,MAEfF,eAAe,EACfC,iBAAiB,EACjBE,WAAY,CAGhC,IAGYE,YAAa,oBAEblV,IACIA,EAAKmV,eAAiBnV,EAAKoV,iBAGP,aADDjkB,KAAKyQ,WAAWyT,KAAKrV,IAC/BmQ,OACLhf,KAAKod,MAAMwB,QAAQ,iCAEnB5e,KAAKod,MAAM/f,MAAM,6BAGrB2C,KAAKod,MAAM/f,MAAM,0BAG7B,CAEA,cAAAygB,CAAe3K,GACX,OAAQA,EAAOA,QACX,IAAK,SACDnT,KAAK8c,aAAaW,cAClBzd,KAAK0d,KAAKC,YACV3d,KAAK4d,cAAc,MACnB,MACJ,IAAK,UACD5d,KAAKmkB,cACL,MACJ,IAAK,kBACDnkB,KAAKojB,iBACL,MACJ,QACI3kB,QAAQnB,KAAK,0BAA0B6V,KAEnD,CAEA,iBAAMgR,GACF,GAAKnkB,KAAKyQ,WAKV,IAII,MAAMxK,QAAesF,GAAO6Y,cAAc,CACtCzL,MAAO,eACPrM,KAAM,KACN+X,aAAc,SACdzZ,MAAO5K,KAAKyQ,WACZ6S,OAAQ,CAEJ,CACIC,KAAM,SACNzL,KAAM,sBACNhZ,MAAO,EACPwlB,MAAO,qBAIX,CACIf,KAAM,QACNgB,QAAS,CAAEC,GAAI,GAAIC,GAAI,GACvB9L,MAAO,SACP2K,OAAQ,CACJ,CACIC,KAAM,QACNxhB,KAAM,SACNuK,KAAM,KACNoY,UAAW,CAAEC,MAAO,IAAKC,OAAQ,KACjCC,YAAa,qBACbC,KAAM,6BAMlB,CACIvB,KAAM,QACNgB,QAAS,CAAEC,GAAI,GAAIC,GAAI,GACvB9L,MAAO,UACP2K,OAAQ,CACJ,CACIC,KAAM,OACNxhB,KAAM,eACNyhB,MAAO,eACPC,UAAU,EACVc,QAAS,GACTM,YAAa,oBAEjB,CACItB,KAAM,QACNxhB,KAAM,QACNyhB,MAAO,gBACPC,UAAU,EACVc,QAAS,EACTM,YAAa,0BAEjB,CACItB,KAAM,MACNxhB,KAAM,eACNyhB,MAAO,eACPe,QAAS,EACTM,YAAa,oBAMzB,CACItB,KAAM,QACNgB,QAAS,GACT5L,MAAO,mBACP2L,MAAO,OACPhB,OAAQ,CACJ,CACIC,KAAM,SACNxhB,KAAM,WACNyhB,MAAO,WACPe,QAAS,EACTxkB,QAAS,CACL,CAAEglB,MAAO,mBAAoBjN,KAAM,gBACnC,CAAEiN,MAAO,kBAAmBjN,KAAM,gBAClC,CAAEiN,MAAO,iBAAkBjN,KAAM,iBACjC,CAAEiN,MAAO,sBAAuBjN,KAAM,gBACtC,CAAEiN,MAAO,MAAOjN,KAAM,SAG9B,CACIyL,KAAM,SACNxhB,KAAM,WACNyhB,MAAO,WACPe,QAAS,EACTxkB,QAAS,CACL,CAAEglB,MAAO,KAAMjN,KAAM,WACrB,CAAEiN,MAAO,KAAMjN,KAAM,WACrB,CAAEiN,MAAO,KAAMjN,KAAM,UACrB,CAAEiN,MAAO,KAAMjN,KAAM,YAG7B,CACIyL,KAAM,SACNxhB,KAAM,sBACNyhB,MAAO,sBACPe,QAAS,GAEb,CACIhB,KAAM,SACNxhB,KAAM,qBACNyhB,MAAO,4BACPe,QAAS,GAEb,CACIhB,KAAM,SACNxhB,KAAM,iBACNyhB,MAAO,iBACPe,QAAS,MAKzBS,WAAY,eACZC,WAAY,WAGZhf,GAAUA,EAAO2Y,QAOjB5e,KAAKklB,YAAY,iCACVjf,GAAWA,EAAO2Y,OAKjC,OAASvhB,GACLoB,QAAQpB,MAAM,8BAA+BA,GAC7C2C,KAAKmlB,UAAU,8BACnB,MAhJInlB,KAAKmlB,UAAU,iCAiJvB,CAKA,aAAMvY,GAGF5M,KAAKsL,YAAc,KAGftL,KAAKmiB,iBACLniB,KAAKmiB,gBAAgBiD,aAErBplB,KAAKoiB,gBACLhkB,OAAO0Y,oBAAoB,SAAU9W,KAAKoiB,gBAI1CpiB,KAAKyc,eACCzc,KAAKyc,OAAO7P,UAClB5M,KAAKyc,OAAS,KACdzc,KAAK0c,OAAS,MAGd1c,KAAKwN,gBACCxN,KAAKwN,QAAQZ,UACnB5M,KAAKwN,QAAU,YAIb7J,MAAMiJ,SAChB,CAKA,aAAOyY,CAAO9X,EAAS,IACnB,OAAO,IAAI8O,UAAU9O,EACzB,ECt6BW,MAAM+X,iBAAiB5L,EAClC,WAAAhW,CAAY3D,EAAU,IAClB4D,MAAM,CACFgV,MAAO,YACPK,YAAa,8BACbjB,KAAM,OACNuL,OAAQ,GACR3J,SAAU,mDACV/V,UAAW,4BACR7D,GAEX,CAEA,YAAM0J,SACI9F,MAAM8F,eACNzJ,KAAKulB,kBACf,CAEA,aAAM9J,SACI9X,MAAM8X,UACRzb,KAAKwlB,gBAECxlB,KAAKulB,kBAEnB,CAEA,mBAAM1F,CAAcpe,GACZzB,KAAKwlB,gBAECxlB,KAAKulB,kBAEnB,CAEA,cAAME,GACF,OAAIzlB,KAAK4K,MACE5K,KAAK4K,MACL5K,KAAK2J,SAAS2B,YACdtL,KAAK2J,SAAS2B,YAElB,IACX,CAEA,sBAAMia,GAEEvlB,KAAKwlB,iBACCxlB,KAAKwlB,SAAS5Y,UACpB5M,KAAKkR,YAAYlR,KAAKwlB,WAI1BxlB,KAAKwlB,SAAW,IAAIE,GAAS,CACzBrb,YAAa,sBACbiZ,OAAQtjB,KAAKD,QAAQujB,OACrBqC,oBAAoB,IAExB3lB,KAAKwK,SAASxK,KAAKwlB,UAEnB,MAAM5a,QAAc5K,KAAKylB,WACrB7a,GACA5K,KAAKwlB,SAASI,SAAShb,EAE/B,ECyHC,MAACib,GAAoB,IA5K1B,MACE,WAAAniB,GACE1D,KAAK8lB,UAAYC,EACjB/lB,KAAKgmB,qCAAwBxhB,GAC/B,CAWA,MAAA0G,CAAOyO,EAAU9K,EAAMoX,EAAW,CAAA,GAGhC,OAAOC,EAAShb,OAAOyO,EAAU9K,EAAMoX,EACzC,CAOA,OAAAE,CAAQxM,GACN,MAAMyM,EAAWF,EAAShD,MAAMvJ,GAEhC,OADA3Z,KAAKgmB,kBAAkBnhB,IAAI8U,EAAUyM,GAC9BA,CACT,CASA,cAAAC,CAAeD,EAAUvX,EAAMoX,EAAW,CAAA,GACxC,OAAOC,EAAShb,OAAOkb,EAAUvX,EAAMoX,EACzC,CAKA,UAAAK,GACEtmB,KAAKgmB,kBAAkBrS,QACvBuS,EAASI,YACX,CAQA,KAAAC,CAAMrnB,EAAKya,GAET,MAAO,CAAEza,MAAKya,WAAUyM,SADPpmB,KAAKmmB,QAAQxM,GAEhC,CAOA,SAAA6M,CAAUtnB,GACR,IAAA,MAAYya,EAAUyM,KAAapmB,KAAKgmB,kBACtC,GAAIrM,IAAaza,GAAOknB,IAAalnB,EACnC,MAAO,CAAEA,MAAKya,WAAUyM,YAG5B,OAAO,IACT,CAQA,iBAAAK,CAAkB1kB,EAAM+jB,GAEtB,OADA9lB,KAAK8lB,UAAUY,SAAS3kB,EAAM+jB,GACvB9lB,IACT,CAOA,QAAA2mB,CAAShN,GACP,MAAO,gCAAgCiN,KAAKjN,EAC9C,CAUA,WAAAkN,CAAYhY,EAAMiY,GAChB,MAAMC,EAAY,IAAKlY,GAEvB,IAAA,MAAY3P,EAAK8nB,KAAe9pB,OAAOqF,QAAQukB,GAE7C,GAAIjY,GAA4B,mBAAbA,EAAKhO,IACtBkmB,EAAU7nB,GAAO2P,EAAKhO,IAAI,GAAG3B,KAAO8nB,SAC/B,CAEL,MAAMjC,EAAQ/kB,KAAKinB,iBAAiBpY,EAAM3P,GAC1C6nB,EAAU7nB,GAAOc,KAAK8lB,UAAUoB,KAAKnC,EAAOiC,EAC9C,CAGF,OAAOD,CACT,CAUA,gBAAAE,CAAiBE,EAAKnL,GACpB,IAAKmL,IAAQnL,EAAM,OAGnB,GAAImL,GAA0B,mBAAZA,EAAItmB,IACpB,OAAOsmB,EAAItmB,IAAImb,GAIjB,MAAMtb,EAAOsb,EAAKoL,MAAM,KACxB,IAAIC,EAAUF,EAEd,IAAA,MAAWjoB,KAAOwB,EAAM,CACtB,GAAI2mB,QACF,OAKAA,GADGC,MAAMpoB,IAAQqQ,MAAMC,QAAQ6X,GACrBA,EAAQE,SAASroB,IAEjBmoB,EAAQnoB,EAEtB,CAEA,OAAOmoB,CACT,CASA,eAAAG,CAAgB7N,EAAU9K,GAGxB,MAAO,CAAE8K,WAAU9K,OACrB,GCxKFhP,GAAgBC,QAAQ,CAAEhB,MAAO,SAiFrB,MAAC2oB,GAAiB,OACjBC,GAAe,WAE5BvhB,GAAe,CACbshB,kBACAC"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/core/utils/ConsoleSilencer.js","../src/core/views/navigation/GroupSearchView.js","../src/core/views/navigation/Sidebar.js","../src/core/views/navigation/PageHeader.js","../src/core/pages/DeniedPage.js","../src/core/pages/NotFoundPage.js","../src/core/PortalApp.js","../src/core/forms/FormPage.js","../src/core/utils/MustacheFormatter.js","../src/core/views/user/ProfileOverviewSection.js","../src/core/models/Passkeys.js","../src/core/views/user/ProfileSessionsSection.js","../src/core/views/user/ProfileDevicesSection.js","../src/core/views/user/ProfileActivitySection.js","../src/core/views/user/ProfileGroupsSection.js","../src/core/views/user/UserProfileView.js","../src/core/views/user/ProfileSecuritySection.js","../src/core/views/user/ProfilePermissionsSection.js","../src/core/views/user/PasskeySetupView.js","../src/index.js"],"sourcesContent":["/**\n * ConsoleSilencer\n * \n * Purpose:\n * - Reduce console noise by filtering out non-critical logs in production.\n * - Keep critical logs (warn, error) by default in production.\n * - Allow runtime override via API, URL params, or localStorage without rebuilding.\n *\n * Usage:\n * import ConsoleSilencer from '@core/utils/ConsoleSilencer.js';\n * ConsoleSilencer.install(); // defaults to 'warn' in production, 'debug' in dev\n *\n * // Optional: set level at install-time\n * ConsoleSilencer.install({ level: 'error' });\n *\n * // Change level at runtime (optionally persist to localStorage)\n * ConsoleSilencer.setLevel('debug', { persist: true });\n *\n * // Temporarily change level around a block\n * ConsoleSilencer.withTemporaryLevel('debug', () => {\n * // noisy diagnostics here\n * });\n *\n * Runtime overrides (no code changes needed):\n * - URL: ?logLevel=debug|info|warn|error|silent\n * also accepts mojoLog and loglevel as param names\n * - localStorage: localStorage.setItem('MOJO_LOG_LEVEL', 'debug')\n *\n * Notes:\n * - This module is side-effect free until install() is called.\n * - Idempotent: calling install() multiple times won't double-wrap console.\n */\n\nconst LEVELS = Object.freeze({\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n log: 3, // alias to info\n debug: 4,\n trace: 5,\n all: 5, // alias\n});\n\n// Resolve environment in a bundler/browser-safe way\n// __DEV__ can be injected by bundlers (e.g., Vite define)\nconst isDev = (() => {\n // Vite/ESM: import.meta.env.DEV if available\n try {\n /* eslint-disable-next-line no-undef */\n if (typeof import.meta !== 'undefined' && import.meta && import.meta.env && typeof import.meta.env.DEV !== 'undefined') {\n return !!import.meta.env.DEV;\n }\n } catch {\n // ignore\n }\n\n // Prefer global __DEV__ if present (e.g., defined via bundler define)\n if (typeof globalThis !== 'undefined' && typeof globalThis.__DEV__ !== 'undefined') {\n try {\n return !!globalThis.__DEV__;\n } catch {\n // fall through if __DEV__ is not directly evaluable\n }\n }\n\n // Fallback detection (best effort)\n const hasProcess = typeof process !== 'undefined' && process && typeof process.env === 'object';\n if (hasProcess && typeof process.env.NODE_ENV === 'string') {\n return process.env.NODE_ENV !== 'production';\n }\n // Default conservative behavior: assume production if unknown\n return false;\n})();\n\nconst isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';\nconst GLOBAL = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : global);\n\n// Capture the original console just once\nconst ORIGINAL_CONSOLE = GLOBAL.console || {};\nconst ORIGINALS = {};\nlet INSTALLED = false;\nlet CURRENT_LEVEL = null;\n\n// Default levels\nconst DEFAULT_DEV_LEVEL = 'debug';\nconst DEFAULT_PROD_LEVEL = 'warn'; // keep warn + error by default\n\n// Helper: normalize/parse level\nfunction parseLevel(level) {\n if (typeof level === 'number') {\n // Clamp to bounds\n const min = LEVELS.silent;\n const max = LEVELS.trace;\n return Math.min(Math.max(level, min), max);\n }\n if (typeof level === 'string') {\n const key = level.toLowerCase();\n if (Object.prototype.hasOwnProperty.call(LEVELS, key)) {\n return LEVELS[key];\n }\n }\n return null;\n}\n\n// Helper: detect override via URLSearchParams\nfunction getUrlLogLevel() {\n if (!isBrowser || typeof location === 'undefined' || !location.search) return null;\n try {\n const params = new URLSearchParams(location.search);\n const keys = ['logLevel', 'loglevel', 'mojoLog'];\n for (const k of keys) {\n const v = params.get(k);\n if (v != null) {\n const parsed = parseLevel(v);\n if (parsed !== null) return parsed;\n }\n }\n } catch {\n // ignore\n }\n return null;\n}\n\n// Helper: detect override via localStorage\nfunction getStoredLogLevel() {\n if (!isBrowser || !('localStorage' in GLOBAL)) return null;\n try {\n const v = GLOBAL.localStorage.getItem('MOJO_LOG_LEVEL');\n if (v != null) {\n const parsed = parseLevel(v);\n if (parsed !== null) return parsed;\n }\n } catch {\n // ignore storage errors\n }\n return null;\n}\n\nfunction storeLogLevel(levelNumberOrName) {\n if (!isBrowser || !('localStorage' in GLOBAL)) return;\n try {\n const key = typeof levelNumberOrName === 'string'\n ? levelNumberOrName\n : levelNumberOrName === null\n ? null\n : Object.entries(LEVELS).find(([, num]) => num === levelNumberOrName)?.[0] ?? null;\n if (key) {\n GLOBAL.localStorage.setItem('MOJO_LOG_LEVEL', key);\n } else {\n GLOBAL.localStorage.removeItem('MOJO_LOG_LEVEL');\n }\n } catch {\n // ignore storage errors\n }\n}\n\n// Build a wrapped method that checks the current level before calling through\nfunction makeWrapper(methodName, methodLevel) {\n const original = ORIGINALS[methodName] || ORIGINAL_CONSOLE[methodName] || (() => {});\n return function wrappedConsoleMethod(...args) {\n // If the method is allowed at the current level, call through\n if (CURRENT_LEVEL >= methodLevel) {\n return original.apply(ORIGINAL_CONSOLE, args);\n }\n // Otherwise, noop\n return undefined;\n };\n}\n\n// Special wrapper for assert: only logs when assertion fails\nfunction makeAssertWrapper() {\n const original = ORIGINALS.assert || ORIGINAL_CONSOLE.assert || (() => {});\n return function wrappedAssert(condition, ...args) {\n // Only logs when condition is falsy; treat failed assert like error-level\n if (!condition) {\n if (CURRENT_LEVEL >= LEVELS.error) {\n return original.apply(ORIGINAL_CONSOLE, [condition, ...args]);\n }\n return undefined;\n }\n // When assertion passes, no-op\n return undefined;\n };\n}\n\nfunction determineInitialLevel(explicitLevel) {\n // 1) Explicit install option\n const explicit = parseLevel(explicitLevel);\n if (explicit !== null) return explicit;\n\n // 2) URL override\n const urlLevel = getUrlLogLevel();\n if (urlLevel !== null) return urlLevel;\n\n // 3) localStorage override\n const storedLevel = getStoredLogLevel();\n if (storedLevel !== null) return storedLevel;\n\n // 4) Environment default\n return parseLevel(isDev ? DEFAULT_DEV_LEVEL : DEFAULT_PROD_LEVEL);\n}\n\n// Construct a patched console that respects CURRENT_LEVEL\nfunction buildPatchedConsole() {\n const patched = { ...ORIGINAL_CONSOLE };\n\n // Save originals once\n const methodLevels = {\n // Critical\n error: LEVELS.error,\n warn: LEVELS.warn,\n\n // Informational\n info: LEVELS.info,\n log: LEVELS.info,\n dir: LEVELS.info,\n table: LEVELS.info,\n\n // Verbose / Debug\n debug: LEVELS.debug,\n group: LEVELS.debug,\n groupCollapsed: LEVELS.debug,\n groupEnd: LEVELS.debug,\n time: LEVELS.debug,\n timeEnd: LEVELS.debug,\n timeLog: LEVELS.debug,\n trace: LEVELS.trace,\n };\n\n // Capture originals and build wrappers\n for (const name of Object.keys(methodLevels)) {\n ORIGINALS[name] = ORIGINAL_CONSOLE[name] || (() => {});\n patched[name] = makeWrapper(name, methodLevels[name]);\n }\n\n // Special-case assert\n ORIGINALS.assert = ORIGINAL_CONSOLE.assert || (() => {});\n patched.assert = makeAssertWrapper();\n\n // Preserve any other console methods as-is (clear, profile, count, etc.)\n return patched;\n}\n\nconst ConsoleSilencer = {\n // Install the silencer (idempotent)\n install(options = {}) {\n if (INSTALLED) {\n // Already installed; update level if provided\n if (options && typeof options.level !== 'undefined') {\n this.setLevel(options.level, { persist: !!options.persist });\n }\n return this;\n }\n\n if (!GLOBAL || !ORIGINAL_CONSOLE) {\n // No console available; nothing to do\n INSTALLED = true; // Prevent re-entry\n return this;\n }\n\n CURRENT_LEVEL = determineInitialLevel(options.level);\n\n const patched = buildPatchedConsole();\n // Replace the global console\n GLOBAL.console = patched;\n\n INSTALLED = true;\n\n // Expose for quick manual access if desired\n GLOBAL.MOJOConsoleSilencer = this;\n\n return this;\n },\n\n // Uninstall and restore the original console\n uninstall() {\n if (!INSTALLED) return this;\n\n try {\n GLOBAL.console = ORIGINAL_CONSOLE;\n } catch {\n // ignore\n }\n INSTALLED = false;\n return this;\n },\n\n // Set current level at runtime; accepts string or number\n // levels: 'silent' | 'error' | 'warn' | 'info' | 'debug' | 'trace'\n setLevel(level, { persist = false } = {}) {\n const parsed = parseLevel(level);\n if (parsed === null) return this; // ignore invalid level\n CURRENT_LEVEL = parsed;\n if (persist) {\n storeLogLevel(level);\n }\n return this;\n },\n\n // Get the current numeric level\n getLevel() {\n return CURRENT_LEVEL;\n },\n\n // Get the current level name (best-effort)\n getLevelName() {\n const entry = Object.entries(LEVELS).find(([, num]) => num === CURRENT_LEVEL);\n return entry ? entry[0] : null;\n },\n\n // Convenience helpers\n criticalOnly({ persist = false } = {}) {\n return this.setLevel('warn', { persist });\n },\n\n errorsOnly({ persist = false } = {}) {\n return this.setLevel('error', { persist });\n },\n\n silent({ persist = false } = {}) {\n return this.setLevel('silent', { persist });\n },\n\n verbose({ persist = false } = {}) {\n return this.setLevel(isDev ? 'debug' : 'info', { persist });\n },\n\n allowAll({ persist = false } = {}) {\n return this.setLevel('trace', { persist });\n },\n\n // Run a block with a temporary level, then restore\n withTemporaryLevel(level, fn) {\n const prev = CURRENT_LEVEL;\n const parsed = parseLevel(level);\n if (parsed === null || typeof fn !== 'function') return fn?.();\n CURRENT_LEVEL = parsed;\n try {\n return fn();\n } finally {\n CURRENT_LEVEL = prev;\n }\n },\n\n // Expose levels map for consumers\n LEVELS,\n};\n\n// Optional: convenience named export alias\nexport default ConsoleSilencer;\nexport const installConsoleSilencer = (options) => ConsoleSilencer.install(options);","/**\n * GroupSearchView - Hierarchical tree-view search component\n * Extends SimpleSearchView to support parent/child relationships\n * Displays items in a tree structure based on parent property\n */\n\nimport SimpleSearchView from './SimpleSearchView.js';\n\nclass GroupSearchView extends SimpleSearchView {\n constructor(options = {}) {\n super({\n ...options,\n className: `group-search-view ${options.className || ''}`.trim()\n });\n\n // Tree-specific configuration\n this.showKind = options.showKind !== undefined ? options.showKind : true;\n this.parentField = options.parentField || 'parent';\n this.kindField = options.kindField || 'kind';\n this.treeData = []; // Processed hierarchical data\n this.flattenedItems = []; // Flattened tree for display\n \n // Visual customization\n this.showLines = options.showLines !== undefined ? options.showLines : true;\n }\n\n /**\n * Build tree hierarchy from flat list\n */\n buildTreeHierarchy(items) {\n if (!items || items.length === 0) {\n return [];\n }\n\n // Create a map of items by ID for quick lookup\n const itemsById = new Map();\n \n // First pass: add all items and extract parent objects\n items.forEach(item => {\n if (!itemsById.has(item.id)) {\n itemsById.set(item.id, {\n ...item,\n children: [],\n level: 0,\n hasChildren: false\n });\n }\n \n // If this item has a parent object, add the parent too\n const parentObj = item[this.parentField];\n if (parentObj && parentObj.id && !itemsById.has(parentObj.id)) {\n itemsById.set(parentObj.id, {\n ...parentObj,\n children: [],\n level: 0,\n hasChildren: false\n });\n }\n });\n\n const rootItems = [];\n\n // Build parent-child relationships\n itemsById.forEach((treeItem, itemId) => {\n const originalItem = items.find(i => i.id === itemId) || treeItem;\n const parentId = originalItem[this.parentField]?.id;\n\n if (parentId && itemsById.has(parentId)) {\n const parent = itemsById.get(parentId);\n parent.children.push(treeItem);\n parent.hasChildren = true;\n } else {\n rootItems.push(treeItem);\n }\n });\n\n // Calculate levels recursively\n const calculateLevels = (nodes, level = 0) => {\n nodes.forEach(node => {\n node.level = level;\n if (node.children.length > 0) {\n node.children.sort((a, b) => (a.name || '').localeCompare(b.name || ''));\n calculateLevels(node.children, level + 1);\n }\n });\n };\n\n rootItems.sort((a, b) => (a.name || '').localeCompare(b.name || ''));\n calculateLevels(rootItems);\n\n return rootItems;\n }\n\n /**\n * Flatten tree structure for rendering\n * Tracks ancestor \"last child\" flags for proper line rendering\n */\n flattenTree(nodes, result = [], ancestorLastFlags = []) {\n nodes.forEach((node, index) => {\n node._isLastChild = index === nodes.length - 1;\n node._ancestorLastFlags = [...ancestorLastFlags];\n \n // Check if this node is the last descendant in its branch\n // (all ancestors are last children AND this node is last child with no children)\n const allAncestorsLast = ancestorLastFlags.every(flag => flag);\n node._isLastDescendant = allAncestorsLast && node._isLastChild && \n (!node.children || node.children.length === 0);\n \n result.push(node);\n \n if (node.children && node.children.length > 0) {\n const newFlags = [...ancestorLastFlags, node._isLastChild];\n this.flattenTree(node.children, result, newFlags);\n }\n });\n \n return result;\n }\n\n /**\n * Second pass: compute which vertical lines should continue for each item\n * \n * Vertical line at segment s shows if there are more siblings coming at level s+1\n * We need to look ahead until we find an item at level s+1 or shallower\n */\n computeVerticalLines(flatList) {\n for (let i = 0; i < flatList.length; i++) {\n const item = flatList[i];\n item._continueVertical = [];\n \n for (let s = 0; s < item.level; s++) {\n // Look ahead to find next item at level s+1 or shallower\n let foundSibling = false;\n for (let j = i + 1; j < flatList.length; j++) {\n const futureItem = flatList[j];\n if (futureItem.level === s + 1) {\n // Found a sibling at this level - vertical should continue\n foundSibling = true;\n break;\n } else if (futureItem.level <= s) {\n // Went back to ancestor level or shallower - no more siblings\n break;\n }\n // Keep searching if futureItem.level > s+1 (still in deeper subtree)\n }\n item._continueVertical[s] = foundSibling;\n }\n }\n }\n\n /**\n * Update filtered items with tree structure\n */\n updateFilteredItems() {\n if (!this.collection) {\n this.filteredItems = [];\n this.treeData = [];\n this.flattenedItems = [];\n return;\n }\n\n // Server-side filtering is handled in performSearch() (from parent class)\n const items = this.collection.toJSON();\n this.treeData = this.buildTreeHierarchy(items);\n this.flattenedItems = this.flattenTree(this.treeData);\n \n // Second pass: compute vertical line continuation\n this.computeVerticalLines(this.flattenedItems);\n \n // Set filteredItems to flattened tree for parent class compatibility\n this.filteredItems = this.flattenedItems;\n\n this.updateResultsView();\n }\n\n /**\n * Get tree-specific item template\n */\n getDefaultItemTemplate() {\n return `\n <div class=\"tree-item-content\">\n <div class=\"tree-item-name\">{{name}}</div>\n {{#showKind}}\n <div class=\"tree-item-kind\">{{kind}}</div>\n {{/showKind}}\n </div>\n `;\n }\n\n /**\n * Process item template with tree structure\n */\n processItemTemplate(item) {\n // Process the base template\n let content = this.itemTemplate;\n content = content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, prop) => {\n if (prop === 'showKind') {\n return this.showKind ? 'true' : '';\n }\n return this.getNestedValue(item, prop) || '';\n });\n\n // Handle conditional sections for kind\n if (this.showKind) {\n content = content.replace(/\\{\\{#showKind\\}\\}(.*?)\\{\\{\\/showKind\\}\\}/gs, '$1');\n } else {\n content = content.replace(/\\{\\{#showKind\\}\\}.*?\\{\\{\\/showKind\\}\\}/gs, '');\n }\n\n // Build tree line segments\n // For an item at level N, we need N segments (one for each ancestor level)\n let lineSegments = '';\n if (this.showLines && item.level > 0) {\n for (let i = 0; i < item.level; i++) {\n const isLastSegment = (i === item.level - 1);\n \n if (isLastSegment) {\n // This segment connects directly to this item\n // Use _isLastChild to determine if this is └ or ├\n const segClass = item._isLastChild ? 'tree-seg tree-seg-last' : 'tree-seg tree-seg-mid';\n lineSegments += `<span class=\"${segClass}\"></span>`;\n } else {\n // Ancestor segment - vertical line continues if next item is deeper than this level\n const continueVertical = item._continueVertical && item._continueVertical[i];\n if (continueVertical) {\n lineSegments += `<span class=\"tree-seg tree-seg-vert\"></span>`;\n } else {\n lineSegments += `<span class=\"tree-seg\"></span>`;\n }\n }\n }\n }\n\n // Classes for the wrapper\n const hasChildren = item.hasChildren ? ' has-children' : '';\n const isLastChild = item._isLastChild ? ' is-last-child' : '';\n\n // Wrap in tree structure\n return `\n <div class=\"tree-item-wrapper${hasChildren}${isLastChild}\" data-tree-level=\"${item.level}\">\n <div class=\"tree-lines\">\n ${lineSegments}\n </div>\n <div class=\"tree-item-body flex-grow-1\">\n ${content}\n </div>\n </div>\n `;\n }\n\n /**\n * Get all root items\n */\n getRootItems() {\n return this.treeData;\n }\n\n /**\n * Get children of a specific node\n */\n getNodeChildren(nodeId) {\n const findNode = (nodes, targetId) => {\n for (const node of nodes) {\n if (node.id === targetId) {\n return node.children;\n }\n if (node.children.length > 0) {\n const found = findNode(node.children, targetId);\n if (found) return found;\n }\n }\n return null;\n };\n\n return findNode(this.treeData, nodeId) || [];\n }\n}\n\nexport default GroupSearchView;\n","/**\n * Sidebar - Simple sidebar navigation component for MOJO framework\n * Provides easy menu switching and dynamic configuration\n */\n\nimport View from '@core/View.js';\nimport GroupSearchView from './GroupSearchView.js';\nimport {GroupList, Group} from '@core/models/Group.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\n\n\nclass Sidebar extends View {\n constructor(options = {}) {\n super({\n tagName: 'nav',\n className: 'sidebar',\n id: 'sidebar',\n ...options\n });\n\n this.menus = new Map();\n this.activeMenuName = null;\n this.currentRoute = null;\n this.showToggle = options.showToggle; // Default to true\n this.isCollapsed = false;\n this.sidebarTheme = options.theme || 'sidebar-light';\n this.customView = null;\n if (this.options.groupHeader) this.groupHeader = this.options.groupHeader;\n\n // Group selector configuration\n // 'inline' (default) - replaces sidebar view\n // 'dialog' - opens in a modal dialog like TopNav\n this.groupSelectorMode = options.groupSelectorMode || 'inline';\n this.groupSelectorDialog = null;\n // Apply sidebar theme\n if (this.sidebarTheme) {\n this.addClass(this.sidebarTheme);\n }\n\n // Initialize menus\n this.initializeMenus(options);\n\n // Setup route change listeners like TopNav\n this.setupRouteListeners();\n\n // Auto-collapse on mobile if specified\n if (options.autoCollapseMobile !== false) {\n this.setupResponsiveBehavior();\n }\n }\n\n groupHeader = `\n {{#group.parent}}\n <div class=\"sidebar-parent-bar\" data-action=\"select-group-parent\">\n <div class=\"parent-info\">\n <span class=\"parent-label\">{{group.parent.kind}}:</span>\n <span class=\"parent-name collapsed-hidden\">{{group.parent.name}}</span>\n </div>\n <i class=\"bi bi-chevron-down parent-expand collapsed-hidden\"></i>\n </div>\n {{/group.parent}}\n <div class=\"sidebar-selected-group-row\" data-action=\"show-group-search\">\n <div class=\"selected-group-info\">\n <div class='selected-group-name collapsed-hidden'>{{group.name}}</div>\n <div class='selected-group-meta collapsed-hidden'>\n <span class=\"selected-group-kind\">{{group.kind}}</span>\n </div>\n </div>\n <i class=\"bi bi-chevron-down selected-group-chevron collapsed-hidden\"></i>\n </div>\n `;\n\n /**\n * Initialize sidebar and auto-switch to correct menu based on current route\n */\n async onInit() {\n await super.onInit();\n\n // Get current route from router\n const app = this.getApp();\n const router = app?.router;\n\n if (router) {\n const currentPath = router.getCurrentPath();\n if (currentPath) {\n this.autoSwitchToMenuForRoute(currentPath);\n }\n }\n\n // Initialize tooltips for nav items\n this.initializeTooltips();\n\n this.searchView = new GroupSearchView({\n noAppend: true,\n showExitButton: true,\n headerText: \"Select Group\",\n containerId: \"sidebar-search-container\",\n Collection: GroupList,\n itemTemplate: `\n <div class=\"p-3 border-bottom\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">#{{id}} {{kind}}</small>\n </div>\n `\n });\n this.addChild(this.searchView);\n this.searchView.on(\"item:selected\", (evt) => {\n console.log(evt);\n this.getApp().setActiveGroup(evt.model);\n });\n this.searchView.on(\"exit\", (item) => {\n console.log(item);\n this.hideGroupSearch();\n });\n }\n\n showGroupSearch() {\n if (this.groupSelectorMode === 'dialog') {\n this.showGroupSearchDialog();\n } else {\n // Inline mode (default)\n this.setClass('sidebar');\n this.showSearch = true;\n this.render();\n }\n }\n\n hideGroupSearch() {\n if (this.groupSelectorMode === 'dialog') {\n if (this.groupSelectorDialog) {\n this.groupSelectorDialog.hide();\n }\n } else {\n // Inline mode\n this.setClass('sidebar');\n this.showSearch = false;\n this.render();\n }\n }\n\n onActionShowGroupSearch() {\n this.showGroupSearch();\n }\n\n async onActionSelectGroupParent() {\n // select-group-parent\n const group = this.getApp().activeGroup;\n const result = await Dialog.confirm(`Are you sure you want to navigate to the '${group.get(\"parent.name\")}'?`);\n if (result) {\n this.getApp().showLoading();\n let parent = new Group({id: group.get(\"parent.id\")});\n await parent.fetch();\n this.getApp().setActiveGroup(parent);\n this.getApp().hideLoading();\n }\n }\n\n /**\n * Show group selector in a dialog (like TopNav)\n */\n async showGroupSearchDialog() {\n // Create or reuse collection instance (like GroupSelectorButton does)\n const collection = new GroupList();\n\n // Create GroupSearchView instance matching GroupSelectorButton pattern\n const searchView = new GroupSearchView({\n Collection: GroupList,\n collection: collection, // Pass the collection instance\n searchFields: ['name'],\n headerText: null,\n searchPlaceholder: \"Search groups...\",\n headerIcon: null,\n maxHeight: Math.min(600, window.innerHeight - 200),\n showExitButton: false,\n showKind: true, // Show kind badges (default: true)\n parentField: 'parent', // Field containing parent object\n kindField: 'kind', // Field containing kind/type\n autoExpandRoot: true, // Auto-expand root items (default: true)\n autoExpandAll: false, // Auto-expand all nodes (default: false)\n indentSize: 20, // Pixels per level (default: 20)\n showLines: true,\n });\n\n // Create dialog\n this.groupSelectorDialog = new Dialog({\n body: searchView,\n size: 'md',\n header: null,\n noBodyPadding: true,\n scrollable: false,\n buttons: [],\n closeButton: true\n });\n\n // Listen for item selection\n searchView.on('item:selected', (evt) => {\n console.log(evt);\n this.getApp().setActiveGroup(evt.model);\n if (this.groupSelectorDialog) {\n this.groupSelectorDialog.hide();\n }\n });\n\n // Clean up dialog reference when closed\n this.groupSelectorDialog.on('hidden', () => {\n this.groupSelectorDialog.destroy();\n this.groupSelectorDialog = null;\n });\n\n // Render and show the dialog\n await this.groupSelectorDialog.render(true, document.body);\n this.groupSelectorDialog.show();\n }\n\n /**\n * Find and switch to the menu that contains the given route\n */\n autoSwitchToMenuForRoute(route) {\n // Search through all menus to find one that contains this route\n for (const [menuName, menuConfig] of this.menus) {\n if (menuConfig.groupKind && !this.getApp().activeGroup)\n continue;\n if (this.menuContainsRoute(menuConfig, route)) {\n // Switch to this menu\n this._setActiveMenu(menuName);\n this.currentRoute = route;\n\n // Clear all active states and set new active item\n this.clearAllActiveStates();\n this.setActiveItemByRoute(route);\n\n // Re-render to show changes\n this.render();\n\n console.log(`Auto-switched to menu '${menuName}' for route '${route}'`);\n\n // Emit event for any listeners\n this.emit('menu-auto-switched', {\n menuName,\n route,\n config: menuConfig,\n sidebar: this\n });\n\n return true;\n }\n }\n\n return false; // No menu found for this route\n }\n\n /**\n * Clear active state from all menu items in all menus\n */\n clearAllActiveStates() {\n for (const [menuName, menuConfig] of this.menus) {\n for (const item of menuConfig.items || []) {\n item.active = false;\n if (item.children) {\n for (const child of item.children) {\n child.active = false;\n }\n }\n }\n }\n }\n\n /**\n * Set active state for item matching the given route\n */\n setActiveItemByRoute(route) {\n const normalizeRoute = (r) => {\n if (!r) return '/';\n // Decode URL-encoded characters (like %2F -> /)\n const decoded = decodeURIComponent(r);\n return decoded.startsWith('/') ? decoded : `/${decoded}`;\n };\n\n const targetRoute = normalizeRoute(route);\n\n // Search through all menus\n for (const [menuName, menuConfig] of this.menus) {\n if (menuConfig.groupKind && !this.getApp().activeGroup)\n continue;\n for (const item of menuConfig.items || []) {\n // Check main item\n if (item.route) {\n const itemRoute = normalizeRoute(item.route);\n if (this.routesMatch(targetRoute, itemRoute)) {\n item.active = true;\n this.activeMenuItem = item;\n return true;\n }\n }\n\n // Check children\n if (item.children) {\n for (const child of item.children) {\n if (child.route) {\n const childRoute = normalizeRoute(child.route);\n if (this.routesMatch(targetRoute, childRoute)) {\n child.active = true;\n item.active = true; // Parent also active\n return true;\n }\n }\n }\n }\n }\n }\n\n return false;\n }\n\n /**\n * Check if a menu contains a specific route in its items or children\n */\n menuContainsRoute(menuConfig, route) {\n const normalizeRoute = (r) => {\n if (!r) return '/';\n // Decode URL-encoded characters (like %2F -> /)\n const decoded = decodeURIComponent(r);\n return decoded.startsWith('/') ? decoded : `/${decoded}`;\n };\n\n const targetRoute = normalizeRoute(route);\n\n // Check each item in the menu\n for (const item of menuConfig.items || []) {\n // Check main item\n if (item.route) {\n const itemRoute = normalizeRoute(item.route);\n if (this.routesMatch(targetRoute, itemRoute)) {\n return true;\n }\n }\n\n // Check children\n if (item.children) {\n for (const child of item.children) {\n if (child.route) {\n const childRoute = normalizeRoute(child.route);\n if (this.routesMatch(targetRoute, childRoute)) {\n return true;\n }\n }\n }\n }\n }\n\n return false;\n }\n\n /**\n * Check if two routes match (using same logic as isItemActive)\n */\n routesMatch(currentRoute, itemRoute) {\n return this.getApp().router.doRoutesMatch(currentRoute, itemRoute);\n }\n\n getTemplate() {\n if (this.customView) {\n return '<div class=\"sidebar-container\" id=\"sidebar-custom-view-container\"></div>';\n }\n if (this.showSearch) return this.getSearchTemplate();\n return this.getMenuTemplate();\n }\n\n getSearchTemplate() {\n return `\n <div class=\"sidebar-container\" id=\"sidebar-search-container\">\n </div>\n `;\n }\n\n getMenuTemplate() {\n return `\n <div class=\"sidebar-container\">\n {{#data.currentMenu}}\n <!-- Header -->\n {{#header}}\n <div class=\"sidebar-header\">\n {{{header}}}\n {{#showToggle}}\n <button class=\"sidebar-toggle\" data-action=\"toggle-sidebar\"\n aria-label=\"Toggle Sidebar\">\n <i class=\"bi bi-chevron-left toggle-icon\"></i>\n <i class=\"bi bi-chevron-right toggle-icon\"></i>\n </button>\n {{/showToggle}}\n </div>\n {{/header}}\n\n <!-- Navigation Items -->\n <div class=\"sidebar-body\">\n <ul class=\"nav nav-pills flex-column sidebar-nav\" id=\"sidebar-nav-menu\">\n {{#items}}\n {{>nav-item}}\n {{/items}}\n </ul>\n </div>\n\n <!-- Footer -->\n {{#footer}}\n <div class=\"sidebar-footer\">\n {{{footer}}}\n </div>\n {{/footer}}\n {{/data.currentMenu}}\n\n {{^data.currentMenu}}\n <div class=\"sidebar-empty\">\n <p class=\"text-danger text-center\">No menu configured</p>\n </div>\n {{/data.currentMenu}}\n </div>\n `;\n }\n\n /**\n * Get template partials for rendering\n */\n getPartials() {\n return {\n 'nav-item': `\n {{#isDivider}}\n {{>nav-divider}}\n {{/isDivider}}\n {{#isSpacer}}\n {{>nav-spacer}}\n {{/isSpacer}}\n {{#isLabel}}\n {{>nav-label}}\n {{/isLabel}}\n\n {{^isDivider}}\n {{^isSpacer}}\n {{^isLabel}}\n <li class=\"nav-item\">\n {{#hasChildren}}\n <!-- Item with submenu -->\n <a class=\"nav-link {{#active}}active{{/active}} has-children collapsed\"\n data-bs-toggle=\"collapse\"\n href=\"#collapse-{{id}}\"\n role=\"button\"\n aria-expanded=\"{{#active}}true{{/active}}{{^active}}false{{/active}}\"\n data-action=\"toggle-submenu\">\n {{#icon}}<i class=\"{{icon}} me-2\"></i>{{/icon}}\n <span class=\"nav-text\">{{text}}</span>\n {{#badge}}\n <span class=\"{{badge.class}} ms-auto\">{{badge.text}}</span>\n {{/badge}}\n <i class=\"bi bi-chevron-down nav-arrow ms-auto\"></i>\n </a>\n <div class=\"collapse {{#active}}show{{/active}}\" id=\"collapse-{{id}}\" data-bs-parent=\"#sidebar-nav-menu\">\n <ul class=\"nav flex-column nav-submenu\">\n {{#children}}\n <li class=\"nav-item\">\n <a class=\"nav-link {{#active}}active{{/active}}\"\n {{#action}}data-action=\"{{action}}\"{{/action}}\n {{#href}}href=\"{{href}}\"{{/href}}>\n {{#icon}}<i class=\"{{icon}} me-2\"></i>{{/icon}}\n <span class=\"nav-text\">{{text}}</span>\n {{#badge}}\n <span class=\"{{badge.class}} ms-auto\">{{badge.text}}</span>\n {{/badge}}\n </a>\n </li>\n {{/children}}\n </ul>\n </div>\n {{/hasChildren}}\n {{^hasChildren}}\n <!-- Simple item -->\n <a class=\"nav-link {{#active}}active{{/active}} {{#disabled}}disabled{{/disabled}}\"\n {{#action}}{{^disabled}}data-action=\"{{action}}\"{{/disabled}}{{/action}}\n {{#href}}{{^disabled}}href=\"{{href}}\"{{/disabled}}{{/href}}>\n {{#icon}}<i class=\"{{icon}} me-2\"></i>{{/icon}}\n <span class=\"nav-text\">{{text}}</span>\n {{#badge}}\n <span class=\"{{badge.class}} ms-auto\">{{badge.text}}</span>\n {{/badge}}\n </a>\n {{/hasChildren}}\n </li>\n {{/isLabel}}\n {{/isSpacer}}\n {{/isDivider}}\n `,\n 'nav-divider': `\n <li class=\"nav-divider-item\">\n <hr class=\"nav-divider-line\">\n </li>\n `,\n 'nav-spacer': `\n <li class=\"nav-spacer-item\"></li>\n `,\n 'nav-label': `\n <li class=\"nav-item {{className}}\">\n <div class=\"nav-text px-3\">{{text}}</div>\n </li>\n `\n };\n }\n\n getGroupHeader() {\n return this.groupHeader;\n }\n\n /**\n * Add a menu configuration\n */\n addMenu(name, config) {\n if (config.groupKind && !config.header) {\n config.header = this.getGroupHeader();\n }\n\n this.menus.set(name, {\n name,\n groupKind: config.groupKind || null,\n header: config.header || null,\n footer: config.footer || null,\n items: config.items || [],\n data: config.data || {},\n className: config.className || \"sidebar sidebar-dark\"\n });\n\n\n // Set as active if it's the first menu\n if (!this.activeMenuName) {\n this._setActiveMenu(name);\n }\n\n return this;\n }\n\n _setActiveMenu(name) {\n this.showSearch = false;\n this.activeMenuName = name;\n const config = this.getCurrentMenuConfig();\n if (config.className) {\n this.setClass(config.className);\n } else {\n this.setClass('sidebar');\n }\n }\n\n /**\n * Set the active menu\n */\n async setActiveMenu(name) {\n if (!this.menus.has(name)) {\n console.warn(`Menu '${name}' not found`);\n return this;\n }\n\n const menuConfig = this.menus.get(name);\n\n if (menuConfig.groupKind) {\n this.lastGroupMenu = menuConfig;\n // Handle group kind logic here\n if (!this.getApp().activeGroup) {\n this.showGroupSearch();\n return;\n }\n }\n\n this._setActiveMenu(name);\n await this.render();\n // Emit event\n this.emit('menu-changed', {\n menuName: name,\n config: menuConfig,\n sidebar: this\n });\n\n return this;\n }\n\n getGroupMenu(group) {\n if (!group) {\n console.warn('No group provided');\n return null;\n }\n // Find menu by group.kind\n let targetMenu = this.lastGroupMenu;\n let anyGroupMenu = null;\n if (group._.kind) {\n for (const [menuName, menuConfig] of this.menus) {\n // Check if groupKind matches\n const matches = this._groupKindMatches(menuConfig.groupKind, group._.kind);\n\n if (matches) {\n targetMenu = menuConfig;\n break;\n } else if (menuConfig.groupKind === 'any') {\n anyGroupMenu = menuConfig;\n }\n }\n }\n\n if (!targetMenu) {\n return anyGroupMenu;\n }\n return targetMenu;\n }\n\n /**\n * Check if a groupKind matches the group's kind\n * Supports both single string and array of strings\n * @param {string|string[]} groupKind - Single kind or array of kinds\n * @param {string} kind - The group's kind to match\n * @returns {boolean} True if matches\n */\n _groupKindMatches(groupKind, kind) {\n if (!groupKind || !kind) return false;\n\n // Handle array of kinds\n if (Array.isArray(groupKind)) {\n return groupKind.includes(kind);\n }\n\n // Handle single kind string\n return groupKind === kind;\n }\n\n showMenuForGroup(group) {\n if (!group) {\n console.warn('No group provided');\n return;\n }\n // Find menu by group.kind\n let targetMenu = this.getGroupMenu(group);\n\n if (!targetMenu) {\n console.warn(`No menu found for group kind: ${group.kind}`);\n return;\n }\n this._setActiveMenu(targetMenu.name);\n this.render();\n // Emit event\n this.emit('menu-changed', {\n menuName: targetMenu.name,\n config: targetMenu,\n sidebar: this\n });\n return this;\n }\n\n /**\n * Get menu configuration\n */\n getMenuConfig(name) {\n return this.menus.get(name) || null;\n }\n\n /**\n * Get current active menu configuration\n */\n getCurrentMenuConfig() {\n return this.activeMenuName ? this.menus.get(this.activeMenuName) : null;\n }\n\n /**\n * Update menu configuration\n */\n updateMenu(name, updates) {\n const menu = this.menus.get(name);\n if (!menu) {\n console.warn(`Menu '${name}' not found`);\n return this;\n }\n\n // Deep merge updates\n Object.assign(menu, updates);\n\n // Re-render if this is the active menu\n if (this.activeMenuName === name) {\n this.render();\n }\n\n return this;\n }\n\n /**\n * Remove a menu\n */\n removeMenu(name) {\n this.menus.delete(name);\n\n // If this was the active menu, switch to another or clear\n if (this.activeMenuName === name) {\n const remainingMenus = Array.from(this.menus.keys());\n this.activeMenuName = remainingMenus.length > 0 ? remainingMenus[0] : null;\n this.render();\n }\n\n return this;\n }\n\n /**\n * Get view data for template rendering\n */\n async onBeforeRender() {\n const currentMenu = this.getCurrentMenuConfig();\n\n if (!currentMenu) {\n return { currentMenu: null };\n }\n\n let subData = {\n version: this.getApp().version || null,\n group: this.getApp().activeGroup || null,\n user: this.getApp.activeUser || null\n };\n // Process menu data through template if it contains handlebars\n this.data = {\n currentMenu: {\n header: this.renderTemplateString(currentMenu.header || '', subData),\n footer: this.renderTemplateString(currentMenu.footer || '', subData),\n items: this.processNavItems(currentMenu.items, currentMenu.groupKind),\n data: currentMenu.data,\n showToggle: this.showToggle\n }\n\n };\n }\n\n async onAfterRender() {\n // Re-initialize tooltips after render, but only if collapsed\n if (this.isCollapsedState()) {\n // Small delay to ensure DOM is fully rendered\n setTimeout(() => this.initializeTooltips(), 50);\n } else {\n this.destroyTooltips();\n }\n }\n\n setCustomView(view) {\n if (this.customView) {\n this.removeChild(this.customView.id);\n }\n this.customView = view;\n if (view) {\n view.containerId = 'sidebar-custom-view-container';\n this.addChild(view);\n }\n this.render();\n return this;\n }\n\n clearCustomView() {\n if (this.customView) {\n this.removeChild(this.customView.id);\n this.customView = null;\n }\n this.render();\n return this;\n }\n\n /**\n * Process navigation items - add IDs, active states, and proper hrefs\n */\n processNavItems(items, groupKind) {\n const app = this.getApp();\n const activeUser = app?.activeUser;\n const activeGroup = app?.activeGroup;\n\n // Helper function to normalize and update route with group parameter\n const updateRouteWithGroup = (route) => {\n let normalizedRoute = route;\n \n // Convert path format (/forms) to query string format (?page=forms)\n if (route.startsWith('/') && !route.includes('?')) {\n const pageName = route.substring(1) || 'home';\n normalizedRoute = `?page=${pageName}`;\n }\n \n // Add group parameter if needed\n if (groupKind && activeGroup && activeGroup.id) {\n const separator = normalizedRoute.includes('?') ? '&' : '?';\n return `${normalizedRoute}${separator}group=${activeGroup.id}`;\n }\n return normalizedRoute;\n };\n\n return items.map((item, index) => {\n // Handle divider items\n if (item === \"\" || (typeof item === 'object' && item.divider)) {\n return {\n isDivider: true,\n id: `divider-${index}`\n };\n }\n\n // Handle spacer items\n if (typeof item === 'object' && item.spacer) {\n return {\n isSpacer: true,\n id: `spacer-${index}`\n };\n }\n\n const processedItem = { ...item };\n\n // Check permissions - skip item if user doesn't have required permissions\n if (processedItem.permissions) {\n if (!activeUser || !activeUser.hasPermission(processedItem.permissions)) {\n return null; // Will be filtered out\n }\n }\n\n // Check requiresGroupKind - skip item if group kind doesn't match\n if (processedItem.requiresGroupKind) {\n const groupKind = activeGroup?._.kind || activeGroup?.kind;\n if (!groupKind || !this._groupKindMatches(processedItem.requiresGroupKind, groupKind)) {\n return null; // Will be filtered out\n }\n }\n\n if (processedItem.kind === 'label') {\n processedItem.isLabel = true;\n if (!processedItem.id) {\n processedItem.id = `nav-label-${index}`;\n }\n return processedItem;\n }\n\n // Generate ID if not provided\n if (!processedItem.id) {\n processedItem.id = `nav-${index}`;\n }\n\n // Use route directly as href (like TopNav does)\n if (processedItem.route) {\n processedItem.href = updateRouteWithGroup(processedItem.route);\n } else if (processedItem.page) {\n // If only page is provided, convert to route format\n const baseRoute = processedItem.page.startsWith('/') ? processedItem.page : `/${processedItem.page}`;\n processedItem.href = updateRouteWithGroup(baseRoute);\n processedItem.route = processedItem.href; // Store for active matching\n }\n\n // Active state is already set on the item object, no need to calculate\n\n // Process children\n if (processedItem.children) {\n processedItem.children = processedItem.children.map(child => {\n const processedChild = { ...child };\n\n // Check permissions for child items\n if (processedChild.permissions && activeUser) {\n if (!activeUser.hasPermission(processedChild.permissions)) {\n return null; // Will be filtered out\n }\n }\n\n // Check requiresGroupKind for child items\n if (processedChild.requiresGroupKind) {\n const groupKind = activeGroup?._.kind || activeGroup?.kind;\n if (!groupKind || !this._groupKindMatches(processedChild.requiresGroupKind, groupKind)) {\n return null; // Will be filtered out\n }\n }\n\n // Use route directly as href\n if (processedChild.route) {\n processedChild.href = updateRouteWithGroup(processedChild.route);\n } else if (processedChild.page) {\n const baseRoute = processedChild.page.startsWith('/') ? processedChild.page : `/${processedChild.page}`;\n processedChild.href = updateRouteWithGroup(baseRoute);\n processedChild.route = processedChild.href;\n }\n\n // Active state is already set on the child object\n return processedChild;\n }).filter(child => child !== null); // Filter out permission-denied children\n\n // Update hasChildren flag after filtering children\n processedItem.hasChildren = !!(processedItem.children && processedItem.children.length > 0);\n } else {\n // Add hasChildren flag for template logic\n processedItem.hasChildren = false;\n }\n\n return processedItem;\n }).filter(item => item !== null); // Filter out permission-denied items\n }\n\n\n\n\n /**\n * Check if navigation item should be active (similar to TopNav)\n */\n isItemActive(item) {\n if (!item.route || !this.currentRoute) {\n return false;\n }\n\n const normalizeRoute = (route) => {\n if (!route) return '/';\n // Decode URL-encoded characters (like %2F -> /)\n const decoded = decodeURIComponent(route);\n return decoded.startsWith('/') ? decoded : `/${decoded}`;\n };\n\n const itemRoute = normalizeRoute(item.route);\n const currentRoute = normalizeRoute(this.currentRoute);\n\n if (itemRoute === '/' && currentRoute === '/') {\n return true;\n }\n\n if (itemRoute !== '/' && currentRoute !== '/') {\n return currentRoute.startsWith(itemRoute) || currentRoute === itemRoute;\n }\n\n return false;\n }\n\n /**\n * Update active item based on current route (like TopNav)\n */\n async updateActiveItem(route) {\n this.currentRoute = route;\n\n // Clear all active states and set new active item\n this.clearAllActiveStates();\n this.setActiveItemByRoute(route);\n\n await this.render();\n return this;\n }\n\n /**\n * Action handler: Toggle submenu\n */\n async handleActionToggleSubmenu(event, element) {\n const arrow = element.querySelector('.nav-arrow');\n if (arrow) {\n arrow.classList.toggle('rotated');\n }\n }\n\n /**\n * Action handler: Toggle sidebar collapsed/expanded state\n */\n async handleActionToggleSidebar(event, element) {\n this.toggleSidebar();\n }\n\n onActionShowGroupMenu(action, event, el) {\n this.setActiveMenu(\"group_default\");\n\n return false;\n }\n\n async onActionDefault(action, event, el) {\n const config = this.getCurrentMenuConfig();\n if (!config) return;\n\n // Helper to recursively search for action in items and children\n const findAndExecuteHandler = (items) => {\n for (const item of items) {\n if ((item.action == action) && item.handler) {\n item.handler(action, event, el, this.getApp());\n return true;\n }\n // Check children recursively\n if (item.children && item.children.length > 0) {\n if (findAndExecuteHandler(item.children)) {\n return true;\n }\n }\n }\n return false;\n };\n\n return findAndExecuteHandler(config.items);\n }\n\n /**\n * Get all menu names\n */\n getMenuNames() {\n return Array.from(this.menus.keys());\n }\n\n /**\n * Check if menu exists\n */\n hasMenu(name) {\n return this.menus.has(name);\n }\n\n /**\n * Clear all menus\n */\n clearMenus() {\n this.menus.clear();\n this.activeMenuName = null;\n this.render();\n return this;\n }\n\n /**\n * Set data for current menu\n */\n setMenuData(data) {\n const currentMenu = this.getCurrentMenuConfig();\n if (currentMenu) {\n currentMenu.data = { ...currentMenu.data, ...data };\n this.render();\n }\n return this;\n }\n\n /**\n * Get data for current menu\n */\n getMenuData() {\n const currentMenu = this.getCurrentMenuConfig();\n return currentMenu ? currentMenu.data : {};\n }\n\n /**\n * Setup listeners for route change events (like TopNav)\n */\n setupRouteListeners() {\n const app = this.getApp();\n if (app && app.events) {\n app.events.on([\"page:showing\"], (data) => {\n this.onRouteChanged(data);\n });\n app.events.on(\"group:changed\", (data) => {\n this.showMenuForGroup(data.group);\n });\n app.events.on(\"portal:user-changed\", (data) => {\n this.render();\n });\n }\n }\n\n /**\n * Handle route changed event - auto-switch menu and update active item\n */\n /**\n * Handle route changed event.\n *\n * Resolution order for \"which menu to show\":\n * 1. The menu that contains the new route (autoSwitchToMenuForRoute).\n * 2. The menu named by `page.sidebarMenu` (declared on the Page class).\n * 3. The first non-group menu registered — prevents \"last-menu-wins\" default.\n *\n * Pages that don't belong to any menu are called \"homeless\" pages.\n * Declare `sidebarMenu = 'menuName'` on the Page class to pin them to a menu:\n *\n * class SettingsPage extends Page {\n * sidebarMenu = 'default'; // show 'default' sidebar on this page\n * }\n */\n onRouteChanged(data) {\n if (data.page && data.page.route) {\n const route = data.page.route;\n if (this.activeMenuItem && this.routesMatch(route, this.activeMenuItem.route)) {\n return;\n }\n\n // 1. Try to auto-switch to the menu that contains this route\n const switchedMenu = this.autoSwitchToMenuForRoute(route);\n\n if (switchedMenu) {\n console.log(`Route changed to '${route}', auto-switched menu`);\n return;\n }\n\n // 2. \"Homeless\" page — route is not in any menu.\n // Check if the page class declares a preferred sidebarMenu name.\n const preferredMenu = data.page.sidebarMenu || data.page.options?.sidebarMenu || null;\n if (preferredMenu && this.menus.has(preferredMenu)) {\n this._setActiveMenu(preferredMenu);\n this.clearAllActiveStates();\n this.render();\n console.log(`Homeless route '${route}' — switched to page-declared sidebarMenu '${preferredMenu}'`);\n return;\n }\n\n // 3. Fall back to the first non-group menu so we never\n // accidentally land on the last-registered menu.\n let fallbackMenu = null;\n for (const [menuName, menuConfig] of this.menus) {\n if (!menuConfig.groupKind) {\n fallbackMenu = menuName;\n break;\n }\n }\n\n if (fallbackMenu && this.activeMenuName !== fallbackMenu) {\n this._setActiveMenu(fallbackMenu);\n console.log(`Homeless route '${route}' — fell back to first non-group menu '${fallbackMenu}'`);\n }\n\n // Always clear active states and re-render for homeless pages\n this.clearAllActiveStates();\n this.updateActiveItem(route);\n this.render();\n }\n }\n\n /**\n * Toggle sidebar between collapsed and expanded states\n */\n toggleSidebar() {\n const portalContainer = document.querySelector('.portal-container');\n if (!portalContainer) return;\n\n // Hide any visible tooltips before state change\n this.hideAllTooltips();\n\n const isCurrentlyCollapsed = portalContainer.classList.contains('collapse-sidebar');\n const isCurrentlyHidden = portalContainer.classList.contains('hide-sidebar');\n\n if (isCurrentlyHidden) {\n // Hidden -> Normal\n portalContainer.classList.remove('hide-sidebar');\n this.isCollapsed = false;\n this.destroyTooltips();\n } else if (isCurrentlyCollapsed) {\n // Collapsed -> Normal\n portalContainer.classList.remove('collapse-sidebar');\n this.isCollapsed = false;\n this.destroyTooltips();\n } else {\n // Normal -> Collapsed\n portalContainer.classList.add('collapse-sidebar');\n this.isCollapsed = true;\n // Initialize tooltips with delay to ensure DOM is ready\n setTimeout(() => this.initializeTooltips(), 150);\n }\n\n return this;\n }\n\n /**\n * Set sidebar state programmatically\n */\n setSidebarState(state) {\n const portalContainer = document.querySelector('.portal-container');\n if (!portalContainer) return this;\n\n // Remove all state classes first\n portalContainer.classList.remove('collapse-sidebar', 'hide-sidebar');\n\n switch (state) {\n case 'collapsed':\n portalContainer.classList.add('collapse-sidebar');\n this.isCollapsed = true;\n break;\n case 'hidden':\n portalContainer.classList.add('hide-sidebar');\n this.isCollapsed = false;\n break;\n case 'normal':\n default:\n this.isCollapsed = false;\n break;\n }\n\n // Handle tooltips based on state\n if (this.isCollapsed) {\n // Hide any visible tooltips first\n this.hideAllTooltips();\n // Initialize tooltips when collapsed\n setTimeout(() => this.initializeTooltips(), 100);\n } else {\n // Destroy tooltips when not collapsed\n this.destroyTooltips();\n }\n\n return this;\n }\n\n /**\n * Initialize tooltips for nav items when sidebar is collapsed\n */\n initializeTooltips() {\n // Clean up existing tooltips first\n this.destroyTooltips();\n\n // Only initialize tooltips in collapsed state\n if (!this.isCollapsedState()) {\n return this;\n }\n\n // Auto-generate tooltips from nav-text content\n const navLinks = this.element.querySelectorAll('.sidebar-nav .nav-link');\n\n navLinks.forEach((link) => {\n const navText = link.querySelector('.nav-text');\n\n if (navText && navText.textContent.trim()) {\n const tooltipText = navText.textContent.trim();\n\n // Set Bootstrap tooltip attributes\n link.setAttribute('data-bs-toggle', 'tooltip');\n link.setAttribute('data-bs-placement', 'right');\n link.setAttribute('data-bs-title', tooltipText);\n link.setAttribute('data-bs-container', 'body');\n\n // Initialize Bootstrap tooltip with better config\n if (window.bootstrap && window.bootstrap.Tooltip) {\n // Extract custom theme/size if specified\n const theme = link.getAttribute('data-tooltip-theme');\n const size = link.getAttribute('data-tooltip-size');\n\n // Build custom class list\n let customClass = '';\n if (theme) customClass += `tooltip-${theme} `;\n if (size) customClass += `tooltip-${size}`;\n\n // Build options object\n const tooltipOptions = {\n placement: 'right',\n container: 'body',\n trigger: 'hover',\n delay: { show: 500, hide: 100 },\n fallbackPlacements: ['top', 'bottom', 'left']\n };\n\n // Only add customClass if it has a value\n const trimmedClass = customClass.trim();\n if (trimmedClass) {\n tooltipOptions.customClass = trimmedClass;\n }\n\n const tooltip = new window.bootstrap.Tooltip(link, tooltipOptions);\n\n // Store tooltip instance for better management\n link._tooltipInstance = tooltip;\n\n // Add event listeners to prevent stuck tooltips\n link.addEventListener('click', () => {\n tooltip.hide();\n });\n\n link.addEventListener('blur', () => {\n tooltip.hide();\n });\n }\n }\n });\n\n // Add global event listeners to hide tooltips\n this.addTooltipHideListeners();\n\n return this;\n }\n\n destroyTooltips() {\n // Remove global tooltip hide listeners\n this.removeTooltipHideListeners();\n\n const navLinks = this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle=\"tooltip\"]');\n\n navLinks.forEach((link) => {\n // Use stored instance first, then try to get it\n const tooltipInstance = link._tooltipInstance || window.bootstrap?.Tooltip?.getInstance(link);\n if (tooltipInstance) {\n // Force hide before dispose\n tooltipInstance.hide();\n tooltipInstance.dispose();\n }\n\n // Clean up stored reference\n delete link._tooltipInstance;\n\n // Remove tooltip attributes\n link.removeAttribute('data-bs-toggle');\n link.removeAttribute('data-bs-placement');\n link.removeAttribute('data-bs-title');\n link.removeAttribute('data-bs-container');\n });\n\n return this;\n }\n\n /**\n * Get current sidebar state\n */\n getSidebarState() {\n const portalContainer = document.querySelector('.portal-container');\n if (!portalContainer) return 'normal';\n\n if (portalContainer.classList.contains('hide-sidebar')) {\n return 'hidden';\n } else if (portalContainer.classList.contains('collapse-sidebar')) {\n return 'collapsed';\n } else {\n return 'normal';\n }\n }\n\n /**\n * Check if sidebar is collapsed\n */\n isCollapsedState() {\n return this.getSidebarState() === 'collapsed';\n }\n\n /**\n * Enable/disable toggle button\n */\n setToggleEnabled(enabled) {\n this.showToggle = enabled;\n this.render();\n return this;\n }\n\n /**\n * Initialize menus from options\n */\n initializeMenus(options) {\n if (options.menus) {\n for (const menu of options.menus) {\n this.addMenu(menu.name, menu);\n }\n } else if (options.menu) {\n options.menu.name = options.menu.name || \"default\";\n this.addMenu(options.menu.name, options.menu);\n }\n }\n\n /**\n * Add global listeners to hide tooltips when needed\n */\n addTooltipHideListeners() {\n // Hide tooltips on scroll\n this._tooltipScrollHandler = () => this.hideAllTooltips();\n this.element.addEventListener('scroll', this._tooltipScrollHandler, { passive: true });\n\n // Hide tooltips on route change\n this._tooltipRouteHandler = () => this.hideAllTooltips();\n const app = this.getApp();\n\n\n // Hide tooltips on window blur\n this._tooltipBlurHandler = () => this.hideAllTooltips();\n window.addEventListener('blur', this._tooltipBlurHandler);\n\n // Hide tooltips on escape key\n this._tooltipEscapeHandler = (e) => {\n if (e.key === 'Escape') {\n this.hideAllTooltips();\n }\n };\n document.addEventListener('keydown', this._tooltipEscapeHandler);\n }\n\n /**\n * Remove global tooltip hide listeners\n */\n removeTooltipHideListeners() {\n if (this._tooltipScrollHandler) {\n this.element.removeEventListener('scroll', this._tooltipScrollHandler);\n delete this._tooltipScrollHandler;\n }\n\n if (this._tooltipBlurHandler) {\n window.removeEventListener('blur', this._tooltipBlurHandler);\n delete this._tooltipBlurHandler;\n }\n\n if (this._tooltipEscapeHandler) {\n document.removeEventListener('keydown', this._tooltipEscapeHandler);\n delete this._tooltipEscapeHandler;\n }\n }\n\n /**\n * Force hide all visible tooltips\n */\n hideAllTooltips() {\n const navLinks = this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle=\"tooltip\"]');\n navLinks.forEach((link) => {\n const tooltip = link._tooltipInstance || window.bootstrap?.Tooltip?.getInstance(link);\n if (tooltip) {\n tooltip.hide();\n }\n });\n\n // Also hide any orphaned tooltips\n const visibleTooltips = document.querySelectorAll('.tooltip.show');\n visibleTooltips.forEach(tooltip => {\n tooltip.remove();\n });\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n // Clean up tooltips\n this.destroyTooltips();\n\n // Call parent cleanup\n await super.onBeforeDestroy();\n }\n\n /**\n * Setup responsive behavior for mobile\n */\n setupResponsiveBehavior() {\n const checkMobile = () => {\n const isMobile = window.innerWidth <= 768;\n const portalContainer = document.querySelector('.portal-container');\n\n if (portalContainer) {\n if (isMobile) {\n portalContainer.classList.add('sidebar-mobile');\n } else {\n portalContainer.classList.remove('sidebar-mobile', 'sidebar-open');\n }\n }\n };\n\n // Check on load and resize\n checkMobile();\n window.addEventListener('resize', checkMobile);\n }\n\n /**\n * Static method to create a sidebar with common configuration\n */\n static createDefault(options = {}) {\n return new Sidebar({\n theme: 'sidebar-clean',\n showToggle: true,\n autoCollapseMobile: true,\n ...options\n });\n }\n\n /**\n * Static method to create a minimal sidebar\n */\n static createMinimal(options = {}) {\n return new Sidebar({\n theme: 'sidebar-clean',\n showToggle: false,\n autoCollapseMobile: false,\n ...options\n });\n }\n\n /**\n * Set sidebar theme\n */\n setSidebarTheme(theme) {\n // Remove existing theme classes\n this.removeClass('sidebar-light sidebar-dark sidebar-clean');\n\n // Add new theme\n this.sidebarTheme = theme;\n this.addClass(theme);\n\n return this;\n }\n\n /**\n * Quick method to show/hide the sidebar\n */\n show() {\n return this.setSidebarState('normal');\n }\n\n hide() {\n return this.setSidebarState('hidden');\n }\n\n collapse() {\n return this.setSidebarState('collapsed');\n }\n\n expand() {\n return this.setSidebarState('normal');\n }\n\n /**\n * Add pulse effect to toggle button\n */\n pulseToggle() {\n const toggleButton = this.element.querySelector('.sidebar-toggle');\n if (toggleButton) {\n toggleButton.classList.add('pulse');\n\n // Remove pulse after 3 seconds or first click\n const removePulse = () => {\n toggleButton.classList.remove('pulse');\n toggleButton.removeEventListener('click', removePulse);\n };\n\n toggleButton.addEventListener('click', removePulse, { once: true });\n setTimeout(removePulse, 3000);\n }\n return this;\n }\n\n /**\n * Utility method to quickly add a simple menu item\n */\n addSimpleMenuItem(menuName, text, route, icon = 'bi-circle') {\n const menu = this.menus.get(menuName);\n if (menu) {\n menu.items = menu.items || [];\n menu.items.push({\n text: text,\n route: route,\n icon: icon\n });\n\n if (this.activeMenuName === menuName) {\n this.render();\n }\n }\n return this;\n }\n\n /**\n * Utility method to quickly create and set a simple menu\n */\n setSimpleMenu(name, header, items) {\n const menu = {\n name: name,\n header: header,\n items: items\n };\n\n this.addMenu(name, menu);\n this.setActiveMenu(name);\n\n return this;\n }\n\n}\n\nexport default Sidebar;\n","/**\n * PageHeader - Displays page title, description, and actions above page content\n * Used by PortalApp to show consistent page headers\n */\n\nimport View from '@core/View.js';\n\nclass PageHeader extends View {\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'page-header',\n ...options\n });\n\n // Configuration\n this.style = options.style || 'default'; // 'default' | 'minimal' | 'breadcrumb'\n this.size = options.size || 'md'; // 'sm' | 'md' | 'lg' | 'xl'\n this.showIcon = options.showIcon !== false;\n this.showDescription = options.showDescription !== false;\n this.showBreadcrumbs = options.showBreadcrumbs || false;\n \n // Current page reference\n this.currentPage = null;\n }\n\n async getTemplate() {\n if (this.style === 'minimal') {\n return this.getMinimalTemplate();\n } else if (this.style === 'breadcrumb') {\n return this.getBreadcrumbTemplate();\n }\n return this.getDefaultTemplate();\n }\n\n getDefaultTemplate() {\n return `\n {{#data.hasPage}}\n <div class=\"page-header-content page-header-{{data.size}}\">\n <div class=\"page-header-main\">\n <div class=\"page-header-info\">\n {{#data.showIcon}}\n {{#data.pageIcon}}\n <div class=\"page-icon\">\n <i class=\"{{data.pageIcon}}\"></i>\n </div>\n {{/data.pageIcon}}\n {{/data.showIcon}}\n \n <div class=\"page-title-group\">\n <h1 class=\"page-title\">{{data.pageTitle}}</h1>\n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class=\"page-description text-muted\">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n </div>\n\n {{#data.hasActions}}\n <div class=\"page-actions\">\n {{#data.actions}}\n <button class=\"btn {{buttonClass}}\" \n data-action=\"{{action}}\"\n type=\"button\">\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n </div>\n {{/data.hasPage}}\n `;\n }\n\n getMinimalTemplate() {\n return `\n {{#data.hasPage}}\n <div class=\"page-header-content page-header-minimal\">\n <h1 class=\"page-title\">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class=\"{{data.pageIcon}} me-2\"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n </div>\n {{/data.hasPage}}\n `;\n }\n\n getBreadcrumbTemplate() {\n return `\n {{#data.hasPage}}\n <div class=\"page-header-content page-header-breadcrumb\">\n {{#data.showBreadcrumbs}}\n <nav aria-label=\"breadcrumb\">\n <ol class=\"breadcrumb mb-2\">\n {{#data.breadcrumbs}}\n <li class=\"breadcrumb-item {{#active}}active{{/active}}\">\n {{#href}}<a href=\"{{href}}\">{{label}}</a>{{/href}}\n {{^href}}{{label}}{{/href}}\n </li>\n {{/data.breadcrumbs}}\n </ol>\n </nav>\n {{/data.showBreadcrumbs}}\n \n <div class=\"d-flex justify-content-between align-items-start\">\n <h1 class=\"page-title\">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class=\"{{data.pageIcon}} me-2\"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n \n {{#data.hasActions}}\n <div class=\"page-actions\">\n {{#data.actions}}\n <button class=\"btn {{buttonClass}}\" \n data-action=\"{{action}}\"\n type=\"button\">\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n \n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class=\"page-description text-muted mt-2\">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n {{/data.hasPage}}\n `;\n }\n\n async onBeforeRender() {\n await super.onBeforeRender();\n\n const page = this.currentPage;\n const hasPage = !!page;\n\n // Debug logging\n if (page) {\n console.log('PageHeader page:', {\n title: page.title,\n displayName: page.displayName,\n name: page.name,\n pageName: page.pageName,\n icon: page.icon,\n pageIcon: page.pageIcon,\n pageDescription: page.pageDescription,\n description: page.description\n });\n }\n\n // Get headerActions from page options, instance, or constructor.prototype\n const headerActions = page?.options?.headerActions ||\n page?.headerActions || \n page?.constructor?.prototype?.headerActions || \n [];\n\n this.data = {\n hasPage,\n pageTitle: page?.title || page?.displayName || page?.name || page?.pageName || '',\n pageIcon: page?.icon || page?.pageIcon || '',\n pageDescription: page?.pageDescription || page?.description || '',\n showIcon: this.showIcon,\n showDescription: this.showDescription,\n showBreadcrumbs: this.showBreadcrumbs,\n breadcrumbs: page?.options?.breadcrumbs || page?.breadcrumbs || [],\n actions: headerActions,\n hasActions: headerActions.length > 0,\n size: this.size\n };\n \n console.log('PageHeader data:', this.data);\n }\n\n /**\n * Set the current page to display\n */\n async setPage(page) {\n console.log('PageHeader.setPage called with:', page?.pageName || page?.name || 'no page');\n this.currentPage = page;\n // Always render if we have a page, even if not yet mounted\n // This handles the case where setPage is called during initial app setup\n if (page) {\n console.log('PageHeader.setPage calling render()');\n await this.render();\n console.log('PageHeader.setPage render() complete');\n }\n }\n\n /**\n * Get the current page\n */\n getPage() {\n return this.currentPage;\n }\n\n /**\n * Handle action clicks from page header buttons\n */\n async onActionDefault(action, event, element) {\n // Emit to page if it has a handler\n if (this.currentPage && typeof this.currentPage.onHeaderAction === 'function') {\n await this.currentPage.onHeaderAction(action, event, element);\n return true;\n }\n\n // Emit event for parent to handle\n this.emit('action', {\n action,\n event,\n element,\n page: this.currentPage\n });\n\n return false;\n }\n}\n\nexport default PageHeader;\n","/**\n * DeniedPage - Access Denied page for MOJO Framework\n * Displays when a user attempts to access a page without proper permissions\n */\n\nimport Page from '@core/Page.js';\n\nclass DeniedPage extends Page {\n constructor(options = {}) {\n super({\n pageName: 'Access Denied',\n route: '/denied',\n title: 'Access Denied',\n pageIcon: 'bi bi-shield-x',\n template: `\n <div class=\"container mt-5\">\n <div class=\"row justify-content-center\">\n <div class=\"col-md-8 col-lg-6\">\n <div class=\"text-center mb-4\">\n <i class=\"bi bi-shield-x text-muted\" style=\"font-size: 3rem;\"></i>\n <h2 class=\"mt-3 mb-2\">Access Denied</h2>\n <p class=\"text-muted\">You don't have permission to access this page.</p>\n </div>\n\n {{#deniedPage}}\n <div class=\"card border-0 shadow-sm mb-4\">\n <div class=\"card-body\">\n <h6 class=\"card-subtitle mb-2 text-muted\">Requested Page</h6>\n <h5 class=\"card-title\">\n <i class=\"{{pageIcon}} me-2\"></i>\n {{displayName}}\n </h5>\n {{#route}}\n <p class=\"card-text text-muted small\">{{route}}</p>\n {{/route}}\n {{#description}}\n <p class=\"card-text\">{{description}}</p>\n {{/description}}\n\n {{#requiredPermissions}}\n <div class=\"mt-3\">\n <h6 class=\"mb-2\">Required Permissions:</h6>\n {{#permissions}}\n <span class=\"badge bg-light text-dark me-1 mb-1\">{{.}}</span>\n {{/permissions}}\n {{^permissions}}\n <span class=\"text-muted small\">Authentication required</span>\n {{/permissions}}\n </div>\n {{/requiredPermissions}}\n </div>\n </div>\n {{/deniedPage}}\n\n <div class=\"d-grid gap-2 d-md-flex justify-content-md-center\">\n <button type=\"button\" class=\"btn btn-primary\" data-action=\"go-back\">\n <i class=\"bi bi-arrow-left me-1\"></i>\n Go Back\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" data-action=\"go-home\">\n <i class=\"bi bi-house me-1\"></i>\n Home\n </button>\n {{#showLogin}}\n <button type=\"button\" class=\"btn btn-outline-primary\" data-action=\"login\">\n <i class=\"bi bi-box-arrow-in-right me-1\"></i>\n Login\n </button>\n {{/showLogin}}\n </div>\n\n {{#currentUser}}\n <div class=\"text-center mt-4\">\n <small class=\"text-muted\">\n Logged in as <strong>{{username}}</strong>\n </small>\n </div>\n {{/currentUser}}\n </div>\n </div>\n </div>\n `,\n ...options\n });\n\n // Store the denied page instance\n this.deniedPage = null;\n this.deniedPageOptions = null;\n }\n\n /**\n * Handle route parameters - expect denied page info\n */\n async onParams(params = {}, query = {}) {\n await super.onParams(params, query);\n\n // If page info is passed in params\n if (params.page) {\n this.deniedPage = params.page;\n this.deniedPageOptions = params.page.options || params.page.pageOptions || {};\n } else if (query.page) {\n // Handle page name from query string\n this.deniedPageName = query.page;\n }\n }\n\n /**\n * Set the denied page instance\n */\n setDeniedPage(pageInstance) {\n this.deniedPage = pageInstance;\n this.deniedPageOptions = pageInstance?.options || pageInstance?.pageOptions || {};\n return this;\n }\n\n /**\n * Get view data for template rendering\n */\n async getViewData() {\n const app = this.getApp();\n\n // Get current user info\n const currentUser = app?.activeUser || app?.getCurrentUser?.() || null;\n\n // Process denied page info\n let deniedPageInfo = null;\n if (this.deniedPage) {\n const permissions = this.deniedPageOptions?.permissions ||\n this.deniedPage.options?.permissions ||\n this.deniedPage.pageOptions?.permissions;\n\n deniedPageInfo = {\n displayName: this.deniedPage.displayName || this.deniedPage.pageName || this.deniedPage.title || 'Unknown Page',\n pageName: this.deniedPage.pageName,\n route: this.deniedPage.route,\n description: this.deniedPage.pageDescription || this.deniedPage.description,\n pageIcon: this.deniedPage.pageIcon || 'bi bi-file-text',\n requiredPermissions: permissions ? {\n permissions: Array.isArray(permissions) ? permissions : [permissions]\n } : null\n };\n } else if (this.deniedPageName) {\n deniedPageInfo = {\n displayName: this.deniedPageName,\n pageName: this.deniedPageName,\n pageIcon: 'bi bi-file-text'\n };\n }\n\n return {\n deniedPage: deniedPageInfo,\n currentUser: currentUser ? {\n username: currentUser.username || currentUser.name || currentUser.email || 'Unknown User',\n name: currentUser.name,\n email: currentUser.email\n } : null,\n showLogin: !currentUser // Show login button if not authenticated\n };\n }\n\n /**\n * Handle going back to previous page\n */\n async handleActionGoBack(event, element) {\n event.preventDefault();\n\n // Try to go back in browser history\n if (window.history.length > 1) {\n window.history.back();\n } else {\n // Fallback to home page\n await this.handleActionGoHome(event, element);\n }\n }\n\n /**\n * Handle navigation to home page\n */\n async handleActionGoHome(event, element) {\n event.preventDefault();\n\n const app = this.getApp();\n if (app) {\n await app.navigateToDefault();\n } else {\n // Fallback navigation\n window.location.href = '/';\n }\n }\n\n /**\n * Handle login action\n */\n async handleActionLogin(event, element) {\n event.preventDefault();\n\n const app = this.getApp();\n\n // Try to navigate to login page\n if (app) {\n try {\n await app.showPage('login');\n } catch (error) {\n // If login page doesn't exist, try auth route\n try {\n await app.navigate('/login');\n } catch (navError) {\n // Fallback - emit login required event\n this.emit('login-required', {\n returnUrl: this.deniedPage?.route || window.location.pathname\n });\n\n // Show message if no handlers\n setTimeout(() => {\n app?.showInfo?.('Please contact your administrator for access.');\n }, 100);\n }\n }\n }\n }\n\n /**\n * Called when entering this page\n */\n async onEnter() {\n await super.onEnter();\n\n // Set appropriate page title\n const pageName = this.deniedPage?.pageName || this.deniedPageName;\n if (pageName) {\n this.setMeta({\n title: `Access Denied - ${pageName}`\n });\n }\n\n // Log access denial for security monitoring\n console.warn('Access denied to page:', {\n page: this.deniedPage?.pageName || this.deniedPageName,\n route: this.deniedPage?.route,\n permissions: this.deniedPageOptions?.permissions,\n timestamp: new Date().toISOString()\n });\n }\n\n /**\n * Static helper to show access denied for a specific page\n */\n static showForPage(app, pageInstance) {\n const deniedPage = new DeniedPage();\n deniedPage.setDeniedPage(pageInstance);\n return app.showPage(deniedPage);\n }\n}\n\nexport default DeniedPage;\n","/**\n * NotFoundPage - 404 Not Found page for MOJO Framework\n * Displays when a user attempts to access a non-existent page or route\n */\n\nimport Page from '@core/Page.js';\n\nclass NotFoundPage extends Page {\n constructor(options = {}) {\n super({\n pageName: '404',\n route: '/404',\n title: '404 - Page Not Found',\n pageIcon: 'bi bi-search',\n template: `\n <div class=\"container mt-5\">\n <div class=\"row justify-content-center\">\n <div class=\"col-md-8 col-lg-6\">\n <div class=\"text-center mb-4\">\n <i class=\"bi bi-search text-muted\" style=\"font-size: 3rem;\"></i>\n <h2 class=\"mt-3 mb-2\">Page Not Found</h2>\n <p class=\"text-muted\">The page you're looking for doesn't exist.</p>\n </div>\n\n {{#path}}\n <div class=\"card border-0 shadow-sm mb-4\">\n <div class=\"card-body text-center\">\n <h6 class=\"card-subtitle mb-2 text-muted\">Requested Path</h6>\n <code class=\"text-primary\">{{path}}</code>\n </div>\n </div>\n {{/path}}\n\n <div class=\"d-grid gap-2 d-md-flex justify-content-md-center\">\n <button type=\"button\" class=\"btn btn-primary\" data-action=\"go-back\">\n <i class=\"bi bi-arrow-left me-1\"></i>\n Go Back\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" data-action=\"go-home\">\n <i class=\"bi bi-house me-1\"></i>\n Home\n </button>\n </div>\n </div>\n </div>\n </div>\n `,\n ...options\n });\n\n // Store the not found path\n this.path = null;\n }\n\n /**\n * Handle route parameters\n */\n async onParams(params = {}, query = {}) {\n await super.onParams(params, query);\n\n // Store path from params or query\n if (params.path) {\n this.path = params.path;\n }\n if (query.path) {\n this.path = query.path;\n }\n }\n\n /**\n * Set not found path\n */\n setInfo(path) {\n this.path = path || null;\n return this;\n }\n\n /**\n * Handle going back to previous page\n */\n async handleActionGoBack(event, _element) {\n event.preventDefault();\n\n // Try to go back in browser history\n if (window.history.length > 1) {\n window.history.back();\n } else {\n // Fallback to home page\n await this.handleActionGoHome(event, _element);\n }\n }\n\n /**\n * Handle navigation to home page\n */\n async handleActionGoHome(event, _element) {\n event.preventDefault();\n\n const app = this.getApp();\n if (app) {\n await app.navigateToDefault();\n } else {\n // Fallback navigation\n window.location.href = '/';\n }\n }\n\n /**\n * Called when entering this page\n */\n async onEnter() {\n await super.onEnter();\n\n // Set appropriate page title\n if (this.path) {\n this.setMeta({\n title: `404 - ${this.path} Not Found`\n });\n }\n\n // Log 404 for analytics\n console.warn('404 Not Found:', {\n path: this.path,\n timestamp: new Date().toISOString()\n });\n }\n\n /**\n * Static helper to show 404 for a specific path\n */\n static showForPath(app, path) {\n const notFoundPage = new NotFoundPage();\n notFoundPage.setInfo(path);\n return notFoundPage.render(); // Just render, don't navigate\n }\n}\n\nexport default NotFoundPage;\n","/**\n * PortalApp - Complete portal application extending WebApp\n * Provides built-in navigation, sidebar, and content management\n * Clean, simple implementation that reuses WebApp and View logic\n */\n\nimport WebApp from '@core/WebApp.js';\nimport TopNav from '@core/views/navigation/TopNav.js';\nimport Sidebar from '@core/views/navigation/Sidebar.js';\nimport PageHeader from '@core/views/navigation/PageHeader.js';\nimport DeniedPage from '@core/pages/DeniedPage.js';\nimport TokenManager from '@core/services/TokenManager.js';\nimport {User} from '@core/models/User.js';\nimport {Group } from '@core/models/Group.js';\nimport {Member} from '@core/models/Member.js';\nimport NotFoundPage from '@core/pages/NotFoundPage.js';\nimport ToastService from '@core/services/ToastService.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\n\nexport default class PortalApp extends WebApp {\n constructor(config = {}) {\n // Pass core WebApp config through\n super(config);\n\n this.sidebarConfig = config.sidebar;\n // Portal-specific configuration (clean flat structure)\n // if (config.sidebar && config.sidebar.menus) {\n // this.sidebarConfig.menus = config.sidebar.menus;\n // this.sidebarConfig.groupSelectorMode = config.sidebar.groupSelectorMode || \"inline\";\n // if (config.sidebar.groupHeader) {\n // this.sidebarConfig.groupHeader = config.sidebar.groupHeader;\n // }\n // } else if (config.sidebar.menu) {\n // this.sidebarConfig.menu = config.sidebar.menu;\n // } else if (config.sidebar.items) {\n // this.sidebarConfig.menu = config.sidebar;\n // }\n\n this.topbarConfig = config.topbar || {};\n\n // Legacy support - topnav -> topbar\n if (config.topnav && !config.topbar) {\n this.topbarConfig = config.topnav;\n }\n\n // Page header configuration\n this.showPageHeader = config.showPageHeader || false;\n this.pageHeaderConfig = config.pageHeader || {};\n\n // Portal components\n this.sidebar = null;\n this.topbar = null;\n this.topnav = null; // Legacy reference\n this.pageHeader = null;\n this.tokenManager = new TokenManager();\n\n // Active group management\n this.activeGroup = null;\n // Portal state - Load from localStorage first, then fallback to config\n if (!this.isMobile()) {\n this.sidebarCollapsed = this.loadSidebarState() ??\n (this.sidebarConfig.defaultCollapsed || false);\n } else {\n this.sidebarCollapsed = this.sidebarConfig.defaultCollapsed || false;\n }\n this.setupPageContainer();\n\n this.toast = new ToastService();\n this.Dialog = Dialog;\n\n this.registerPage(\"denied\", DeniedPage);\n this.registerPage(\"404\", NotFoundPage);\n }\n\n /**\n * Override WebApp start to setup portal layout\n */\n async start() {\n // Call parent start (handles router, error handling, etc.)\n // Setup router\n await this.checkAuthStatus();\n\n this.events.on('auth:unauthorized', () => {\n this.tokenManager.clearTokens();\n this.rest.clearAuth();\n this.setActiveUser(null);\n return;\n });\n\n this.events.on('auth:logout', () => {\n this.tokenManager.clearTokens();\n this.rest.clearAuth();\n this.setActiveUser(null);\n return;\n });\n\n this.events.on(\"browser:focus\", () => {\n if (!this.activeUser) return;\n this.tokenManager.checkAndRefreshTokens(this);\n });\n\n this.events.on('portal:action', this.onPortalAction.bind(this));\n\n if (this.activeUser) {\n // Check and load active group after auth\n await this.checkActiveGroup();\n }\n\n await this.setupRouter();\n\n // Mark as started\n this.isStarted = true;\n\n // Emit app ready event\n this.events.emit('app:ready', { app: this });\n\n // Prompt passkey setup after login if not dismissed\n if (this.activeUser && !this.activeUser.get(\"has_passkey\")) {\n this.showPasskeySetup();\n }\n\n\n }\n\n async checkAuthStatus() {\n const tokenStatus = this.tokenManager.checkTokenStatus();\n\n // Handle logout scenarios\n if (tokenStatus.action === 'logout') {\n this.events.emit('auth:unauthorized', { app: this });\n return false;\n }\n\n // Handle refresh scenarios - attempt refresh if needed\n if (tokenStatus.action === 'refresh') {\n const refreshed = await this.tokenManager.checkAndRefreshTokens(this);\n if (!refreshed) {\n // If refresh failed, checkAndRefreshTokens already handled logout\n return false;\n }\n }\n\n // At this point we have a valid token\n const token = this.tokenManager.getTokenInstance();\n\n // If user already loaded, just start auto-refresh and return\n if (this.activeUser) {\n this.tokenManager.startAutoRefresh(this);\n return true;\n }\n\n // Load user data\n this.rest.setAuthToken(token.token);\n const user = new User({ id: token.getUserId() });\n const resp = await user.fetch();\n if (!resp.success) {\n this.tokenManager.clearTokens();\n this.events.emit('auth:unauthorized', { app: this, error: resp.error });\n return false;\n }\n\n this.setActiveUser(user);\n this.tokenManager.startAutoRefresh(this);\n return true;\n }\n\n /**\n * Check and load active group from storage\n */\n async checkActiveGroup() {\n // First check URL search params for group parameter\n const urlParams = new URLSearchParams(window.location.search);\n const urlGroupId = urlParams.get('group');\n\n // Determine which group ID to use: URL param takes priority\n const groupId = urlGroupId || this.loadActiveGroupId();\n\n if (groupId) {\n try {\n const group = new Group({ id: groupId });\n const resp = await group.fetch();\n if (!resp.success || !resp.data.status) {\n this.clearActiveGroup();\n console.warn('Failed to load active group:', resp.statusText);\n return;\n }\n\n this.activeGroup = group;\n // If we got the group from URL, save it as the new active group\n if (urlGroupId) {\n this.saveActiveGroupId(groupId);\n }\n\n if (this.activeUser) {\n this.activeUser.member = new Member();\n await this.activeUser.member.fetchForGroup(group.id);\n }\n\n // Emit event that group was loaded\n this.events.emit('group:loaded', { group: this.activeGroup });\n\n\n } catch (error) {\n console.warn('Failed to load active group:', error);\n // If URL group failed, try to clear it and fall back to stored group\n if (urlGroupId && !this.loadActiveGroupId()) {\n // URL group failed and no stored group, clear everything\n this.clearActiveGroupId();\n } else if (urlGroupId) {\n // URL group failed but we have a stored group, try that instead\n const storedGroupId = this.loadActiveGroupId();\n if (storedGroupId && storedGroupId !== urlGroupId) {\n try {\n const fallbackGroup = new Group({ id: storedGroupId });\n await fallbackGroup.fetch();\n this.activeGroup = fallbackGroup;\n this.events.emit('group:loaded', { group: this.activeGroup });\n\n } catch (fallbackError) {\n console.warn('Fallback to stored group also failed:', fallbackError);\n this.clearActiveGroupId();\n }\n }\n }\n }\n }\n }\n\n\n /**\n * Set the active group\n */\n async setActiveGroup(group) {\n const previousGroup = this.activeGroup;\n this.activeGroup = group;\n\n // Save to storage\n if (group && group.get('id')) {\n this.saveActiveGroupId(group.get('id'));\n } else {\n this.clearActiveGroupId();\n }\n\n if (this.activeUser) {\n this.activeUser.member = new Member();\n await this.activeUser.member.fetchForGroup(group.id);\n }\n\n // Emit event\n this.events.emit('group:changed', {\n group,\n previousGroup,\n app: this\n });\n\n const page = this.getCurrentPage();\n if (page) {\n page.onGroupChange(group);\n }\n\n this.router.updateUrl({group:group.id}, { replace: true });\n\n return this;\n }\n\n /**\n * Get the active group\n */\n getActiveGroup() {\n return this.activeGroup;\n }\n\n /**\n * Clear the active group\n */\n async clearActiveGroup() {\n const previousGroup = this.activeGroup;\n this.activeGroup = null;\n this.clearActiveGroupId();\n // Emit event\n this.events.emit('group:cleared', {\n previousGroup,\n app: this\n });\n return this;\n }\n\n /**\n * Save active group ID to localStorage\n */\n saveActiveGroupId(groupId) {\n try {\n const key = this.getActiveGroupStorageKey();\n localStorage.setItem(key, groupId.toString());\n } catch (error) {\n console.warn('Failed to save active group ID:', error);\n }\n }\n\n /**\n * Load active group ID from localStorage\n */\n loadActiveGroupId() {\n try {\n const key = this.getActiveGroupStorageKey();\n return localStorage.getItem(key);\n } catch (error) {\n console.warn('Failed to load active group ID:', error);\n return null;\n }\n }\n\n /**\n * Clear active group ID from localStorage\n */\n clearActiveGroupId() {\n try {\n const key = this.getActiveGroupStorageKey();\n localStorage.removeItem(key);\n } catch (error) {\n console.warn('Failed to clear active group ID:', error);\n }\n }\n\n /**\n * Get storage key for active group ID\n */\n getActiveGroupStorageKey() {\n return `active_group_id`;\n }\n\n /**\n * Set portal profile to localStorage\n */\n setPortalProfile(profile) {\n try {\n localStorage.setItem('portal_profile', profile);\n } catch (error) {\n console.warn('Failed to save portal profile:', error);\n }\n }\n\n /**\n * Check if user needs to select a group\n */\n needsGroupSelection() {\n return !this.activeGroup;\n }\n\n /**\n * Setup layout based on configuration\n */\n setupPageContainer() {\n const container = typeof this.container === 'string'\n ? document.querySelector(this.container)\n : this.container;\n\n if (!container) {\n throw new Error(`Portal container not found: ${this.container}`);\n }\n\n // Create clean portal layout\n const showSidebar = this.sidebarConfig && Object.keys(this.sidebarConfig).length > 0;\n const showTopbar = this.topbarConfig && Object.keys(this.topbarConfig).length > 0;\n\n // If page header is enabled, wrap content in two containers\n const contentMarkup = this.showPageHeader ? `\n <div class=\"portal-content\" id=\"portal-content\">\n <div id=\"page-header\"></div>\n <div id=\"page-container\">\n <!-- Pages render here -->\n </div>\n </div>\n ` : `\n <div class=\"portal-content\" id=\"page-container\">\n <!-- Pages render here -->\n </div>\n `;\n\n container.innerHTML = `\n <div class=\"portal-layout hide-sidebar\">\n ${showSidebar ? '<div id=\"portal-sidebar\"></div>' : ''}\n <div class=\"portal-body\">\n ${showTopbar ? '<div id=\"portal-topnav\"></div>' : ''}\n ${contentMarkup}\n </div>\n </div>\n `;\n\n // Set page container for WebApp\n this.pageContainer = '#page-container';\n\n // Add portal CSS classes and apply saved state\n container.classList.add('portal-container');\n\n // Setup page container\n this.setupPortalComponents();\n\n // Apply the saved sidebar state\n this.applySidebarState(container);\n }\n\n /**\n * Setup portal components\n */\n async setupPortalComponents() {\n await this.setupSidebar();\n await this.setupTopbar();\n await this.setupPageHeader();\n this.setupPortalEvents();\n }\n\n /**\n * Setup sidebar component\n */\n async setupSidebar() {\n if (!this.sidebarConfig || Object.keys(this.sidebarConfig).length === 0) return;\n\n this.sidebar = new Sidebar({\n containerId: 'portal-sidebar',\n ...this.sidebarConfig\n });\n\n await this.sidebar.render();\n }\n\n /**\n * Setup topbar component\n */\n async setupTopbar() {\n if (!this.topbarConfig || Object.keys(this.topbarConfig).length === 0) return;\n\n // Map config to TopNav format\n this.topbar = new TopNav({\n containerId: \"portal-topnav\",\n brandText: this.topbarConfig.brand || this.brand || this.title,\n brandRoute: this.topbarConfig.brandRoute || '/',\n brandIcon: this.topbarConfig.brandIcon || this.brandIcon,\n navItems: this.topbarConfig.leftItems || [],\n rightItems: this.topbarConfig.rightItems || [],\n displayMode: this.topbarConfig.displayMode || 'both',\n showSidebarToggle: this.topbarConfig.showSidebarToggle || false,\n ...this.topbarConfig\n });\n\n await this.topbar.render();\n\n // Legacy support\n this.topnav = this.topbar;\n }\n\n /**\n * Setup page header component\n */\n async setupPageHeader() {\n if (!this.showPageHeader) return;\n\n this.pageHeader = new PageHeader({\n containerId: 'page-header',\n style: this.pageHeaderConfig.style || 'default',\n showIcon: this.pageHeaderConfig.showIcon !== false,\n showDescription: this.pageHeaderConfig.showDescription !== false,\n showBreadcrumbs: this.pageHeaderConfig.showBreadcrumbs || false,\n ...this.pageHeaderConfig\n });\n\n // Render into the portal-content container\n const headerContainer = document.getElementById('page-header');\n if (headerContainer) {\n await this.pageHeader.render(true, headerContainer);\n }\n }\n\n /**\n * Setup portal event handling\n */\n setupPortalEvents() {\n // Handle sidebar toggle via event delegation\n document.addEventListener('click', (event) => {\n if (event.target.closest('[data-action=\"toggle-sidebar\"]')) {\n event.preventDefault();\n this.toggleSidebar();\n }\n });\n\n // Handle responsive changes\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n this.handleResponsive();\n });\n resizeObserver.observe(document.body);\n this._resizeObserver = resizeObserver;\n } else {\n // Fallback for older browsers\n this._resizeHandler = () => this.handleResponsive();\n window.addEventListener('resize', this._resizeHandler);\n }\n\n // Initial responsive setup\n this.handleResponsive();\n }\n\n /**\n * Toggle sidebar state\n */\n toggleSidebar() {\n if (!this.sidebar) return;\n\n const container = document.querySelector('.portal-container');\n const isMobile = this.isMobile();\n\n if (isMobile) {\n container.classList.toggle('hide-sidebar');\n } else {\n container.classList.toggle('collapse-sidebar');\n this.sidebarCollapsed = !this.sidebarCollapsed;\n\n // Save the new state\n this.saveSidebarState(this.sidebarCollapsed);\n }\n\n this.events.emit('sidebar:toggled', {\n collapsed: this.sidebarCollapsed,\n mobile: isMobile\n });\n }\n\n /**\n * Handle responsive layout\n */\n handleResponsive() {\n const container = document.querySelector('.portal-container');\n if (!container) return;\n const isMobile = this.isMobile();\n\n if (isMobile) {\n container.classList.add('mobile-layout');\n if (!container.classList.contains('hide-sidebar')) {\n container.classList.add('hide-sidebar');\n }\n } else {\n container.classList.remove('mobile-layout', 'hide-sidebar');\n }\n\n this.events.emit('responsive:changed', { mobile: isMobile });\n }\n\n getPortalContainer() {\n return document.querySelector('.portal-container');\n }\n\n isMobile() {\n return window.innerWidth < 768;\n }\n\n hasMobileLayout() {\n return this.getPortalContainer().classList.contains('mobile-layout');\n }\n\n /**\n * Override showPage to update navigation\n */\n async showPage(page, query = {}, params = {}, options = {}) {\n const result = await super.showPage(page, query, params, options);\n\n if (this.hasMobileLayout()) {\n this.getPortalContainer().classList.add('hide-sidebar');\n }\n\n if (this.currentPage) {\n this.updateNavigation(this.currentPage);\n }\n\n return result;\n }\n\n /**\n * Update navigation active states\n */\n updateNavigation(page) {\n // Update sidebar active state\n if (this.sidebar && this.sidebar.setActivePage) {\n this.sidebar.setActivePage(page.route);\n }\n\n // Update topbar active state\n if (this.topbar && this.topbar.setActivePage) {\n this.topbar.setActivePage(page.route);\n }\n\n // Update page header\n if (this.pageHeader) {\n this.pageHeader.setPage(page);\n }\n\n this.events.emit('portal:page-changed', { page });\n }\n\n /**\n * Set active user\n */\n setActiveUser(user) {\n this.activeUser = user;\n\n if (this.topbar) {\n this.topbar.setUser(user);\n }\n\n\n this.events.emit('portal:user-changed', { user });\n }\n\n /**\n * Get the active user (for backward compatibility)\n */\n getActiveUser() {\n return this.activeUser;\n }\n\n /**\n * Save sidebar state to localStorage\n */\n saveSidebarState(collapsed) {\n try {\n const key = this.getSidebarStorageKey();\n localStorage.setItem(key, JSON.stringify(collapsed));\n } catch (error) {\n console.warn('Failed to save sidebar state:', error);\n }\n }\n\n /**\n * Load sidebar state from localStorage\n */\n loadSidebarState() {\n try {\n const key = this.getSidebarStorageKey();\n const saved = localStorage.getItem(key);\n return saved !== null ? JSON.parse(saved) : null;\n } catch (error) {\n console.warn('Failed to load sidebar state:', error);\n return null;\n }\n }\n\n /**\n * Get storage key for sidebar state (allows multiple apps on same domain)\n */\n getSidebarStorageKey() {\n // Use app title/name to create unique key\n const appKey = this.title ? this.title.replace(/\\s+/g, '_').toLowerCase() : 'portal_app';\n return `${appKey}_sidebar_collapsed`;\n }\n\n /**\n * Apply saved sidebar state to the UI\n */\n applySidebarState(container = null) {\n if (!container) {\n container = document.querySelector('.portal-container');\n }\n\n if (!container) return;\n\n if (this.sidebarCollapsed) {\n container.classList.add('collapse-sidebar');\n } else {\n container.classList.remove('collapse-sidebar');\n }\n }\n\n /**\n * Clear saved sidebar state\n */\n clearSidebarState() {\n try {\n const key = this.getSidebarStorageKey();\n localStorage.removeItem(key);\n } catch (error) {\n console.warn('Failed to clear sidebar state:', error);\n }\n }\n\n async changePassword() {\n const data = await this.showForm({\n title: \"Change Password\",\n fields: [\n {\n name: 'current_password', type: 'password',\n label: 'Current Password', required: true,\n showToggle: true, // default, can omit\n strengthMeter: true,\n capsLockWarning: true,\n\n },\n {\n name: 'new_password', type: 'password', label: 'New Password', required: true,\n showToggle: true,\n passwordUsage: 'new', // sets autocomplete to 'new-password'\n\n strengthMeter: true,\n capsLockWarning: true,\n attributes: {\n // optional, override autocomplete if needed\n autocomplete: 'new-password'\n }\n },\n {\n name: 'confirm_password', type: 'password', label: 'Confirm Password', required: true,\n showToggle: true,\n passwordUsage: 'new', // sets autocomplete to 'new-password'\n\n strengthMeter: true,\n capsLockWarning: true,\n attributes: {\n // optional, override autocomplete if needed\n // autocomplete: 'new-password'\n }\n }\n ],\n submitLabel: 'Change Password'\n });\n if (data) {\n if (data.new_password === data.confirm_password) {\n // Perform password change logic here\n const resp = await this.activeUser.save(data);\n if (resp.status === 200) {\n this.toast.success('Password changed successfully');\n } else {\n this.toast.error('Failed to change password');\n }\n } else {\n this.toast.error('Passwords do not match');\n }\n }\n }\n\n onPortalAction(action) {\n switch (action.action) {\n case 'logout':\n this.tokenManager.clearTokens();\n this.rest.clearAuth();\n this.setActiveUser(null);\n break;\n case 'profile':\n this.showProfile();\n break;\n case 'change-password':\n this.changePassword();\n break;\n default:\n console.warn(`Unknown portal action: ${action}`);\n }\n }\n\n async showProfile() {\n if (!this.activeUser) {\n this.showError(\"No user is currently logged in\");\n return;\n }\n\n try {\n const { UserProfileView } = await import('@core/views/user/index.js');\n const profileView = new UserProfileView({ model: this.activeUser });\n\n await Dialog.showDialog({\n body: profileView,\n header: null,\n size: 'lg'\n });\n } catch (error) {\n console.error('Error showing profile:', error);\n this.showError('Failed to load profile');\n }\n }\n\n async showPasskeySetup() {\n if (localStorage.getItem('passkey_setup_dismissed')) return;\n\n try {\n const { PasskeySetupView } = await import('@core/views/user/index.js');\n const setupView = new PasskeySetupView();\n\n setupView.on('dismiss', () => {\n // Close the dialog by finding and clicking dismiss\n const dialog = setupView.element?.closest('.modal');\n if (dialog) {\n const bsModal = bootstrap.Modal.getInstance(dialog);\n if (bsModal) bsModal.hide();\n }\n });\n\n await Dialog.showDialog({\n header: null,\n body: setupView,\n size: 'sm',\n centered: true,\n buttons: []\n });\n } catch (error) {\n console.error('Error showing passkey setup:', error);\n }\n }\n\n /**\n * Clean up portal resources\n */\n async destroy() {\n // Clean up event listeners\n // Clear active group\n this.activeGroup = null;\n\n // Clean up portal resources\n if (this._resizeObserver) {\n this._resizeObserver.disconnect();\n }\n if (this._resizeHandler) {\n window.removeEventListener('resize', this._resizeHandler);\n }\n\n // Destroy components using View lifecycle\n if (this.topbar) {\n await this.topbar.destroy();\n this.topbar = null;\n this.topnav = null;\n }\n\n if (this.sidebar) {\n await this.sidebar.destroy();\n this.sidebar = null;\n }\n\n // Call parent destroy\n await super.destroy();\n }\n\n /**\n * Static factory method\n */\n static create(config = {}) {\n return new PortalApp(config);\n }\n}\n","import FormView from './FormView.js';\nimport Page from '@core/Page.js';\n\nexport default class FormPage extends Page {\n constructor(options = {}) {\n super({\n title: 'Form Page',\n description: 'A page for submitting forms',\n icon: 'form',\n fields: [],\n template: '<div data-container=\"form-view-container\"></div>',\n className: \"form-page container-sm\",\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n await this.recreateFormView();\n }\n\n async onEnter() {\n await super.onEnter();\n if (this.formView) {\n // Recreate formView to ensure clean slate with new model\n await this.recreateFormView();\n }\n }\n\n async onGroupChange(group) {\n if (this.formView) {\n // Recreate formView to ensure clean slate with new model\n await this.recreateFormView();\n }\n }\n\n async getModel() {\n if (this.model) {\n return this.model;\n } else if (this.getApp().activeGroup) {\n return this.getApp().activeGroup;\n }\n return null;\n }\n\n async recreateFormView() {\n // Destroy old formView\n if (this.formView) {\n await this.formView.destroy();\n this.removeChild(this.formView);\n }\n\n // Create new formView with current model\n this.formView = new FormView({\n containerId: 'form-view-container',\n fields: this.options.fields,\n autosaveModelField: true\n });\n this.addChild(this.formView);\n\n const model = await this.getModel();\n if (model) {\n this.formView.setModel(model);\n }\n }\n}\n","/**\n * MustacheFormatter - Mustache wrapper for MOJO Framework\n * \n * This is now a thin wrapper around Mustache.js since pipe formatting\n * is handled directly in Model.get() and View.get() via MOJOUtils.\n * \n * The wrapper is maintained for backward compatibility and to provide\n * a consistent API for template rendering throughout the framework.\n */\n\nimport mustache from './mustache.js';\nimport dataFormatter from './DataFormatter.js';\n\nclass MustacheFormatter {\n constructor() {\n this.formatter = dataFormatter;\n this.compiledTemplates = new Map();\n }\n\n /**\n * Render template with data\n * Pipes are now handled by Model.get() and View.get() automatically\n * \n * @param {string} template - Mustache template\n * @param {object} data - Data to render (View, Model, or plain object)\n * @param {object} partials - Mustache partials\n * @returns {string} Rendered template\n */\n render(template, data, partials = {}) {\n // Simply pass through to Mustache\n // If data has a get() method, Mustache will use it automatically\n return mustache.render(template, data, partials);\n }\n\n /**\n * Compile template for reuse\n * @param {string} template - Template to compile\n * @returns {object} Compiled template tokens\n */\n compile(template) {\n const compiled = mustache.parse(template);\n this.compiledTemplates.set(template, compiled);\n return compiled;\n }\n\n /**\n * Render with compiled template\n * @param {object} compiled - Compiled template tokens\n * @param {object} data - Data to render\n * @param {object} partials - Mustache partials\n * @returns {string} Rendered template\n */\n renderCompiled(compiled, data, partials = {}) {\n return mustache.render(compiled, data, partials);\n }\n\n /**\n * Clear compiled template cache\n */\n clearCache() {\n this.compiledTemplates.clear();\n mustache.clearCache();\n }\n\n /**\n * Process and cache a template\n * @param {string} key - Cache key\n * @param {string} template - Template to cache\n * @returns {object} Cached template info\n */\n cache(key, template) {\n const compiled = this.compile(template);\n return { key, template, compiled };\n }\n\n /**\n * Get cached template\n * @param {string} key - Cache key\n * @returns {object|null} Cached template info or null\n */\n getCached(key) {\n for (const [template, compiled] of this.compiledTemplates) {\n if (template === key || compiled === key) {\n return { key, template, compiled };\n }\n }\n return null;\n }\n\n /**\n * Register a custom formatter with DataFormatter\n * @param {string} name - Formatter name\n * @param {function} formatter - Formatter function\n * @returns {MustacheFormatter} This instance for chaining\n */\n registerFormatter(name, formatter) {\n this.formatter.register(name, formatter);\n return this;\n }\n\n /**\n * Check if a string contains pipe syntax\n * @param {string} template - Template string to check\n * @returns {boolean} True if contains pipes\n */\n hasPipes(template) {\n return /\\{\\{[{]?[^}|]+\\|[^}]+\\}[}]?\\}/.test(template);\n }\n\n /**\n * Pre-process data with pipe formatters\n * This is now handled automatically by get() methods, but kept for backward compatibility\n * \n * @param {object} data - Data object\n * @param {object} pipes - Object mapping keys to pipe strings\n * @returns {object} Processed data\n */\n processData(data, pipes) {\n const processed = { ...data };\n \n for (const [key, pipeString] of Object.entries(pipes)) {\n // If data has a get method, use it (which will handle pipes)\n if (data && typeof data.get === 'function') {\n processed[key] = data.get(`${key}|${pipeString}`);\n } else {\n // For plain objects, apply formatter directly\n const value = this.getValueFromPath(data, key);\n processed[key] = this.formatter.pipe(value, pipeString);\n }\n }\n \n return processed;\n }\n\n /**\n * Get value from object using dot notation path\n * Kept for backward compatibility, but MOJOUtils.getContextData is preferred\n * \n * @param {object} obj - Source object\n * @param {string} path - Dot notation path\n * @returns {*} Value at path\n */\n getValueFromPath(obj, path) {\n if (!obj || !path) return undefined;\n \n // If obj has a get method, use it\n if (obj && typeof obj.get === 'function') {\n return obj.get(path);\n }\n \n // Otherwise use standard property navigation\n const keys = path.split('.');\n let current = obj;\n \n for (const key of keys) {\n if (current === null || current === undefined) {\n return undefined;\n }\n \n // Handle array index\n if (!isNaN(key) && Array.isArray(current)) {\n current = current[parseInt(key)];\n } else {\n current = current[key];\n }\n }\n \n return current;\n }\n\n /**\n * Process template to handle pipe formatters\n * @deprecated Pipes are now handled by get() methods automatically\n * @param {string} template - Original template\n * @param {object} data - Original data\n * @returns {object} {template: processedTemplate, data: processedData}\n */\n processTemplate(template, data) {\n // For backward compatibility, just return as-is\n // Pipes will be handled by get() methods during Mustache rendering\n return { template, data };\n }\n}\n\n// Create singleton instance\nconst mustacheFormatter = new MustacheFormatter();\n\n// Export both class and instance\nexport { MustacheFormatter };\nexport default mustacheFormatter;","/**\n * ProfileOverviewSection - Profile overview tab\n *\n * Shows avatar, contact, personal info, account status, and permissions peek.\n * Editable: display name, timezone, avatar.\n * Verification actions for email/phone.\n *\n * Full user graph fields used:\n * id, username, email, phone_number, display_name, first_name, last_name,\n * avatar, org (int), permissions, is_active, is_superuser, is_staff,\n * is_email_verified, is_phone_verified, requires_mfa, timezone,\n * last_login, last_activity, date_joined\n */\nimport View from '@core/View.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport rest from '@core/Rest.js';\nimport { User } from '@core/models/User.js';\n\nexport default class ProfileOverviewSection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-overview-section',\n template: `\n <style>\n .po-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .po-section-label:first-child { margin-top: 0; }\n .po-field-row { display: flex; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #f0f0f0; }\n .po-field-row:last-child { border-bottom: none; }\n .po-field-label { width: 130px; font-size: 0.8rem; color: #6c757d; flex-shrink: 0; }\n .po-field-value { flex: 1; font-size: 0.88rem; color: #212529; display: flex; align-items: center; gap: 0.4rem; }\n .po-field-action { color: #6c757d; cursor: pointer; font-size: 0.8rem; margin-left: auto; padding: 0.15rem 0.4rem; border-radius: 4px; background: none; border: none; }\n .po-field-action:hover { background: #f0f0f0; color: #0d6efd; }\n .po-badge-warn { font-size: 0.65rem; padding: 0.15em 0.45em; background: #fff3cd; color: #856404; border-radius: 3px; }\n .po-badge-ok { font-size: 0.65rem; padding: 0.15em 0.45em; background: #d1e7dd; color: #0f5132; border-radius: 3px; }\n .po-badge-muted { font-size: 0.65rem; padding: 0.15em 0.45em; background: #f0f0f0; color: #6c757d; border-radius: 3px; }\n .po-not-set { color: #adb5bd; font-style: italic; font-size: 0.85rem; }\n .po-perm-pill { display: inline-block; font-size: 0.72rem; padding: 0.2em 0.55em; background: #e7f1ff; color: #0d6efd; border-radius: 3px; margin: 0.1rem; }\n .po-perm-more { font-size: 0.72rem; color: #6c757d; cursor: pointer; }\n .po-perm-more:hover { color: #0d6efd; text-decoration: underline; }\n </style>\n\n <!-- Contact -->\n <div class=\"po-section-label\">Contact</div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Email</div>\n <div class=\"po-field-value\">\n {{model.email}}\n {{#model.is_email_verified|bool}}\n <span class=\"po-badge-ok\">Verified</span>\n {{/model.is_email_verified|bool}}\n {{^model.is_email_verified|bool}}\n <span class=\"po-badge-warn\">Unverified</span>\n {{/model.is_email_verified|bool}}\n </div>\n {{^model.is_email_verified|bool}}\n <button type=\"button\" class=\"po-field-action\" data-action=\"verify-email\" title=\"Send verification email\"><i class=\"bi bi-envelope-check\"></i></button>\n {{/model.is_email_verified|bool}}\n </div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Phone</div>\n <div class=\"po-field-value\">\n {{#hasPhone|bool}}\n {{model.phone_number}}\n {{#model.is_phone_verified|bool}}\n <span class=\"po-badge-ok\">Verified</span>\n {{/model.is_phone_verified|bool}}\n {{^model.is_phone_verified|bool}}\n <span class=\"po-badge-warn\">Unverified</span>\n {{/model.is_phone_verified|bool}}\n {{/hasPhone|bool}}\n {{^hasPhone|bool}}\n <span class=\"po-not-set\">Not set</span>\n {{/hasPhone|bool}}\n </div>\n {{^hasPhone|bool}}\n <button type=\"button\" class=\"po-field-action\" data-action=\"add-phone\" title=\"Add phone number\"><i class=\"bi bi-plus\"></i></button>\n {{/hasPhone|bool}}\n {{#hasPhone|bool}}\n {{^model.is_phone_verified|bool}}\n <button type=\"button\" class=\"po-field-action\" data-action=\"verify-phone\" title=\"Send verification\"><i class=\"bi bi-phone-vibrate\"></i></button>\n {{/model.is_phone_verified|bool}}\n {{/hasPhone|bool}}\n </div>\n\n <!-- Personal -->\n <div class=\"po-section-label\">Personal</div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Display Name</div>\n <div class=\"po-field-value\">{{model.display_name}}</div>\n <button type=\"button\" class=\"po-field-action\" data-action=\"edit-name\"><i class=\"bi bi-pencil\"></i></button>\n </div>\n {{#hasFullName|bool}}\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Full Name</div>\n <div class=\"po-field-value\">{{fullName}}</div>\n </div>\n {{/hasFullName|bool}}\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Timezone</div>\n <div class=\"po-field-value\">{{model.timezone|default:'Not set'}}</div>\n <button type=\"button\" class=\"po-field-action\" data-action=\"edit-timezone\"><i class=\"bi bi-pencil\"></i></button>\n </div>\n\n <!-- Account -->\n <div class=\"po-section-label\">Account</div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Username</div>\n <div class=\"po-field-value\">{{model.username}}</div>\n </div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Status</div>\n <div class=\"po-field-value\">\n {{#model.is_active|bool}}<span class=\"po-badge-ok\">Active</span>{{/model.is_active|bool}}\n {{^model.is_active|bool}}<span class=\"po-badge-warn\">Inactive</span>{{/model.is_active|bool}}\n </div>\n </div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Role</div>\n <div class=\"po-field-value\">\n {{roleLabel}}\n {{#model.is_staff|bool}}<span class=\"po-badge-muted\">Staff</span>{{/model.is_staff|bool}}\n </div>\n </div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">MFA</div>\n <div class=\"po-field-value\">\n {{#model.requires_mfa|bool}}<span class=\"po-badge-ok\">Required</span>{{/model.requires_mfa|bool}}\n {{^model.requires_mfa|bool}}<span class=\"po-badge-muted\">Not required</span>{{/model.requires_mfa|bool}}\n </div>\n </div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Member Since</div>\n <div class=\"po-field-value\">{{model.date_joined|date}}</div>\n </div>\n <div class=\"po-field-row\">\n <div class=\"po-field-label\">Last Login</div>\n <div class=\"po-field-value\">{{model.last_login|relative}}</div>\n </div>\n\n <!-- Permissions peek -->\n <div class=\"po-section-label\">Permissions</div>\n <div class=\"po-perms-peek\">\n {{#permissionPeek}}\n {{#items}}\n <span class=\"po-perm-pill\">{{.}}</span>\n {{/items}}\n {{#remaining|bool}}\n <span class=\"po-perm-more\" data-action=\"navigate\" data-section=\"permissions\">+{{remaining}} more</span>\n {{/remaining|bool}}\n {{/permissionPeek}}\n {{^permissionPeek|bool}}\n <span class=\"po-not-set\">No permissions assigned</span>\n {{/permissionPeek|bool}}\n </div>\n `,\n ...options\n });\n }\n\n get hasPhone() {\n return !!(this.model && this.model.get('phone_number'));\n }\n\n get hasFullName() {\n if (!this.model) return false;\n const first = this.model.get('first_name');\n const last = this.model.get('last_name');\n return !!(first || last);\n }\n\n get fullName() {\n if (!this.model) return '';\n const first = this.model.get('first_name') || '';\n const last = this.model.get('last_name') || '';\n return `${first} ${last}`.trim();\n }\n\n get roleLabel() {\n if (!this.model) return 'User';\n if (this.model.get('is_superuser')) return 'Superuser';\n return 'User';\n }\n\n get permissionPeek() {\n if (!this.model) return null;\n const perms = this.model.get('permissions');\n if (!perms) return null;\n\n const permMap = {};\n User.PERMISSIONS.forEach(p => { permMap[p.name] = p.label; });\n\n const active = Object.keys(perms)\n .filter(k => perms[k] === true)\n .map(k => permMap[k] || k.replace(/_/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase()));\n\n if (active.length === 0) return null;\n\n const PEEK_COUNT = 5;\n return {\n items: active.slice(0, PEEK_COUNT),\n remaining: Math.max(0, active.length - PEEK_COUNT)\n };\n }\n\n async onActionEditName() {\n const name = await Dialog.prompt(\n 'Enter your display name:',\n 'Display Name',\n { defaultValue: this.model.get('display_name') || '' }\n );\n if (name !== null && name.trim()) {\n const resp = await this.model.save({ display_name: name.trim() });\n if (resp.status === 200) {\n this.getApp()?.toast?.success('Display name updated');\n await this.render();\n } else {\n this.getApp()?.toast?.error('Failed to update display name');\n }\n }\n return true;\n }\n\n async onActionEditTimezone() {\n const result = await Dialog.showForm({\n title: 'Change Timezone',\n fields: [{\n name: 'timezone',\n type: 'select',\n label: 'Timezone',\n columns: 12,\n options: [\n { value: 'America/New_York', text: 'Eastern Time (ET)' },\n { value: 'America/Chicago', text: 'Central Time (CT)' },\n { value: 'America/Denver', text: 'Mountain Time (MT)' },\n { value: 'America/Los_Angeles', text: 'Pacific Time (PT)' },\n { value: 'America/Anchorage', text: 'Alaska Time (AKT)' },\n { value: 'Pacific/Honolulu', text: 'Hawaii Time (HT)' },\n { value: 'UTC', text: 'UTC' },\n { value: 'Europe/London', text: 'London (GMT/BST)' },\n { value: 'Europe/Paris', text: 'Paris (CET/CEST)' },\n { value: 'Europe/Berlin', text: 'Berlin (CET/CEST)' },\n { value: 'Asia/Tokyo', text: 'Tokyo (JST)' },\n { value: 'Asia/Shanghai', text: 'Shanghai (CST)' },\n { value: 'Australia/Sydney', text: 'Sydney (AEST)' }\n ]\n }],\n data: { timezone: this.model.get('timezone') || '' },\n size: 'sm'\n });\n\n if (result && result.submitted) {\n const resp = await this.model.save({ timezone: result.data.timezone });\n if (resp.status === 200) {\n this.getApp()?.toast?.success('Timezone updated');\n await this.render();\n } else {\n this.getApp()?.toast?.error('Failed to update timezone');\n }\n }\n return true;\n }\n\n async onActionVerifyEmail() {\n try {\n const resp = await rest.POST('/api/account/email/verify/send');\n if (resp.success) {\n this.getApp()?.toast?.success('Verification email sent');\n } else {\n this.getApp()?.toast?.error(resp.data?.error || 'Failed to send verification email');\n }\n } catch (err) {\n this.getApp()?.toast?.error('Failed to send verification email');\n }\n return true;\n }\n\n async onActionVerifyPhone() {\n try {\n const resp = await rest.POST('/api/account/phone/verify/send');\n if (resp.success) {\n this.getApp()?.toast?.success('Verification code sent');\n } else {\n this.getApp()?.toast?.error(resp.data?.error || 'Failed to send verification');\n }\n } catch (err) {\n this.getApp()?.toast?.error('Failed to send verification');\n }\n return true;\n }\n\n async onActionAddPhone() {\n this.getApp()?.toast?.info('Phone management coming soon');\n return true;\n }\n\n async onActionNavigate(event, el) {\n // Bubble up to parent UserProfileView\n if (this.parent && this.parent.onActionNavigate) {\n return this.parent.onActionNavigate(event, el);\n }\n return true;\n }\n}\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\n/**\n * Passkey - WebAuthn/FIDO2 passkey model\n * Maps to REST endpoints under /api/account/passkeys\n *\n * Key operations:\n * - List/View/Update/Delete passkeys (standard CRUD)\n * - Register new passkeys via WebAuthn flow\n *\n * Notes:\n * - Registration is a two-step process: begin → complete\n * - Login flow is NOT handled here (separate auth flow)\n * - Passkeys are portal-specific (rp_id = domain)\n * - Most fields are read-only; only friendly_name and is_enabled are editable\n */\nclass Passkey extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/account/passkeys',\n ...options\n });\n }\n\n /**\n * Begin passkey registration flow.\n * POST /api/account/passkeys/register/begin\n *\n * Returns WebAuthn challenge and publicKey options for navigator.credentials.create()\n *\n * @param {object} options - Optional { params?: object }\n * @returns {Promise<object>} REST response with challenge_id and publicKey options\n *\n * Example response:\n * {\n * status: true,\n * data: {\n * challenge_id: \"uuid...\",\n * expiresAt: \"2025-02-05T18:27:10Z\",\n * publicKey: {\n * rp: { name: \"MOJO\", id: \"portal.example.com\" },\n * user: { name: \"username\", displayName: \"User\", id: \"...\" },\n * challenge: \"base64...\",\n * pubKeyCredParams: [...],\n * authenticatorSelection: {...}\n * }\n * }\n * }\n */\n static async registerBegin(options = {}) {\n try {\n const url = '/api/account/passkeys/register/begin';\n return await rest.POST(url, {}, options.params);\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to begin passkey registration'\n };\n }\n }\n\n /**\n * Complete passkey registration flow.\n * POST /api/account/passkeys/register/complete\n *\n * Submits the WebAuthn credential created by navigator.credentials.create()\n *\n * @param {object} data\n * challenge_id: string (from registerBegin response)\n * credential: object (from navigator.credentials.create)\n * friendly_name: string (optional, e.g., \"My iPhone\")\n * @param {object} options - Optional { params?: object }\n * @returns {Promise<object>} REST response with created Passkey object\n *\n * Example data:\n * {\n * challenge_id: \"uuid...\",\n * credential: {\n * id: \"...\",\n * rawId: \"...\",\n * type: \"public-key\",\n * response: {\n * clientDataJSON: \"...\",\n * attestationObject: \"...\"\n * },\n * transports: [\"internal\"]\n * },\n * friendly_name: \"My MacBook\"\n * }\n */\n static async registerComplete(data = {}, options = {}) {\n if (!data.challenge_id) {\n return {\n success: false,\n status: 400,\n error: 'Missing challenge_id'\n };\n }\n\n if (!data.credential) {\n return {\n success: false,\n status: 400,\n error: 'Missing credential data'\n };\n }\n\n try {\n const url = '/api/account/passkeys/register/complete';\n return await rest.POST(url, data, options.params);\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to complete passkey registration'\n };\n }\n }\n}\n\n/**\n * PasskeyList - Collection of Passkey\n * Supports standard MOJO list/search/sort/pagination patterns\n */\nclass PasskeyList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Passkey,\n endpoint: '/api/account/passkeys',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for Passkey\n *\n * Notes:\n * - No create form (registration uses WebAuthn flow)\n * - Edit form allows changing friendly_name and is_enabled only\n * - View form shows all fields as read-only for informational purposes\n */\nconst PasskeyForms = {\n edit: {\n title: 'Edit Passkey',\n fields: [\n {\n name: 'friendly_name',\n type: 'text',\n label: 'Name',\n placeholder: 'My iPhone',\n required: true,\n columns: 12,\n help: 'A friendly name to identify this passkey'\n },\n {\n name: 'is_enabled',\n type: 'switch',\n label: 'Enabled',\n columns: 12,\n help: 'Disable to prevent this passkey from being used for authentication'\n }\n ]\n },\n\n view: {\n title: 'Passkey Details',\n fields: [\n {\n name: 'friendly_name',\n type: 'text',\n label: 'Name',\n readonly: true,\n columns: 12\n },\n {\n name: 'is_enabled',\n type: 'switch',\n label: 'Enabled',\n readonly: true,\n columns: 6\n },\n {\n name: 'rp_id',\n type: 'text',\n label: 'Portal (RP ID)',\n readonly: true,\n columns: 6,\n help: 'The portal/domain this passkey is registered for'\n },\n {\n name: 'aaguid',\n type: 'text',\n label: 'Authenticator GUID',\n readonly: true,\n columns: 12,\n help: 'Unique identifier for the authenticator device'\n },\n {\n name: 'transports',\n type: 'text',\n label: 'Transports',\n readonly: true,\n columns: 6,\n help: 'Available transport methods (e.g., internal, usb, nfc, ble)'\n },\n {\n name: 'sign_count',\n type: 'number',\n label: 'Signature Count',\n readonly: true,\n columns: 6,\n help: 'Number of times this passkey has been used (for clone detection)'\n },\n {\n name: 'last_used',\n type: 'datetime',\n label: 'Last Used',\n readonly: true,\n columns: 6\n },\n {\n name: 'created',\n type: 'datetime',\n label: 'Created',\n readonly: true,\n columns: 6\n }\n ]\n }\n};\n\nexport {\n Passkey,\n PasskeyList,\n PasskeyForms\n};\n","/**\n * ProfileSessionsSection - Sessions tab\n *\n * Uses ListView + UserDeviceLocationList to show\n * active sessions with browser, device, location info.\n */\nimport View from '@core/View.js';\nimport ListView from '@core/views/list/ListView.js';\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport { UserDeviceLocationList } from '@core/models/User.js';\n\nclass SessionItem extends ListViewItem {\n get browserName() {\n const ud = this.model?.get('user_device');\n return ud?.device_info?.user_agent?.family || 'Unknown';\n }\n\n get deviceName() {\n const ud = this.model?.get('user_device');\n const dev = ud?.device_info?.device || {};\n return `${dev.brand || ''} ${dev.family || ''}`.trim() || 'Unknown Device';\n }\n\n get location() {\n const geo = this.model?.get('geolocation') || {};\n const parts = [geo.city, geo.region].filter(Boolean);\n return parts.length ? parts.join(', ') : (geo.country_name || '');\n }\n\n get isMobile() {\n const ud = this.model?.get('user_device');\n const dev = ud?.device_info?.device || {};\n const os = ud?.device_info?.os || {};\n return ['iPhone', 'Android'].some(m =>\n (dev.family || '').includes(m) || (os.family || '').includes(m)\n );\n }\n\n get deviceIcon() {\n return this.isMobile ? 'bi-phone' : 'bi-laptop';\n }\n}\n\nexport default class ProfileSessionsSection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-sessions-section',\n template: `\n <style>\n .pss-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.75rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.4rem; }\n .pss-icon { width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex-shrink: 0; background: #f0f0f0; color: #6c757d; }\n .pss-info { flex: 1; min-width: 0; }\n .pss-name { font-weight: 600; font-size: 0.85rem; }\n .pss-meta { font-size: 0.73rem; color: #6c757d; }\n </style>\n <div id=\"sessions-list\"></div>\n `,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.listView = new ListView({\n containerId: 'sessions-list',\n collection: new UserDeviceLocationList({ size: 20 }),\n defaultQuery: { user: this.model.id },\n itemClass: SessionItem,\n itemTemplate: `\n <div class=\"pss-row\">\n <div class=\"pss-icon\"><i class=\"bi {{deviceIcon}}\"></i></div>\n <div class=\"pss-info\">\n <div class=\"pss-name\">{{browserName}} on {{deviceName}}</div>\n <div class=\"pss-meta\">{{location}} · {{model.ip_address}} · Last seen {{model.last_seen|relative}}</div>\n </div>\n </div>\n `,\n emptyMessage: 'No session data available'\n });\n this.addChild(this.listView);\n }\n}\n","/**\n * ProfileDevicesSection - Devices tab\n *\n * Uses ListView + UserDeviceList to show\n * registered devices with browser, OS, IP info.\n */\nimport View from '@core/View.js';\nimport ListView from '@core/views/list/ListView.js';\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport { UserDeviceList } from '@core/models/User.js';\n\nclass DeviceItem extends ListViewItem {\n get deviceName() {\n const info = this.model?.get('device_info') || {};\n const dev = info.device || {};\n return `${dev.brand || ''} ${dev.family || ''}`.trim() || 'Unknown Device';\n }\n\n get browserInfo() {\n const info = this.model?.get('device_info') || {};\n const ua = info.user_agent || {};\n return ua.family ? `${ua.family} ${ua.major || ''}`.trim() : 'Unknown Browser';\n }\n\n get osName() {\n const info = this.model?.get('device_info') || {};\n return info.os?.family || '';\n }\n\n get isMobile() {\n const info = this.model?.get('device_info') || {};\n const dev = info.device || {};\n const os = info.os || {};\n return ['iPhone', 'Android'].some(m =>\n (dev.family || '').includes(m) || (os.family || '').includes(m)\n );\n }\n\n get deviceIcon() {\n return this.isMobile ? 'bi-phone' : 'bi-laptop';\n }\n}\n\nexport default class ProfileDevicesSection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-devices-section',\n template: `\n <style>\n .pd-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.75rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.4rem; }\n .pd-icon { width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex-shrink: 0; background: #f0f0f0; color: #495057; }\n .pd-info { flex: 1; min-width: 0; }\n .pd-name { font-weight: 600; font-size: 0.85rem; }\n .pd-meta { font-size: 0.73rem; color: #6c757d; }\n </style>\n <div id=\"devices-list\"></div>\n `,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.listView = new ListView({\n containerId: 'devices-list',\n collection: new UserDeviceList({ size: 20 }),\n defaultQuery: { user: this.model.id },\n itemClass: DeviceItem,\n itemTemplate: `\n <div class=\"pd-row\">\n <div class=\"pd-icon\"><i class=\"bi {{deviceIcon}}\"></i></div>\n <div class=\"pd-info\">\n <div class=\"pd-name\">{{deviceName}}</div>\n <div class=\"pd-meta\">{{browserInfo}} · {{osName}} · {{model.last_ip}} · Last seen {{model.last_seen|relative}}</div>\n </div>\n </div>\n `,\n emptyMessage: 'No registered devices'\n });\n this.addChild(this.listView);\n }\n}\n","/**\n * ProfileActivitySection - Activity log tab\n *\n * Uses TableView + LogList to show user activity\n * with parsed log messages, level badges, kind, path, and time.\n * Includes pagination, search, and level filtering.\n */\nimport View from '@core/View.js';\nimport TableView from '@core/views/table/TableView.js';\nimport TableRow from '@core/views/table/TableRow.js';\nimport { LogList } from '@core/models/Log.js';\n\nclass ActivityRow extends TableRow {\n get logMessage() {\n const log = this.model?.get('log');\n if (!log) return '';\n if (typeof log === 'string') {\n try {\n const parsed = JSON.parse(log);\n return parsed.message || parsed.type || log.substring(0, 120);\n } catch {\n return log.length > 120 ? log.substring(0, 120) + '…' : log;\n }\n }\n if (typeof log === 'object') {\n return log.message || log.type || JSON.stringify(log).substring(0, 120);\n }\n return String(log);\n }\n\n get levelText() {\n return this.model?.get('level') || 'log';\n }\n\n get levelBadgeClass() {\n const level = this.model?.get('level');\n if (level === 'error') return 'bg-danger';\n if (level === 'warn') return 'bg-warning text-dark';\n if (level === 'info') return 'bg-info';\n return 'bg-secondary';\n }\n\n get methodPath() {\n const method = this.model?.get('method') || '';\n const path = this.model?.get('path') || '';\n if (!method && !path) return '';\n return `${method} ${path}`.trim();\n }\n}\n\nexport default class ProfileActivitySection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-activity-section',\n template: `<div id=\"activity-table\"></div>`,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.tableView = new TableView({\n containerId: 'activity-table',\n collection: new LogList({ size: 10 }),\n defaultQuery: { uid: this.model.id, sort: '-created' },\n hideActivePillNames: ['uid', 'sort'],\n itemClass: ActivityRow,\n columns: [\n {\n key: 'level',\n label: 'Level',\n sortable: true,\n template: '<span class=\"badge {{levelBadgeClass}}\">{{levelText}}</span>',\n filter: { type: 'select', options: ['info', 'warn', 'error'] }\n },\n {\n key: 'log',\n label: 'Message',\n template: '{{logMessage}}'\n },\n { key: 'kind', label: 'Kind', sortable: true, visibility: 'md' },\n {\n key: 'path',\n label: 'Path',\n visibility: 'lg',\n template: '{{methodPath}}'\n },\n { key: 'ip', label: 'IP', visibility: 'xl' },\n { key: 'created|relative', label: 'Time', sortable: true }\n ],\n searchable: true,\n sortable: true,\n filterable: true,\n paginated: true,\n showAdd: false,\n showExport: false,\n tableOptions: {\n striped: false,\n hover: true,\n size: 'sm'\n },\n emptyMessage: 'No activity logs available'\n });\n this.addChild(this.tableView);\n }\n}\n","/**\n * ProfileGroupsSection - Groups/memberships tab\n *\n * Uses ListView + MemberList to show user's group\n * memberships with role badges.\n */\nimport View from '@core/View.js';\nimport ListView from '@core/views/list/ListView.js';\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport { MemberList } from '@core/models/Member.js';\n\nconst AVATAR_COLORS = ['#667eea', '#f5576c', '#38b2ac', '#ed8936', '#9f7aea', '#48bb78', '#4299e1', '#ed64a6'];\n\nclass GroupMemberItem extends ListViewItem {\n get groupName() {\n return this.model?.get('group')?.name || 'Unknown Group';\n }\n\n get groupKind() {\n const kind = this.model?.get('group')?.kind || '';\n return kind.replace(/^\\w/, c => c.toUpperCase());\n }\n\n get initials() {\n return this.groupName.split(/\\s+/).map(w => w[0]).join('').substring(0, 2).toUpperCase();\n }\n\n get avatarColor() {\n const name = this.groupName;\n let hash = 0;\n for (let i = 0; i < name.length; i++) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash);\n }\n return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];\n }\n\n get roleName() {\n let role = this.model?.get('metadata')?.role || '';\n if (!role && this.model?.get('permissions')?.manage_group) {\n role = 'Admin';\n }\n return role;\n }\n\n get hasRole() {\n return !!this.roleName;\n }\n\n get roleBadgeClass() {\n const r = (this.roleName || '').toLowerCase();\n if (r === 'owner') return 'bg-primary';\n if (r === 'admin') return 'bg-info';\n return 'bg-secondary';\n }\n}\n\nexport default class ProfileGroupsSection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-groups-section',\n template: `\n <style>\n .pg-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0; border-bottom: 1px solid #f0f0f0; }\n .pg-row:last-child { border-bottom: none; }\n .pg-avatar { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; color: #fff; flex-shrink: 0; }\n .pg-info { flex: 1; }\n .pg-name { font-weight: 600; font-size: 0.88rem; }\n .pg-meta { font-size: 0.73rem; color: #6c757d; }\n .pg-role { font-size: 0.7rem; }\n </style>\n <div id=\"groups-list\"></div>\n `,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.listView = new ListView({\n containerId: 'groups-list',\n collection: new MemberList({ size: 50 }),\n defaultQuery: { user: this.model.id },\n itemClass: GroupMemberItem,\n itemTemplate: `\n <div class=\"pg-row\">\n <div class=\"pg-avatar\" style=\"background: {{avatarColor}};\">{{initials}}</div>\n <div class=\"pg-info\">\n <div class=\"pg-name\">{{groupName}}</div>\n <div class=\"pg-meta\">{{groupKind}}</div>\n </div>\n {{#hasRole|bool}}\n <span class=\"pg-role badge {{roleBadgeClass}}\">{{roleName}}</span>\n {{/hasRole|bool}}\n </div>\n `,\n emptyMessage: 'You are not a member of any groups'\n });\n this.addChild(this.listView);\n }\n}\n","/**\n * UserProfileView - Rich user profile shown in a Dialog\n *\n * Main container with left nav and section switching.\n * 7 sections: Profile, Security, Sessions, Devices, Activity, Groups, Permissions.\n * Fetches full user data on render.\n * Includes its own context menu (3-dot) for quick actions.\n */\nimport View from '@core/View.js';\nimport ContextMenu from '@core/views/feedback/ContextMenu.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport { File as FileModel } from '@core/models/Files.js';\nimport ProfileOverviewSection from './ProfileOverviewSection.js';\nimport ProfileSecuritySection from './ProfileSecuritySection.js';\nimport ProfileSessionsSection from './ProfileSessionsSection.js';\nimport ProfileDevicesSection from './ProfileDevicesSection.js';\nimport ProfileActivitySection from './ProfileActivitySection.js';\nimport ProfileGroupsSection from './ProfileGroupsSection.js';\nimport ProfilePermissionsSection from './ProfilePermissionsSection.js';\n\nconst SECTIONS = {\n profile: ProfileOverviewSection,\n security: ProfileSecuritySection,\n sessions: ProfileSessionsSection,\n devices: ProfileDevicesSection,\n activity: ProfileActivitySection,\n groups: ProfileGroupsSection,\n permissions: ProfilePermissionsSection\n};\n\nexport default class UserProfileView extends View {\n constructor(options = {}) {\n super({\n className: 'user-profile-view',\n template: `\n <style>\n .up-layout { display: flex; height: 100%; }\n .up-nav { width: 200px; background: #f8f9fc; border-right: 1px solid #e9ecef; padding: 0.75rem 0; flex-shrink: 0; overflow-y: auto; }\n .up-nav-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; padding: 0.75rem 1.25rem 0.25rem; }\n .up-nav a { color: #495057; padding: 0.45rem 1.25rem; font-size: 0.85rem; display: flex; align-items: center; gap: 0.5rem; text-decoration: none; }\n .up-nav a:hover { background: #e9ecef; }\n .up-nav a.active { background: #e7f1ff; color: #0d6efd; font-weight: 600; border-right: 2px solid #0d6efd; }\n .up-nav a i { width: 18px; text-align: center; font-size: 0.9rem; }\n .up-content { flex: 1; overflow-y: auto; padding: 1.5rem 2.5rem; }\n .up-header { display: flex; align-items: center; gap: 1rem; padding: 0.85rem 1.25rem; border-bottom: 1px solid #e9ecef; background: #fff; }\n .up-avatar-wrap { position: relative; flex-shrink: 0; cursor: pointer; }\n .up-avatar-wrap img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; border: 2px solid #e9ecef; }\n .up-avatar-initials { width: 44px; height: 44px; border-radius: 50%; background: #e7f1ff; color: #0d6efd; display: flex; align-items: center; justify-content: center; font-size: 1rem; font-weight: 700; border: 2px solid #e9ecef; }\n .up-header-info { flex: 1; min-width: 0; }\n .up-header-info h5 { margin: 0; font-weight: 700; font-size: 1rem; }\n .up-header-info .up-sub { font-size: 0.78rem; color: #6c757d; }\n @media (max-width: 576px) {\n .up-nav { display: none; }\n .up-content { padding: 1.25rem; }\n }\n </style>\n <div class=\"up-layout\" style=\"flex-direction: column; min-height: 480px;\">\n <div class=\"up-header\">\n <div class=\"up-avatar-wrap\" data-action=\"change-avatar\" title=\"Change avatar\">\n {{{model.avatar|avatar}}}\n </div>\n <div class=\"up-header-info\">\n <h5>{{model.display_name}}</h5>\n <div class=\"up-sub\">@{{model.username}} · {{model.last_activity|relative}}</div>\n </div>\n <div id=\"up-context-menu\"></div>\n </div>\n <div class=\"up-layout\" style=\"flex: 1; min-height: 0;\">\n <nav class=\"up-nav\">\n <a href=\"#\" class=\"nav-link active\" data-action=\"navigate\" data-section=\"profile\"><i class=\"bi bi-person\"></i> Profile</a>\n <a href=\"#\" class=\"nav-link\" data-action=\"navigate\" data-section=\"security\"><i class=\"bi bi-shield-lock\"></i> Security</a>\n <a href=\"#\" class=\"nav-link\" data-action=\"navigate\" data-section=\"groups\"><i class=\"bi bi-people\"></i> Groups</a>\n <a href=\"#\" class=\"nav-link\" data-action=\"navigate\" data-section=\"permissions\"><i class=\"bi bi-shield-check\"></i> Permissions</a>\n <div class=\"up-nav-label\">Activity</div>\n <a href=\"#\" class=\"nav-link\" data-action=\"navigate\" data-section=\"sessions\"><i class=\"bi bi-clock-history\"></i> Sessions</a>\n <a href=\"#\" class=\"nav-link\" data-action=\"navigate\" data-section=\"devices\"><i class=\"bi bi-laptop\"></i> Devices</a>\n <a href=\"#\" class=\"nav-link\" data-action=\"navigate\" data-section=\"activity\"><i class=\"bi bi-activity\"></i> Activity Log</a>\n </nav>\n <div class=\"up-content\" id=\"profile-section\"></div>\n </div>\n </div>\n `,\n ...options\n });\n this.activeSection = 'profile';\n this.sectionView = null;\n }\n\n get hasAvatar() {\n return !!(this.model && this.model.get('avatar') && this.model.get('avatar').url);\n }\n\n async onBeforeRender() {\n if (this.model) {\n await this.model.fetch({ params: { graph: 'full' } });\n }\n }\n\n async onInit() {\n await super.onInit();\n\n // Context menu (3-dot)\n this.contextMenu = new ContextMenu({\n containerId: 'up-context-menu',\n config: {\n icon: 'bi-three-dots-vertical',\n items: [\n { icon: 'bi-lock', label: 'Change Password', action: 'change-password' },\n { icon: 'bi-envelope', label: 'Update Email', action: 'update-email' },\n { icon: 'bi-phone', label: 'Update Phone', action: 'update-phone' }\n ]\n }\n });\n this.addChild(this.contextMenu);\n\n // Default section\n this.sectionView = new ProfileOverviewSection({\n model: this.model,\n containerId: 'profile-section'\n });\n this.addChild(this.sectionView);\n }\n\n async onActionNavigate(event, el) {\n event.preventDefault();\n const section = el.dataset.section;\n if (!section || section === this.activeSection) return true;\n\n const SectionClass = SECTIONS[section];\n if (!SectionClass) return true;\n\n // Remove current section\n if (this.sectionView) {\n this.removeChild(this.sectionView);\n }\n\n // Create and add new section\n this.sectionView = new SectionClass({\n model: this.model,\n containerId: 'profile-section'\n });\n this.addChild(this.sectionView);\n await this.sectionView.render();\n\n this.activeSection = section;\n\n // Update nav active state\n this.element.querySelectorAll('.up-nav a').forEach(link => {\n link.classList.toggle('active', link.dataset.section === section);\n });\n\n return true;\n }\n\n async onActionChangeAvatar() {\n const resp = await Dialog.updateModelImage({\n model: this.model,\n field: 'avatar',\n title: 'Change Avatar',\n upload: true,\n }, {\n name: 'avatar',\n size: 'lg',\n imageSize: { width: 200, height: 200 },\n placeholder: 'Upload your avatar',\n });\n if (resp && resp.status === 200) {\n await this.render();\n }\n }\n\n // Context menu action handlers (dispatched by ContextMenu to parent)\n async onActionChangePassword() {\n const app = this.getApp();\n if (app && app.changePassword) {\n await app.changePassword();\n }\n return true;\n }\n\n async onActionUpdateEmail() {\n this.getApp()?.toast?.info('Email management coming soon');\n return true;\n }\n\n async onActionUpdatePhone() {\n this.getApp()?.toast?.info('Phone management coming soon');\n return true;\n }\n}\n","/**\n * ProfileSecuritySection - Security dashboard tab\n *\n * Compact card rows. Password and Passkeys are actions (dialog/flow).\n * Sessions, Devices, Activity navigate to their own nav sections.\n */\nimport View from '@core/View.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport { Passkey, PasskeyList, PasskeyForms } from '@core/models/Passkeys.js';\n\nexport default class ProfileSecuritySection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-security-section',\n template: `\n <style>\n .ps-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .ps-section-label:first-child { margin-top: 0; }\n .ps-item { display: flex; align-items: center; gap: 0.85rem; padding: 0.85rem 1rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.5rem; cursor: pointer; transition: border-color 0.15s, background 0.15s; }\n .ps-item:hover { border-color: #dee2e6; background: #fafbfd; }\n .ps-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1rem; flex-shrink: 0; }\n .ps-info { flex: 1; min-width: 0; }\n .ps-title { font-weight: 600; font-size: 0.88rem; }\n .ps-desc { font-size: 0.78rem; color: #6c757d; }\n .ps-badge { font-size: 0.72rem; padding: 0.15em 0.5em; border-radius: 3px; flex-shrink: 0; }\n .ps-chevron { color: #ced4da; font-size: 0.8rem; flex-shrink: 0; }\n </style>\n\n <div class=\"ps-section-label\">Authentication</div>\n\n <div class=\"ps-item\" data-action=\"change-password\">\n <div class=\"ps-icon bg-primary bg-opacity-10 text-primary\"><i class=\"bi bi-lock\"></i></div>\n <div class=\"ps-info\">\n <div class=\"ps-title\">Password</div>\n <div class=\"ps-desc\">Change your account password</div>\n </div>\n <span class=\"ps-badge bg-light text-muted border\">Change</span>\n </div>\n\n <div class=\"ps-item\" data-action=\"manage-passkeys\">\n <div class=\"ps-icon bg-success bg-opacity-10 text-success\"><i class=\"bi bi-fingerprint\"></i></div>\n <div class=\"ps-info\">\n <div class=\"ps-title\">Passkeys</div>\n <div class=\"ps-desc\">Passwordless sign-in with biometrics</div>\n </div>\n <i class=\"bi bi-chevron-right ps-chevron\"></i>\n </div>\n\n <div class=\"ps-section-label\">Sessions & Devices</div>\n\n <div class=\"ps-item\" data-action=\"navigate\" data-section=\"sessions\">\n <div class=\"ps-icon bg-info bg-opacity-10 text-info\"><i class=\"bi bi-clock-history\"></i></div>\n <div class=\"ps-info\">\n <div class=\"ps-title\">Active Sessions</div>\n <div class=\"ps-desc\">Manage where you're signed in</div>\n </div>\n <i class=\"bi bi-chevron-right ps-chevron\"></i>\n </div>\n\n <div class=\"ps-item\" data-action=\"navigate\" data-section=\"devices\">\n <div class=\"ps-icon bg-warning bg-opacity-10 text-warning\"><i class=\"bi bi-laptop\"></i></div>\n <div class=\"ps-info\">\n <div class=\"ps-title\">Devices</div>\n <div class=\"ps-desc\">Registered devices</div>\n </div>\n <i class=\"bi bi-chevron-right ps-chevron\"></i>\n </div>\n\n <div class=\"ps-section-label\">Activity</div>\n\n <div class=\"ps-item\" data-action=\"navigate\" data-section=\"activity\">\n <div class=\"ps-icon bg-secondary bg-opacity-10 text-secondary\"><i class=\"bi bi-activity\"></i></div>\n <div class=\"ps-info\">\n <div class=\"ps-title\">Recent Activity</div>\n <div class=\"ps-desc\">Sign-in history and account events</div>\n </div>\n <i class=\"bi bi-chevron-right ps-chevron\"></i>\n </div>\n `,\n ...options\n });\n }\n\n // --- Actions ---\n\n async onActionChangePassword() {\n const app = this.getApp();\n if (app && app.changePassword) {\n await app.changePassword();\n }\n return true;\n }\n\n async onActionManagePasskeys() {\n const collection = new PasskeyList({ params: { user: this.model.id } });\n try {\n await collection.fetch();\n } catch (e) {\n // ignore fetch errors\n }\n\n const items = collection.models || [];\n const view = new View({\n template: `\n <style>\n .pk-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.75rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.4rem; }\n .pk-icon { width: 32px; height: 32px; background: #e7f1ff; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #0d6efd; font-size: 0.9rem; flex-shrink: 0; }\n .pk-info { flex: 1; min-width: 0; }\n .pk-name { font-weight: 600; font-size: 0.85rem; }\n .pk-meta { font-size: 0.73rem; color: #6c757d; }\n .pk-actions { display: flex; gap: 0.25rem; }\n .pk-actions .btn { padding: 0.2rem 0.4rem; font-size: 0.75rem; }\n .pk-empty { text-align: center; padding: 2rem 1rem; color: #6c757d; }\n .pk-empty i { font-size: 2rem; color: #ced4da; display: block; margin-bottom: 0.5rem; }\n </style>\n {{#passkeys}}\n <div class=\"pk-row\">\n <div class=\"pk-icon\"><i class=\"bi bi-fingerprint\"></i></div>\n <div class=\"pk-info\">\n <div class=\"pk-name\">{{.friendly_name|default:'Unnamed Passkey'}}</div>\n <div class=\"pk-meta\">Created {{.created|date}} · Last used {{.last_used|relative|default:'never'}} · {{.sign_count}} uses</div>\n </div>\n <div class=\"pk-actions\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" data-action=\"edit-passkey\" data-id=\"{{.id}}\" title=\"Edit\"><i class=\"bi bi-pencil\"></i></button>\n <button type=\"button\" class=\"btn btn-outline-danger\" data-action=\"delete-passkey\" data-id=\"{{.id}}\" title=\"Delete\"><i class=\"bi bi-trash\"></i></button>\n </div>\n </div>\n {{/passkeys}}\n {{^passkeys|bool}}\n <div class=\"pk-empty\">\n <i class=\"bi bi-fingerprint\"></i>\n No passkeys registered yet\n </div>\n {{/passkeys|bool}}\n `\n });\n view.passkeys = items.map(p => p.toJSON ? p.toJSON() : p);\n\n view.onActionEditPasskey = async (event, el) => {\n const id = el.dataset.id;\n const passkey = items.find(p => String(p.id) === String(id));\n if (passkey) {\n await Dialog.showModelForm({\n title: 'Edit Passkey',\n model: passkey,\n fields: PasskeyForms.edit.fields,\n size: 'sm'\n });\n }\n return true;\n };\n\n view.onActionDeletePasskey = async (event, el) => {\n const id = el.dataset.id;\n const confirmed = await Dialog.confirm('Delete this passkey? You won\\'t be able to use it to sign in anymore.', 'Delete Passkey');\n if (confirmed) {\n const passkey = items.find(p => String(p.id) === String(id));\n if (passkey) {\n await passkey.destroy();\n this.getApp()?.toast?.success('Passkey deleted');\n }\n }\n return true;\n };\n\n const result = await Dialog.showDialog({\n title: 'Passkeys',\n body: view,\n size: 'md',\n buttons: [\n { text: 'Add Passkey', icon: 'bi-plus-lg', class: 'btn-primary', value: 'add' },\n { text: 'Close', class: 'btn-outline-secondary', dismiss: true }\n ]\n });\n\n if (result === 'add') {\n await this._registerPasskey();\n }\n return true;\n }\n\n _base64urlToBytes(base64url) {\n const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - base64.length % 4) % 4);\n return Uint8Array.from(atob(padded), c => c.charCodeAt(0));\n }\n\n async _registerPasskey() {\n try {\n const beginResp = await Passkey.registerBegin();\n if (!beginResp.success || !beginResp.data) {\n this.getApp()?.toast?.error('Failed to start passkey registration');\n return;\n }\n\n const options = beginResp.data.data || beginResp.data;\n const publicKey = options.publicKey;\n\n // Override rp.id to match current domain (server may return production domain)\n if (publicKey.rp) {\n publicKey.rp.id = window.location.hostname;\n }\n\n if (publicKey.challenge && typeof publicKey.challenge === 'string') {\n publicKey.challenge = this._base64urlToBytes(publicKey.challenge);\n }\n if (publicKey.user && publicKey.user.id && typeof publicKey.user.id === 'string') {\n publicKey.user.id = this._base64urlToBytes(publicKey.user.id);\n }\n if (publicKey.excludeCredentials) {\n publicKey.excludeCredentials = publicKey.excludeCredentials.map(cred => ({\n ...cred,\n id: typeof cred.id === 'string' ? this._base64urlToBytes(cred.id) : cred.id\n }));\n }\n\n const credential = await navigator.credentials.create({ publicKey });\n if (!credential) {\n this.getApp()?.toast?.error('Passkey creation was cancelled');\n return;\n }\n\n const friendlyName = await Dialog.prompt('Name this passkey:', 'Passkey Name', {\n defaultValue: '',\n placeholder: 'e.g., My MacBook'\n });\n\n const credentialData = {\n id: credential.id,\n rawId: btoa(String.fromCharCode(...new Uint8Array(credential.rawId))),\n type: credential.type,\n response: {\n clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON))),\n attestationObject: btoa(String.fromCharCode(...new Uint8Array(credential.response.attestationObject)))\n }\n };\n\n if (credential.response.getTransports) {\n credentialData.transports = credential.response.getTransports();\n }\n\n const completeResp = await Passkey.registerComplete({\n challenge_id: options.challenge_id,\n credential: credentialData,\n friendly_name: friendlyName || 'My Passkey'\n });\n\n if (completeResp.success) {\n this.getApp()?.toast?.success('Passkey registered successfully');\n } else {\n this.getApp()?.toast?.error(completeResp.error || 'Failed to register passkey');\n }\n } catch (err) {\n if (err.name === 'NotAllowedError') return;\n if (err.name === 'SecurityError') {\n this.getApp()?.toast?.error('Passkeys are not supported on this domain');\n } else {\n console.error('Passkey registration error:', err);\n this.getApp()?.toast?.error('Passkey registration failed');\n }\n }\n }\n\n // Navigate to sections in the parent UserProfileView\n async onActionNavigate(event, el) {\n if (this.parent && this.parent.onActionNavigate) {\n return this.parent.onActionNavigate(event, el);\n }\n return true;\n }\n}\n","/**\n * ProfilePermissionsSection - Read-only permissions tab\n *\n * Shows role indicator and flat permission tag cloud.\n * Pure template view — no fetching needed.\n */\nimport View from '@core/View.js';\nimport { User } from '@core/models/User.js';\n\nexport default class ProfilePermissionsSection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-permissions-section',\n template: `\n <style>\n .pp-role-bar { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 1rem; background: #f0f0ff; border-radius: 8px; margin-bottom: 1.25rem; font-size: 0.85rem; }\n .pp-role-bar i { color: #6f42c1; font-size: 1rem; }\n .pp-role-bar strong { color: #6f42c1; }\n .pp-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; }\n .pp-grid { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 1.25rem; }\n .pp-tag { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.78rem; padding: 0.25em 0.6em; background: #f8f9fc; border: 1px solid #e9ecef; border-radius: 4px; color: #495057; }\n .pp-tag i { font-size: 0.65rem; color: #198754; }\n .pp-note { font-size: 0.78rem; color: #adb5bd; margin-top: 1rem; }\n .pp-empty { color: #6c757d; font-style: italic; font-size: 0.85rem; padding: 1rem 0; }\n </style>\n\n {{#model.is_superuser|bool}}\n <div class=\"pp-role-bar\">\n <i class=\"bi bi-star-fill\"></i>\n <span><strong>Superuser</strong> — full system access</span>\n </div>\n {{/model.is_superuser|bool}}\n\n <div class=\"pp-section-label\">Granted Permissions</div>\n\n {{#permissionTags|bool}}\n <div class=\"pp-grid\">\n {{#permissionTags}}\n <span class=\"pp-tag\"><i class=\"bi bi-check-circle-fill\"></i> {{.}}</span>\n {{/permissionTags}}\n </div>\n {{/permissionTags|bool}}\n\n {{^permissionTags|bool}}\n <div class=\"pp-empty\">No permissions assigned</div>\n {{/permissionTags|bool}}\n\n <div class=\"pp-note\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Permissions are managed by your administrator.\n </div>\n `,\n ...options\n });\n }\n\n get permissionTags() {\n if (!this.model) return [];\n const perms = this.model.get('permissions');\n if (!perms) return [];\n\n const permMap = {};\n User.PERMISSIONS.forEach(p => { permMap[p.name] = p.label; });\n\n return Object.keys(perms)\n .filter(k => perms[k] === true)\n .map(k => permMap[k] || k.replace(/_/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase()));\n }\n}\n","/**\n * PasskeySetupView - Post-login passkey setup prompt\n *\n * Compact centered dialog body shown after login to encourage\n * users to register a passkey. Create / Skip / Don't ask again.\n */\nimport View from '@core/View.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport { Passkey } from '@core/models/Passkeys.js';\n\nexport default class PasskeySetupView extends View {\n constructor(options = {}) {\n super({\n className: 'passkey-setup-view',\n template: `\n <style>\n .pks-body { padding: 2rem 1.75rem 1rem; text-align: center; }\n .pks-icon { width: 56px; height: 56px; background: #e7f1ff; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 1.5rem; color: #0d6efd; margin-bottom: 1rem; }\n .pks-body h5 { font-weight: 700; font-size: 1.05rem; margin-bottom: 0.35rem; }\n .pks-body p { font-size: 0.83rem; color: #6c757d; margin-bottom: 1.25rem; line-height: 1.45; }\n .pks-footer { padding: 0 1.75rem 1.5rem; display: flex; flex-direction: column; gap: 0.4rem; }\n .pks-footer .btn-create { padding: 0.6rem; font-weight: 600; font-size: 0.9rem; border-radius: 8px; }\n .pks-footer .btn-skip { background: none; border: none; color: #6c757d; font-size: 0.82rem; padding: 0.4rem; cursor: pointer; }\n .pks-footer .btn-skip:hover { color: #495057; }\n .pks-dont-show { text-align: center; padding: 0 1.75rem 1.25rem; }\n .pks-dont-show label { font-size: 0.73rem; color: #adb5bd; cursor: pointer; }\n </style>\n\n <div class=\"pks-body\">\n <div class=\"pks-icon\"><i class=\"bi bi-fingerprint\"></i></div>\n <h5>Add a Passkey</h5>\n <p>Sign in faster with Face ID, Touch ID, or your device PIN. No passwords needed.</p>\n </div>\n <div class=\"pks-footer\">\n <button class=\"btn btn-primary btn-create\" data-action=\"create-passkey\"><i class=\"bi bi-fingerprint me-1\"></i>Create Passkey</button>\n <button class=\"btn-skip\" data-action=\"skip\">Not now</button>\n </div>\n <div class=\"pks-dont-show\">\n <label><input type=\"checkbox\" class=\"form-check-input form-check-input-sm me-1\" data-action=\"dont-ask\"> Don't ask again</label>\n </div>\n `,\n ...options\n });\n }\n\n _base64urlToBytes(base64url) {\n const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - base64.length % 4) % 4);\n return Uint8Array.from(atob(padded), c => c.charCodeAt(0));\n }\n\n async onActionCreatePasskey() {\n try {\n const beginResp = await Passkey.registerBegin();\n if (!beginResp.success || !beginResp.data) {\n this.getApp()?.toast?.error('Failed to start passkey registration');\n return true;\n }\n\n const options = beginResp.data.data || beginResp.data;\n const publicKey = options.publicKey;\n\n // Override rp.id to match current domain (server may return production domain)\n if (publicKey.rp) {\n publicKey.rp.id = window.location.hostname;\n }\n\n // Decode challenge and user.id from base64url\n if (publicKey.challenge && typeof publicKey.challenge === 'string') {\n publicKey.challenge = this._base64urlToBytes(publicKey.challenge);\n }\n if (publicKey.user && publicKey.user.id && typeof publicKey.user.id === 'string') {\n publicKey.user.id = this._base64urlToBytes(publicKey.user.id);\n }\n if (publicKey.excludeCredentials) {\n publicKey.excludeCredentials = publicKey.excludeCredentials.map(cred => ({\n ...cred,\n id: typeof cred.id === 'string' ? this._base64urlToBytes(cred.id) : cred.id\n }));\n }\n\n const credential = await navigator.credentials.create({ publicKey });\n if (!credential) {\n this.getApp()?.toast?.error('Passkey creation was cancelled');\n return true;\n }\n\n // Prompt for friendly name\n const friendlyName = await Dialog.prompt('Name this passkey:', 'Passkey Name', {\n defaultValue: '',\n placeholder: 'e.g., My MacBook'\n });\n\n const credentialData = {\n id: credential.id,\n rawId: btoa(String.fromCharCode(...new Uint8Array(credential.rawId))),\n type: credential.type,\n response: {\n clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON))),\n attestationObject: btoa(String.fromCharCode(...new Uint8Array(credential.response.attestationObject)))\n }\n };\n\n if (credential.response.getTransports) {\n credentialData.transports = credential.response.getTransports();\n }\n\n const completeResp = await Passkey.registerComplete({\n challenge_id: options.challenge_id,\n credential: credentialData,\n friendly_name: friendlyName || 'My Passkey'\n });\n\n if (completeResp.success) {\n this.getApp()?.toast?.success('Passkey registered successfully');\n // Dismiss after successful registration\n localStorage.setItem('passkey_setup_dismissed', '1');\n this.emit('dismiss');\n } else {\n this.getApp()?.toast?.error(completeResp.error || 'Failed to register passkey');\n }\n } catch (err) {\n if (err.name === 'NotAllowedError') return true;\n if (err.name === 'SecurityError') {\n this.getApp()?.toast?.error('Passkeys are not supported on this domain');\n } else {\n console.error('Passkey registration error:', err);\n this.getApp()?.toast?.error('Passkey registration failed');\n }\n }\n return true;\n }\n\n async onActionSkip() {\n this.emit('dismiss');\n return true;\n }\n\n async onActionDontAsk() {\n const checkbox = this.element?.querySelector('.pks-dont-show input[type=\"checkbox\"]');\n if (checkbox && checkbox.checked) {\n localStorage.setItem('passkey_setup_dismissed', '1');\n } else {\n localStorage.removeItem('passkey_setup_dismissed');\n }\n return true;\n }\n}\n","/**\n * MOJO Framework - Core Entry (2.1.0)\n */\n\n// Bundle core CSS\nimport '@core/css/core.css';\nimport '@core/css/portal.css';\nimport '@core/css/table.css';\nimport '@core/css/toast.css';\nimport '@core/css/chat.css';\n\nimport ConsoleSilencer from '@core/utils/ConsoleSilencer.js';\n// Reduce console noise globally: errors only by default (suppress logs, info, and warnings)\nConsoleSilencer.install({ level: 'warn' });\n\n// Version info\nexport {\n VERSION_INFO,\n VERSION,\n VERSION_MAJOR,\n VERSION_MINOR,\n VERSION_REVISION,\n BUILD_TIME\n} from './version.js';\n\n// Core runtime\nexport { default as View } from '@core/View.js';\nexport { default as Page } from '@core/Page.js';\nexport { default as Router } from '@core/Router.js';\nexport { default as Model } from '@core/Model.js';\nexport { default as Collection } from '@core/Collection.js';\nexport { default as Rest } from '@core/Rest.js';\n\n// Core Models - re-export everything from models/\nexport * from '@core/models/AWS.js';\nexport * from '@core/models/Email.js';\nexport * from '@core/models/Files.js';\nexport * from '@core/models/Group.js';\nexport * from '@core/models/Incident.js';\nexport * from '@core/models/Job.js';\nexport * from '@core/models/JobRunner.js';\nexport * from '@core/models/Log.js';\nexport * from '@core/models/Member.js';\nexport * from '@core/models/Metrics.js';\nexport * from '@core/models/Push.js';\nexport * from '@core/models/System.js';\nexport * from '@core/models/Tickets.js';\nexport * from '@core/models/User.js';\n\n// App classes\nexport { default as WebApp } from '@core/WebApp.js';\nexport { default as PortalApp } from '@core/PortalApp.js';\n\n// UI helper\nexport { default as Dialog } from '@core/views/feedback/Dialog.js';\n\n// Selected views (curated for tree-shaking)\nexport { default as TableView } from '@core/views/table/TableView.js';\nexport { default as TableRow } from '@core/views/table/TableRow.js';\nexport { default as TablePage } from '@core/pages/TablePage.js';\nexport { default as ListView } from '@core/views/list/ListView.js';\nexport { default as ListViewItem } from '@core/views/list/ListViewItem.js';\nexport { default as TopNav } from '@core/views/navigation/TopNav.js';\nexport { default as Sidebar } from '@core/views/navigation/Sidebar.js';\nexport { default as TabView } from '@core/views/navigation/TabView.js';\nexport { default as SimpleSearchView } from '@core/views/navigation/SimpleSearchView.js';\nexport { default as DataView } from '@core/views/data/DataView.js';\nexport { default as FormView } from '@core/forms/FormView.js';\nexport { default as FormPage } from '@core/forms/FormPage.js';\nexport { default as FilePreviewView } from '@core/views/data/FilePreviewView.js';\nexport { default as ChatView } from '@core/views/chat/ChatView.js';\nexport { default as ChatMessageView } from '@core/views/chat/ChatMessageView.js';\nexport { default as ChatInputView } from '@core/views/chat/ChatInputView.js';\n\n// Services, utils, mixins\nexport { default as FileUpload } from '@core/services/FileUpload.js';\nexport { default as applyFileDropMixin } from '@core/mixins/FileDropMixin.js';\nexport { default as TokenManager } from '@core/services/TokenManager.js';\nexport { default as ToastService } from '@core/services/ToastService.js';\nexport { default as WebSocketClient } from '@core/services/WebSocketClient.js';\nexport { default as EventDelegate } from '@core/mixins/EventDelegate.js';\nexport { default as EventBus } from '@core/utils/EventBus.js';\nexport { default as dataFormatter } from '@core/utils/DataFormatter.js';\nexport { default as MustacheFormatter } from '@core/utils/MustacheFormatter.js';\nexport { default as MOJOUtils, DataWrapper } from '@core/utils/MOJOUtils.js';\nexport { default as ConsoleSilencer } from '@core/utils/ConsoleSilencer.js';\nexport { installConsoleSilencer } from '@core/utils/ConsoleSilencer.js';\nexport { default as DjangoLookups, parseFilterKey, formatFilterDisplay, LOOKUPS } from '@core/utils/DjangoLookups.js';\n\n// Additional views\nexport { default as ProgressView } from '@core/views/feedback/ProgressView.js';\nexport { default as ContextMenu } from '@core/views/feedback/ContextMenu.js';\n\n// User profile views\nexport { UserProfileView, PasskeySetupView } from '@core/views/user/index.js';\n\n// Names\nexport const FRAMEWORK_NAME = 'MOJO';\nexport const PACKAGE_NAME = 'web-mojo';\n\nexport default {\n FRAMEWORK_NAME,\n PACKAGE_NAME,\n};\n"],"names":["LEVELS","Object","freeze","silent","error","warn","info","log","debug","trace","all","isDev","__vite_import_meta_env__","globalThis","__DEV__","process","env","NODE_ENV","isBrowser","window","document","GLOBAL","global","ORIGINAL_CONSOLE","console","ORIGINALS","INSTALLED","CURRENT_LEVEL","parseLevel","level","min","max","Math","key","toLowerCase","prototype","hasOwnProperty","call","makeWrapper","methodName","methodLevel","original","args","apply","ConsoleSilencer","install","options","this","setLevel","persist","explicitLevel","explicit","urlLevel","location","search","params","URLSearchParams","keys","k","v","get","parsed","getUrlLogLevel","storedLevel","localStorage","getItem","getStoredLogLevel","determineInitialLevel","patched","methodLevels","dir","table","group","groupCollapsed","groupEnd","time","timeEnd","timeLog","name","assert","condition","makeAssertWrapper","buildPatchedConsole","MOJOConsoleSilencer","uninstall","levelNumberOrName","entries","find","num","setItem","removeItem","storeLogLevel","getLevel","getLevelName","entry","criticalOnly","errorsOnly","verbose","allowAll","withTemporaryLevel","fn","prev","installConsoleSilencer","GroupSearchView","SimpleSearchView","constructor","super","className","trim","showKind","parentField","kindField","treeData","flattenedItems","showLines","buildTreeHierarchy","items","length","itemsById","Map","forEach","item","has","id","set","children","hasChildren","parentObj","rootItems","treeItem","itemId","originalItem","i","parentId","parent","push","calculateLevels","nodes","node","sort","a","b","localeCompare","flattenTree","result","ancestorLastFlags","index","_isLastChild","_ancestorLastFlags","allAncestorsLast","every","flag","_isLastDescendant","newFlags","computeVerticalLines","flatList","_continueVertical","s","foundSibling","j","futureItem","updateFilteredItems","collection","filteredItems","toJSON","updateResultsView","getDefaultItemTemplate","processItemTemplate","content","itemTemplate","replace","match","prop","getNestedValue","lineSegments","getRootItems","getNodeChildren","nodeId","findNode","targetId","found","Sidebar","View","tagName","menus","activeMenuName","currentRoute","showToggle","isCollapsed","sidebarTheme","theme","customView","groupHeader","groupSelectorMode","groupSelectorDialog","addClass","initializeMenus","setupRouteListeners","autoCollapseMobile","setupResponsiveBehavior","onInit","app","getApp","router","currentPath","getCurrentPath","autoSwitchToMenuForRoute","initializeTooltips","searchView","noAppend","showExitButton","headerText","containerId","Collection","GroupList","addChild","on","evt","setActiveGroup","model","hideGroupSearch","showGroupSearch","showGroupSearchDialog","setClass","showSearch","render","hide","onActionShowGroupSearch","onActionSelectGroupParent","activeGroup","Dialog","confirm","showLoading","Group","fetch","hideLoading","searchFields","searchPlaceholder","headerIcon","maxHeight","innerHeight","autoExpandRoot","autoExpandAll","indentSize","body","size","header","noBodyPadding","scrollable","buttons","closeButton","destroy","show","route","menuName","menuConfig","groupKind","menuContainsRoute","_setActiveMenu","clearAllActiveStates","setActiveItemByRoute","emit","config","sidebar","active","child","normalizeRoute","r","decoded","decodeURIComponent","startsWith","targetRoute","itemRoute","routesMatch","activeMenuItem","childRoute","doRoutesMatch","getTemplate","getSearchTemplate","getMenuTemplate","getPartials","getGroupHeader","addMenu","footer","data","getCurrentMenuConfig","setActiveMenu","lastGroupMenu","getGroupMenu","targetMenu","anyGroupMenu","_","kind","_groupKindMatches","Array","isArray","includes","showMenuForGroup","getMenuConfig","updateMenu","updates","menu","assign","removeMenu","delete","remainingMenus","from","onBeforeRender","currentMenu","subData","version","user","activeUser","renderTemplateString","processNavItems","onAfterRender","isCollapsedState","setTimeout","destroyTooltips","setCustomView","view","removeChild","clearCustomView","updateRouteWithGroup","normalizedRoute","substring","separator","map","divider","isDivider","spacer","isSpacer","processedItem","permissions","hasPermission","requiresGroupKind","isLabel","href","page","baseRoute","processedChild","filter","isItemActive","updateActiveItem","handleActionToggleSubmenu","event","element","arrow","querySelector","classList","toggle","handleActionToggleSidebar","toggleSidebar","onActionShowGroupMenu","action","el","onActionDefault","findAndExecuteHandler","handler","getMenuNames","hasMenu","clearMenus","clear","setMenuData","getMenuData","events","onRouteChanged","preferredMenu","sidebarMenu","fallbackMenu","portalContainer","hideAllTooltips","isCurrentlyCollapsed","contains","remove","add","setSidebarState","state","querySelectorAll","link","navText","textContent","tooltipText","setAttribute","bootstrap","Tooltip","getAttribute","customClass","tooltipOptions","placement","container","trigger","delay","fallbackPlacements","trimmedClass","tooltip","_tooltipInstance","addEventListener","addTooltipHideListeners","removeTooltipHideListeners","tooltipInstance","getInstance","dispose","removeAttribute","getSidebarState","setToggleEnabled","enabled","_tooltipScrollHandler","passive","_tooltipRouteHandler","_tooltipBlurHandler","_tooltipEscapeHandler","e","removeEventListener","onBeforeDestroy","checkMobile","isMobile","innerWidth","createDefault","createMinimal","setSidebarTheme","removeClass","collapse","expand","pulseToggle","toggleButton","removePulse","once","addSimpleMenuItem","text","icon","setSimpleMenu","PageHeader","style","showIcon","showDescription","showBreadcrumbs","currentPage","getMinimalTemplate","getBreadcrumbTemplate","getDefaultTemplate","hasPage","title","displayName","pageName","pageIcon","pageDescription","description","headerActions","pageTitle","breadcrumbs","actions","hasActions","setPage","getPage","onHeaderAction","DeniedPage","Page","template","deniedPage","deniedPageOptions","onParams","query","pageOptions","deniedPageName","setDeniedPage","pageInstance","getViewData","currentUser","getCurrentUser","deniedPageInfo","requiredPermissions","username","email","showLogin","handleActionGoBack","preventDefault","history","back","handleActionGoHome","navigateToDefault","handleActionLogin","showPage","navigate","navError","returnUrl","pathname","showInfo","onEnter","setMeta","timestamp","Date","toISOString","showForPage","NotFoundPage","path","setInfo","_element","showForPath","notFoundPage","PortalApp","WebApp","sidebarConfig","topbarConfig","topbar","topnav","showPageHeader","pageHeaderConfig","pageHeader","tokenManager","TokenManager","sidebarCollapsed","defaultCollapsed","loadSidebarState","setupPageContainer","toast","ToastService","registerPage","start","checkAuthStatus","clearTokens","rest","clearAuth","setActiveUser","checkAndRefreshTokens","onPortalAction","bind","checkActiveGroup","setupRouter","isStarted","showPasskeySetup","tokenStatus","checkTokenStatus","token","getTokenInstance","startAutoRefresh","setAuthToken","User","getUserId","resp","success","urlGroupId","groupId","loadActiveGroupId","status","clearActiveGroup","statusText","saveActiveGroupId","member","Member","fetchForGroup","clearActiveGroupId","storedGroupId","fallbackGroup","fallbackError","previousGroup","getCurrentPage","onGroupChange","updateUrl","getActiveGroup","getActiveGroupStorageKey","toString","setPortalProfile","profile","needsGroupSelection","Error","showSidebar","showTopbar","contentMarkup","innerHTML","pageContainer","setupPortalComponents","applySidebarState","setupSidebar","setupTopbar","setupPageHeader","setupPortalEvents","TopNav","brandText","brand","brandRoute","brandIcon","navItems","leftItems","rightItems","displayMode","showSidebarToggle","headerContainer","getElementById","target","closest","ResizeObserver","resizeObserver","handleResponsive","observe","_resizeObserver","_resizeHandler","saveSidebarState","collapsed","mobile","getPortalContainer","hasMobileLayout","updateNavigation","setActivePage","setUser","getActiveUser","getSidebarStorageKey","JSON","stringify","saved","parse","clearSidebarState","changePassword","showForm","fields","type","label","required","strengthMeter","capsLockWarning","passwordUsage","attributes","autocomplete","submitLabel","new_password","confirm_password","save","showProfile","UserProfileView","Promise","resolve","then","index$1","profileView","showDialog","showError","PasskeySetupView","setupView","dialog","bsModal","Modal","centered","disconnect","create","FormPage","recreateFormView","formView","getModel","FormView","autosaveModelField","setModel","mustacheFormatter","formatter","dataFormatter","compiledTemplates","partials","mustache","compile","compiled","renderCompiled","clearCache","cache","getCached","registerFormatter","register","hasPipes","test","processData","pipes","processed","pipeString","value","getValueFromPath","pipe","obj","split","current","isNaN","parseInt","processTemplate","ProfileOverviewSection","hasPhone","hasFullName","first","last","fullName","roleLabel","permissionPeek","perms","permMap","PERMISSIONS","p","c","toUpperCase","slice","remaining","onActionEditName","prompt","defaultValue","display_name","onActionEditTimezone","columns","timezone","submitted","onActionVerifyEmail","POST","err","onActionVerifyPhone","onActionAddPhone","onActionNavigate","Passkey","Model","endpoint","registerBegin","url","message","registerComplete","challenge_id","credential","PasskeyList","ModelClass","PasskeyForms","edit","placeholder","help","SessionItem","ListViewItem","browserName","ud","device_info","user_agent","family","deviceName","dev","device","geo","parts","city","region","Boolean","join","country_name","os","some","m","deviceIcon","DeviceItem","browserInfo","ua","major","osName","ActivityRow","TableRow","logMessage","String","levelText","levelBadgeClass","methodPath","method","AVATAR_COLORS","GroupMemberItem","groupName","initials","w","avatarColor","hash","charCodeAt","abs","roleName","role","manage_group","hasRole","roleBadgeClass","SECTIONS","security","onActionChangePassword","onActionManagePasskeys","models","passkeys","onActionEditPasskey","async","dataset","passkey","showModelForm","onActionDeletePasskey","class","dismiss","_registerPasskey","_base64urlToBytes","base64url","base64","padded","repeat","Uint8Array","atob","beginResp","publicKey","rp","hostname","challenge","excludeCredentials","cred","navigator","credentials","friendlyName","credentialData","rawId","btoa","fromCharCode","response","clientDataJSON","attestationObject","getTransports","transports","completeResp","friendly_name","sessions","listView","ListView","UserDeviceLocationList","defaultQuery","itemClass","emptyMessage","devices","UserDeviceList","activity","tableView","TableView","LogList","uid","hideActivePillNames","sortable","visibility","searchable","filterable","paginated","showAdd","showExport","tableOptions","striped","hover","groups","MemberList","permissionTags","activeSection","sectionView","hasAvatar","graph","contextMenu","ContextMenu","section","SectionClass","onActionChangeAvatar","updateModelImage","field","upload","imageSize","width","height","onActionUpdateEmail","onActionUpdatePhone","onActionCreatePasskey","onActionSkip","onActionDontAsk","checkbox","checked","FRAMEWORK_NAME","PACKAGE_NAME"],"mappings":"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"}
|