web-mojo 2.2.15 → 2.2.16

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.
Files changed (51) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.es.js +9 -9
  3. package/dist/auth.cjs.js +1 -1
  4. package/dist/auth.es.js +1 -1
  5. package/dist/charts.cjs.js +1 -1
  6. package/dist/charts.es.js +3 -3
  7. package/dist/chunks/{ChatView-BJK6SF8T.js → ChatView-BqViL7e-.js} +2 -2
  8. package/dist/chunks/{ChatView-BJK6SF8T.js.map → ChatView-BqViL7e-.js.map} +1 -1
  9. package/dist/chunks/{ChatView-BPVE1u2i.js → ChatView-DMC-gH-t.js} +37 -9
  10. package/dist/chunks/{ChatView-BPVE1u2i.js.map → ChatView-DMC-gH-t.js.map} +1 -1
  11. package/dist/chunks/{Collection-zmb3xHhH.js → Collection-sSP1JF8d.js} +2 -2
  12. package/dist/chunks/{Collection-zmb3xHhH.js.map → Collection-sSP1JF8d.js.map} +1 -1
  13. package/dist/chunks/{ContextMenu-DPjJuxpq.js → ContextMenu-C9qnhdFg.js} +2 -2
  14. package/dist/chunks/{ContextMenu-DPjJuxpq.js.map → ContextMenu-C9qnhdFg.js.map} +1 -1
  15. package/dist/chunks/{Dialog-RzLLLfJD.js → Dialog-DgYEebtR.js} +3 -3
  16. package/dist/chunks/{Dialog-RzLLLfJD.js.map → Dialog-DgYEebtR.js.map} +1 -1
  17. package/dist/chunks/{Dialog--hl_Uh6X.js → Dialog-VyLukswR.js} +2 -2
  18. package/dist/chunks/{Dialog--hl_Uh6X.js.map → Dialog-VyLukswR.js.map} +1 -1
  19. package/dist/chunks/{FormView-C1emfj3B.js → FormView-DCI29KlM.js} +3 -3
  20. package/dist/chunks/FormView-DCI29KlM.js.map +1 -0
  21. package/dist/chunks/{FormView--WuITh01.js → FormView-GO8yahxE.js} +39 -5
  22. package/dist/chunks/FormView-GO8yahxE.js.map +1 -0
  23. package/dist/chunks/{ListView-BBZw7GUS.js → ListView-0fx1T87y.js} +2 -2
  24. package/dist/chunks/{ListView-BBZw7GUS.js.map → ListView-0fx1T87y.js.map} +1 -1
  25. package/dist/chunks/{MetricsMiniChartWidget-DoxqoF1X.js → MetricsMiniChartWidget-JLmDm3sm.js} +2 -2
  26. package/dist/chunks/{MetricsMiniChartWidget-DoxqoF1X.js.map → MetricsMiniChartWidget-JLmDm3sm.js.map} +1 -1
  27. package/dist/chunks/{MetricsMiniChartWidget-DyVs4Wt0.js → MetricsMiniChartWidget-XTU45k_e.js} +2 -2
  28. package/dist/chunks/{MetricsMiniChartWidget-DyVs4Wt0.js.map → MetricsMiniChartWidget-XTU45k_e.js.map} +1 -1
  29. package/dist/chunks/{PDFViewer-CHX2NLkG.js → PDFViewer-DMpN-d6N.js} +2 -2
  30. package/dist/chunks/{PDFViewer-CHX2NLkG.js.map → PDFViewer-DMpN-d6N.js.map} +1 -1
  31. package/dist/chunks/{PDFViewer-BxFcG82d.js → PDFViewer-DbPamhVj.js} +2 -2
  32. package/dist/chunks/{PDFViewer-BxFcG82d.js.map → PDFViewer-DbPamhVj.js.map} +1 -1
  33. package/dist/chunks/{TokenManager-DFWJ-cuN.js → TokenManager-A0zSdQ6i.js} +4 -4
  34. package/dist/chunks/{TokenManager-DFWJ-cuN.js.map → TokenManager-A0zSdQ6i.js.map} +1 -1
  35. package/dist/chunks/{TokenManager-Bi5eDY8Y.js → TokenManager-DH4-kElJ.js} +2 -2
  36. package/dist/chunks/{TokenManager-Bi5eDY8Y.js.map → TokenManager-DH4-kElJ.js.map} +1 -1
  37. package/dist/chunks/{version-BzRzH4mJ.js → version-BpfLBsbA.js} +2 -2
  38. package/dist/chunks/{version-BzRzH4mJ.js.map → version-BpfLBsbA.js.map} +1 -1
  39. package/dist/chunks/{version-S1OYxk1c.js → version-iQsUQBiz.js} +4 -4
  40. package/dist/chunks/{version-S1OYxk1c.js.map → version-iQsUQBiz.js.map} +1 -1
  41. package/dist/docit.cjs.js +1 -1
  42. package/dist/docit.es.js +5 -5
  43. package/dist/index.cjs.js +1 -1
  44. package/dist/index.es.js +11 -11
  45. package/dist/lightbox.cjs.js +1 -1
  46. package/dist/lightbox.es.js +4 -4
  47. package/dist/map.es.js +1 -1
  48. package/dist/timeline.es.js +2 -2
  49. package/package.json +1 -1
  50. package/dist/chunks/FormView--WuITh01.js.map +0 -1
  51. package/dist/chunks/FormView-C1emfj3B.js.map +0 -1
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./Rest-P-KCJpjB.js"),t=require("./Dialog--hl_Uh6X.js"),s=require("./ContextMenu-NNHmt1iq.js");class ResultsView extends e.View{constructor(e={}){super({className:"search-results-view flex-grow-1 overflow-auto d-flex flex-column",template:'\n <div id="results-container" class="flex-grow-1 overflow-auto">\n {{#data.loading}}\n <div class="text-center p-4">\n <div class="spinner-border spinner-border-sm text-muted" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <div class="mt-2 small text-muted">{{data.loadingText}}</div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <div class="simple-search-item position-relative"\n data-action="select-item"\n data-item-index="{{index}}">\n {{{itemContent}}}\n\n </div>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class="text-center p-4">\n <i class="bi bi-search text-muted mb-2" style="font-size: 1.5rem;"></i>\n <div class="text-muted small">{{data.noResultsText}}</div>\n <button type="button"\n class="btn btn-link btn-sm mt-2 p-0"\n data-action="clear-search">\n Clear search\n </button>\n </div>\n {{/data.showNoResults}}\n\n {{#data.showEmpty}}\n <div class="text-center p-4">\n <i class="{{data.emptyIcon}} text-muted mb-2" style="font-size: 2rem;"></i>\n <div class="text-muted small mb-2">{{data.emptyText}}</div>\n {{#data.emptySubtext}}\n <div class="text-muted" style="font-size: 0.75rem;">\n {{data.emptySubtext}}\n </div>\n {{/data.emptySubtext}}\n </div>\n {{/data.showEmpty}}\n {{/data.loading}}\n </div>\n\n {{#data.showResultsCount}}\n <div class="border-top bg-light p-2 text-center">\n <small class="text-muted">\n {{data.filteredCount}} of {{data.totalCount}}\n </small>\n </div>\n {{/data.showResultsCount}}\n ',...e}),this.parentView=e.parentView}async handleActionSelectItem(e,t){e.preventDefault();const s=parseInt(t.getAttribute("data-item-index"));this.parentView&&this.parentView.handleItemSelection(s)}async handleActionClearSearch(e,t){e.preventDefault(),this.parentView&&this.parentView.clearSearch()}async onAfterRender(){if(this.parentView&&this.parentView.maxHeight){const e=this.element.querySelector("#results-container");e&&(e.style.maxHeight=`${this.parentView.maxHeight}px`)}}}class SimpleSearchView extends e.View{constructor(e={}){super({className:"simple-search-view d-flex flex-column",template:'\n <div class="p-3 border-bottom bg-light">\n {{#data.headerText}}\n <div class="d-flex justify-content-between align-items-start mb-3">\n <h6 class="text-muted fw-semibold mb-0">\n {{#data.headerIcon}}<i class="{{data.headerIcon}} me-2"></i>{{/data.headerIcon}}\n {{{data.headerText}}}\n </h6>\n {{#data.showExitButton}}\n <button class="btn btn-link p-0 text-muted simple-search-exit-btn"\n type="button"\n data-action="exit-view"\n title="Exit"\n aria-label="Exit view">\n <i class="bi bi-x-lg" aria-hidden="true"></i>\n </button>\n {{/data.showExitButton}}\n </div>\n {{/data.headerText}}\n <div class="position-relative">\n <input type="text"\n class="form-control form-control-sm pe-5"\n placeholder="{{data.searchPlaceholder}}"\n value="{{data.searchValue}}"\n data-filter="live-search"\n data-filter-debounce="{{data.debounceMs}}"\n data-change-action="search-items">\n <button class="btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn"\n type="button"\n data-action="clear-search"\n title="Clear search"\n aria-label="Clear search">\n <i class="bi bi-x-circle-fill" aria-hidden="true"></i>\n </button>\n </div>\n </div>\n\n <div data-container="results"></div>\n\n {{#data.showFooter}}\n <div class="p-3 border-top bg-light">\n <small class="text-muted">\n <i class="{{data.footerIcon}} me-1"></i>\n {{{data.footerContent}}}\n </small>\n </div>\n {{/data.showFooter}}\n ',...e}),this.Collection=e.Collection,this.collection=e.collection,this.itemTemplate=e.itemTemplate||this.getDefaultItemTemplate(),this.searchFields=e.searchFields||["name"],this.collectionParams={size:25,...e.collectionParams},void 0===e.headerText&&(this.headerText="Select Item"),this.headerText=e.headerText,this.headerIcon=e.headerIcon||"bi bi-list",this.searchPlaceholder=e.searchPlaceholder||"Search...",this.loadingText=e.loadingText||"Loading items...",this.noResultsText=e.noResultsText||"No items match your search",this.emptyText=e.emptyText||"No items available",this.emptySubtext=e.emptySubtext||null,this.emptyIcon=e.emptyIcon||"bi bi-inbox",this.footerContent=e.footerContent||null,this.footerIcon=e.footerIcon||"bi bi-info-circle",this.showExitButton=e.showExitButton||!1,this.searchValue="",this.filteredItems=[],this.loading=!1,this.hasSearched=!1,this.searchTimer=null,this.debounceMs=e.debounceMs||300,e.maxHeight?this.maxHeight=e.maxHeight:this.addClass("h-100"),this.resultsView=new ResultsView({parentView:this}),!this.collection&&this.Collection&&(this.collection=new this.Collection),this.addChild(this.resultsView)}onInit(){this.collection&&this.setupCollection(),this.collection&&!1!==this.options.autoLoad&&this.loadItems()}setupCollection(){Object.assign(this.collection.params,this.collectionParams),this.collection.on("fetch:success",()=>{this.loading=!1,this.updateFilteredItems()}),this.collection.on("fetch:error",()=>{this.loading=!1})}async loadItems(){if(this.collection){this.loading=!0,this.updateResultsView();try{await this.collection.fetch(),this.updateFilteredItems()}catch(e){console.error("Error loading items:",e);const t=this.getApp();t?.showError?.("Failed to load items. Please try again.")}finally{this.loading=!1,this.updateFilteredItems()}}else console.warn("SimpleSearchView: No collection provided")}updateFilteredItems(){this.collection?(this.filteredItems=this.collection.toJSON(),this.updateResultsView()):this.filteredItems=[]}getNestedValue(e,t){return t.split(".").reduce((e,t)=>e?.[t],e)}async getViewData(){return{searchValue:this.searchValue,showFooter:!!this.footerContent,showExitButton:this.showExitButton,debounceMs:this.debounceMs,headerText:this.headerText,headerIcon:this.headerIcon,searchPlaceholder:this.searchPlaceholder,footerContent:this.footerContent,footerIcon:this.footerIcon}}updateResultsView(){if(!this.resultsView)return;const e=this.collection&&this.collection.length()>0,t=this.filteredItems.length>0,s=this.searchValue.length>0,n=this.filteredItems.map((e,t)=>({...e,index:t,itemContent:this.processItemTemplate(e)}));this.resultsView.data={loading:this.loading,items:n,showEmpty:!this.loading&&!e,showNoResults:!this.loading&&e&&!t&&s,showResultsCount:!this.loading&&e,filteredCount:this.filteredItems.length,totalCount:this.collection?.restEnabled?this.collection?.meta?.count||0:this.collection?.length()||0,loadingText:this.loadingText,noResultsText:this.noResultsText,emptyText:this.emptyText,emptySubtext:this.emptySubtext,emptyIcon:this.emptyIcon},this.resultsView.render()}processItemTemplate(e){let t=this.itemTemplate;return t=t.replace(/\{\{(\w+)\}\}/g,(t,s)=>this.getNestedValue(e,s)||""),t}getDefaultItemTemplate(){return'\n <div class="p-3 border-bottom">\n <div class="fw-semibold text-dark">{{name}}</div>\n <small class="text-muted">{{id}}</small>\n </div>\n '}async onPassThruActionSearchItems(e,t){const s=t.value||"";this.searchValue=s,this.hasSearched=!0,this.searchTimer&&clearTimeout(this.searchTimer),this.performSearch()}async performSearch(){const e={...this.collectionParams};this.searchValue&&this.searchValue.length>1&&(e.search=this.searchValue.trim()),this.collection.setParams(e,!0)}handleItemSelection(e){if(isNaN(e)||e<0||e>=this.filteredItems.length)return void console.error("Invalid item index:",e);const t=this.filteredItems[e];let s=this.collection?this.collection.get(t.id):null;if(!s){s=new this.collection.ModelClass({id:t.id});const n=this.getApp();return n.showLoading(),void s.fetch().then(()=>{n.hideLoading(),this.emit("item:selected",{item:t,model:s,index:e})})}this.emit("item:selected",{item:t,model:s,index:e})}setCollection(e){return this.collection=e,this.setupCollection(),this}setItemTemplate(e){return this.itemTemplate=e,this.updateResultsView(),this}setSearchFields(e){return this.searchFields=Array.isArray(e)?e:[e],this}async refresh(){await this.loadItems()}focusSearch(){const e=this.element?.querySelector('input[data-action="search-items"]');e&&e.focus()}async handleActionExitView(e,t){this.emit("exit",{view:this})}async handleActionClearSearch(e,t){this.clearSearch()}clearSearch(){this.searchValue="",this.hasSearched=!1;const e=this.element?.querySelector('input[data-change-action="search-items"]');e&&(e.value="",e.focus()),this.performSearch()}getItemCount(){return this.collection?this.collection.length():0}getFilteredItemCount(){return this.filteredItems.length}hasItems(){return this.getItemCount()>0}getSearchValue(){return this.searchValue}setSearchValue(e){this.searchValue=e||"",this.hasSearched=!!this.searchValue;const t=this.element?.querySelector('input[data-action="search-items"]');return t&&(t.value=this.searchValue),this.performSearch(),this}async onAfterRender(){if(await super.onAfterRender(),this.resultsView&&!this.resultsView.isMounted()){const e=this.element?.querySelector('[data-container="results"]');e&&await this.resultsView.render(!0,e)}this.updateResultsView()}async onBeforeDestroy(){this.searchTimer&&clearTimeout(this.searchTimer),this.collection&&this.collection.off("update"),await super.onBeforeDestroy()}}class GroupSelectorButton extends e.View{constructor(e={}){super({tagName:"div",className:"nav-item",...e});const t=this.getApp();this.Collection=e.Collection||t?.GroupCollection||s.GroupList,this.collection=e.collection||new this.Collection,this.currentGroup=void 0!==e.currentGroup?e.currentGroup:t?.activeGroup,this.buttonClass=e.buttonClass||"btn btn-link nav-link",this.buttonIcon=e.buttonIcon||"bi-building",this.defaultText=e.defaultText||"Select Group",this.itemTemplate=e.itemTemplate,this.searchFields=e.searchFields||["name"],this.headerText=e.headerText||"Select Group",this.searchPlaceholder=e.searchPlaceholder||"Search groups...",this.autoSetActiveGroup=!1!==e.autoSetActiveGroup,this.onGroupSelected=e.onGroupSelected,this.dialog=null,t?.events&&t.events.on("group:changed",e=>{e.group!==this.currentGroup&&this.setCurrentGroup(e.group)})}async getTemplate(){return'\n <button class="{{buttonClass}}" \n data-action="show-selector"\n type="button"\n style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">\n <i class="{{buttonIcon}} me-1"></i>\n <span class="group-name">{{displayName}}</span>\n </button>\n '}async onBeforeRender(){await super.onBeforeRender(),this.currentGroup?.get?.("name")||this.currentGroup,this.buttonClass=this.buttonClass,this.buttonIcon=this.buttonIcon,this.displayName=this.currentGroup?.get?.("name")||this.currentGroup?.name||this.defaultText}async onActionShowSelector(e){const s=new SimpleSearchView({Collection:this.Collection,collection:this.collection,itemTemplate:this.itemTemplate||this.getDefaultItemTemplate(),searchFields:this.searchFields,headerText:this.headerText,searchPlaceholder:this.searchPlaceholder,headerIcon:this.buttonIcon,showExitButton:!1});return this.dialog=new t.Dialog({title:this.headerText,body:s,size:"md",scrollable:!0,noBodyPadding:!0,buttons:[],closeButton:!0}),s.on("item:selected",e=>{this.handleGroupSelection(e.model||e.item),this.dialog&&this.dialog.hide()}),this.dialog.on("hidden",()=>{this.dialog.destroy(),this.dialog=null}),await this.dialog.render(!0,document.body),this.dialog.show(),!0}handleGroupSelection(e){this.currentGroup=e,this.displayName=e?.get?.("name")||e?.name||this.defaultText,this.render();const t=this.getApp();this.autoSetActiveGroup&&t?.setActiveGroup&&t.setActiveGroup(e),this.onGroupSelected&&this.onGroupSelected({group:e}),this.emit("group-selected",{group:e}),t?.events&&(t.events.emit("group:selected",{group:e}),t.events.emit("group:changed",{group:e}))}getDefaultItemTemplate(){return'\n <div class="d-flex align-items-center p-3 border-bottom">\n <div class="flex-grow-1">\n <div class="fw-semibold text-dark">{{name}}</div>\n <small class="text-muted">#{{id}} {{kind}}</small>\n </div>\n </div>\n '}setCurrentGroup(e){this.currentGroup=e,this.displayName=e?.get?.("name")||e?.name||this.defaultText,this.mounted&&this.render()}getCurrentGroup(){return this.currentGroup}}class TopNav extends e.View{constructor(e={}){const t={light:"navbar navbar-expand-lg navbar-light topnav-light",dark:"navbar navbar-expand-lg navbar-dark topnav-dark",clean:"navbar navbar-expand-lg navbar-light topnav-clean",gradient:"navbar navbar-expand-lg navbar-dark topnav-gradient"};let s=t[e.theme||"light"]||t.light;e.shadow&&(s+=` topnav-shadow-${e.shadow}`),super({tagName:"nav",className:s,enableTooltips:!0,style:"position: relative; z-index: 1030;",...e}),this.displayMode=e.displayMode||"both",this.showPageIcon=!1!==e.showPageIcon,this.showPageDescription=e.showPageDescription||!1,this.showBreadcrumbs=e.showBreadcrumbs||!1,this.groupIcon=e.groupIcon||"bi-building",this.currentPage=null,this.previousPage=null,this.config={brand:e.brand||"MOJO App",brandIcon:e.brandIcon||"bi bi-play-circle",brandRoute:e.brandRoute||"/",navItems:e.navItems||[],rightItems:e.rightItems||[],showSidebarToggle:e.showSidebarToggle||!1,sidebarToggleAction:e.sidebarToggleAction||"toggle-sidebar",...e},this.userMenu=e.userMenu||this.findMenuItem("user"),this.userMenu&&(this.userMenu.id="user"),this.loginMenu=e.loginMenu||this.findMenuItem("login"),this.setupPageListeners(),this.setupGroupListeners(),this.groupSelectorButton=null,this.currentGroup=null}findMenuItem(e){let t=this.config.navItems.find(t=>t.id===e);return t||(t=this.config.rightItems.find(t=>t.id===e)),t||null}replaceMenuItem(e,t){const s=this.config.navItems.findIndex(t=>t.id===e);if(-1!==s)return this.config.navItems[s]=t,!0;const n=this.config.rightItems.findIndex(t=>t.id===e);return-1!==n&&(this.config.rightItems[n]=t,!0)}setBrand(e,t=null){this.config.brand=e,this.config.brandIcon=t||this.config.brandIcon,this.render()}setUser(e){e?(this.userMenu.label=e.get("display_name"),this.replaceMenuItem("login",this.userMenu)):this.replaceMenuItem("user",this.loginMenu),this.setModel(e)}_onModelChange(){this.model&&(this.userMenu.label=this.model.get("display_name")),this.isMounted()&&this.render()}async getTemplate(){return'\n <div class="container-fluid">\n {{#data.showSidebarToggle}}\n <button class="topnav-sidebar-toggle me-2" data-action="{{data.sidebarToggleAction}}" aria-label="Toggle Sidebar">\n <i class="bi bi-chevron-right toggle-chevron"></i>\n </button>\n {{/data.showSidebarToggle}}\n\n {{#data.showGroupInfo}}\n <div class="navbar-brand d-flex align-items-center">\n {{#data.groupIcon}}<i class="{{data.groupIcon}} me-2"></i>{{/data.groupIcon}}\n <div>\n <span class="topnav-group-name"\n role="button"\n tabindex="0"\n data-action="open-group-selector"\n style="cursor: pointer;">\n {{data.currentGroupName}}\n </span>\n {{#data.showPageTitle}}\n <span class="text-muted mx-2">|</span>\n <span>{{data.currentPageName}}</span>\n {{/data.showPageTitle}}\n </div>\n </div>\n {{/data.showGroupInfo}}\n\n {{#data.showPageInfo}}\n <div class="navbar-brand d-flex align-items-center">\n {{#data.currentPageIcon}}<i class="{{data.currentPageIcon}} me-2"></i>{{/data.currentPageIcon}}\n <div>\n <span>{{data.currentPageName}}</span>\n {{#data.currentPageDescription}}\n <small class="d-block" style="font-size: 0.75rem; line-height: 1;">{{data.currentPageDescription}}</small>\n {{/data.currentPageDescription}}\n </div>\n </div>\n {{/data.showPageInfo}}\n\n {{#data.showBrand}}\n <a class="navbar-brand" href="{{data.brandRoute}}">\n {{#data.brandIcon}}<i class="{{data.brandIcon}} me-2"></i>{{/data.brandIcon}}\n {{data.brand}}\n </a>\n {{/data.showBrand}}\n\n <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#{{data.navbarId}}">\n <span class="navbar-toggler-icon"></span>\n </button>\n\n <div class="collapse navbar-collapse" id="{{data.navbarId}}">\n {{#data.showNavItems}}\n <ul class="navbar-nav me-auto mb-2 mb-lg-0">\n {{#data.navItems}}\n <li class="nav-item">\n <a class="nav-link {{#active}}active{{/active}}" href="{{route}}" {{#tooltip}}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{tooltip}}"{{/tooltip}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{text}}\n </a>\n </li>\n {{/data.navItems}}\n </ul>\n {{/data.showNavItems}}\n\n {{#data.hasRightItems}}\n <div class="navbar-nav ms-auto">\n {{#data.rightItems}}\n {{#isGroupSelector}}\n <div data-container="group-selector-{{id}}"></div>\n {{/isGroupSelector}}\n {{^isGroupSelector}}\n {{#isDropdown}}\n <div class="nav-item dropdown">\n <a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </a>\n <ul class="dropdown-menu dropdown-menu-end">\n {{#items}}\n {{#divider}}\n <li><hr class="dropdown-divider"></li>\n {{/divider}}\n {{^divider}}\n <li>\n <a class="dropdown-item" role="button" {{#action}}data-action="{{action}}"{{/action}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </a>\n </li>\n {{/divider}}\n {{/items}}\n </ul>\n </div>\n {{/isDropdown}}\n {{^isDropdown}}\n {{#isButton}}\n <button class="{{buttonClass}}" data-action="{{action}}" data-id="{{id}}" {{#tooltip}}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{tooltip}}"{{/tooltip}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </button>\n {{/isButton}}\n {{^isButton}}\n <a class="nav-link" href="{{href}}" {{#action}}data-action="{{action}}"{{/action}} {{#tooltip}}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{tooltip}}"{{/tooltip}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </a>\n {{/isButton}}\n {{/isDropdown}}\n {{/isGroupSelector}}\n {{/data.rightItems}}\n </div>\n {{/data.hasRightItems}}\n </div>\n </div>\n '}async onBeforeRender(){await super.onBeforeRender();const e=this.getApp(),t=this.currentGroup||e?.activeGroup,s="group"===this.displayMode||"group_page_titles"===this.displayMode,n="group_page_titles"===this.displayMode,i="page"===this.displayMode||"both"===this.displayMode,a=!s&&!i,o="menu"===this.displayMode||"both"===this.displayMode,r=this.filterItemsByPermissions(this.config.navItems||[]),l=this.processRightItems(this.config.rightItems||[]);this.data={brand:this.config.brand,brandIcon:this.config.brandIcon,brandRoute:this.config.brandRoute,showBrand:a,navbarId:`navbar-${this.id}`,navItems:r,showNavItems:o,rightItems:l,hasRightItems:l.length>0,showGroupInfo:s,showPageTitle:n,currentGroupName:t?.get?.("name")||t?.name||"Select Group",groupIcon:this.groupIcon,showPageInfo:i,currentPageName:this.currentPage?.title||this.currentPage?.name||"",currentPageIcon:this.currentPage?.icon||this.currentPage?.pageIcon||"",currentPageDescription:this.showPageDescription?this.currentPage?.description:"",showSidebarToggle:this.config.showSidebarToggle,sidebarToggleAction:this.config.sidebarToggleAction,displayMode:this.displayMode}}processRightItems(e){return this.filterItemsByPermissions(e).map(e=>{const t={...e};if(e.items&&(t.items=this.filterItemsByPermissions(e.items)),"group-selector"===e.type){t.isGroupSelector=!0,t.isDropdown=!1,t.isButton=!1;const s={containerId:`group-selector-${e.id||"default"}`};void 0!==e.Collection&&(s.Collection=e.Collection),void 0!==e.collection&&(s.collection=e.collection),void 0!==e.currentGroup&&(s.currentGroup=e.currentGroup),void 0!==e.buttonClass&&(s.buttonClass=e.buttonClass),void 0!==e.buttonIcon&&(s.buttonIcon=e.buttonIcon),void 0!==e.defaultText&&(s.defaultText=e.defaultText),void 0!==e.itemTemplate&&(s.itemTemplate=e.itemTemplate),void 0!==e.searchFields&&(s.searchFields=e.searchFields),void 0!==e.headerText&&(s.headerText=e.headerText),void 0!==e.searchPlaceholder&&(s.searchPlaceholder=e.searchPlaceholder),void 0!==e.autoSetActiveGroup&&(s.autoSetActiveGroup=e.autoSetActiveGroup),void 0!==e.onGroupSelected&&(s.onGroupSelected=e.onGroupSelected);const n=new GroupSelectorButton(s);this.groupSelectorButton=n,this.addChild(n)}else t.items&&t.items.length>0?(t.isDropdown=!0,t.isButton=!1):e.buttonClass?(t.isButton=!0,t.isDropdown=!1):(t.isButton=!1,t.isDropdown=!1);return e.handler&&(this.rightItemHandlers=this.rightItemHandlers||/* @__PURE__ */new Map,this.rightItemHandlers.set(e.id,e.handler)),t})}setupPageListeners(){this.getApp().events.on("page:show",e=>{this.onPageChanged(e)})}setupGroupListeners(){const e=this.getApp();e?.events&&e.events.on(["group:changed","group:loaded"],e=>{e?.group&&(this.currentGroup=e.group),"group"!==this.displayMode&&"group_page_titles"!==this.displayMode||this.mounted&&this.render()})}onPageBeforeChange(e){"page"===this.displayMode||this.displayMode}onPageChanged(e){this.previousPage=this.currentPage,this.currentPage=e.page,"page"!==this.displayMode&&"both"!==this.displayMode||this.updatePageDisplay(),"menu"!==this.displayMode&&"both"!==this.displayMode||this.currentPage&&this.currentPage.route&&this.updateActiveItem(this.currentPage.route)}updatePageDisplay(){this.currentPage&&this.mounted&&this.render()}updateActiveItem(e){const t=e=>e?e.startsWith("/")?e:`/${e}`:"/",s=t(e),n=this.data.navItems.map(e=>{const n=t(e.route);let i=!1;return"/"===n&&"/"===s?i=!0:"/"!==n&&"/"!==s&&(i=s.startsWith(n)||s===n),{...e,active:i}});this.updateData({navItems:n},!0)}onPassThruActionProfile(){this.getApp().events.emit("portal:action",{action:"profile"})}onActionSettings(){this.getApp().events.emit("portal:action",{action:"settings"})}onActionLogout(){this.getApp().events.emit("auth:logout",{action:"logout"})}async onActionOpenGroupSelector(e){if(this.groupSelectorButton)return await this.groupSelectorButton.onActionShowSelector(e),!0;const{GroupList:t}=await Promise.resolve().then(()=>require("./ContextMenu-NNHmt1iq.js")).then(e=>e.Group$1),s=new GroupSelectorButton({Collection:t,currentGroup:this.getApp()?.activeGroup});return await s.onActionShowSelector(e),!0}async handleAction(e,t,s){const n=s.getAttribute("data-id");if(n&&this.rightItemHandlers&&this.rightItemHandlers.has(n)){const i=this.rightItemHandlers.get(n);if("function"==typeof i)return await i.call(this,e,t,s)}const i=`onAction${e.charAt(0).toUpperCase()+e.slice(1).replace(/-([a-z])/g,e=>e[1].toUpperCase())}`;if("function"==typeof this[i])return await this[i](t,s);this.emit("action",{action:e,event:t,element:s,topnav:this})}async onActionDefault(e,t,s){if(this.config.navItems)for(const n of this.config.navItems)if(n.action===e&&n.handler)return await n.handler.call(this,e,t,s),!0;if(this.config.rightItems)for(const n of this.config.rightItems){if(n.action===e&&n.handler)return await n.handler.call(this,e,t,s),!0;if(n.items)for(const i of n.items)if(i.action===e&&i.handler)return await i.handler.call(this,e,t,s),!0}return this.getApp().events.emit("portal:action",{action:e,event:t,el:s}),!1}filterItemsByPermissions(e){if(!e)return[];const t=this.getApp(),s=t?.activeUser;return e.filter(e=>!e.permissions||!s||s.hasPermission(e.permissions))}}class Token{constructor(e){this.token=e,this.payload=null,this.uid=null,this.email=null,this.name=null,this.exp=null,this.iat=null,this.isValidToken=!1,this._decode()}_decode(){if(this.token&&"string"==typeof this.token)try{const e=this.token.split(".");if(3!==e.length)return;let t=e[1].replace(/-/g,"+").replace(/_/g,"/");const s=4-t.length%4;4!==s&&(t+="=".repeat(s));const n=atob(t);this.payload=JSON.parse(n),this.uid=this.payload.uid||this.payload.sub||this.payload.user_id||null,this.email=this.payload.email||null,this.name=this.payload.name||this.payload.username||null,this.exp=this.payload.exp?new Date(1e3*this.payload.exp):null,this.iat=this.payload.iat?new Date(1e3*this.payload.iat):null,this.isValidToken=this._checkValidity()}catch(e){this.payload=null}}_checkValidity(){return!(!this.token||!this.payload)&&(!this.payload.exp||Math.floor(Date.now()/1e3)<this.payload.exp)}decode(){return this.payload}getUserId(){return this.uid}isValid(){return this.isValidToken}isExpiringSoon(e=5){if(!this.payload?.exp)return!1;const t=Math.floor(Date.now()/1e3),s=60*e;return this.payload.exp-t<=s}isExpired(){return!!this.payload?.exp&&Math.floor(Date.now()/1e3)>=this.payload.exp}getAgeMinutes(){if(!this.payload?.iat)return null;const e=Math.floor(Date.now()/1e3)-this.payload.iat;return Math.floor(e/60)}getAuthHeader(){return this.token?`Bearer ${this.token}`:null}getUserInfo(){return this.payload?{uid:this.uid,email:this.email,name:this.name,exp:this.exp,iat:this.iat}:null}}exports.SimpleSearchView=SimpleSearchView,exports.TokenManager=class{constructor(){this.tokenKey="access_token",this.refreshTokenKey="refresh_token",this.tokenInstance=null}setTokens(e,t=null,s=!0){const n=s?localStorage:sessionStorage;this.tokenInstance=new Token(e),e&&n.setItem(this.tokenKey,e),t&&n.setItem(this.refreshTokenKey,t)}getToken(){return localStorage.getItem(this.tokenKey)||sessionStorage.getItem(this.tokenKey)}getRefreshToken(){return localStorage.getItem(this.refreshTokenKey)||sessionStorage.getItem(this.refreshTokenKey)}clearTokens(){localStorage.removeItem(this.tokenKey),localStorage.removeItem(this.refreshTokenKey),sessionStorage.removeItem(this.tokenKey),sessionStorage.removeItem(this.refreshTokenKey)}getTokenInstance(){const e=this.getToken();return e?(this.tokenInstance&&this.tokenInstance.token===e||(this.tokenInstance=new Token(e)),this.tokenInstance):(this.tokenInstance=null,null)}getRefreshTokenInstance(){const e=this.getRefreshToken();return e?(this._refreshTokenInstance&&this._refreshTokenInstance.token===e||(this._refreshTokenInstance=new Token(e)),this._refreshTokenInstance):(this._refreshTokenInstance=null,null)}decode(e=null){const t=e||this.getToken();return new Token(t).decode()}getUserId(){const e=this.getTokenInstance();return e?e.getUserId():null}isValid(){const e=this.getTokenInstance();return!!e&&e.isValid()}isExpiringSoon(e=5){const t=this.getTokenInstance();return!!t&&t.isExpiringSoon(e)}getAuthHeader(){const e=this.getTokenInstance();return e?e.getAuthHeader():null}getUserInfo(){const e=this.getTokenInstance();return e?e.getUserInfo():null}checkTokenStatus(){const e=this.getTokenInstance(),t=this.getRefreshTokenInstance();return e&&e.isValid()&&!e.isExpired()?e.isExpiringSoon(10)||e.getAgeMinutes()&&e.getAgeMinutes()>60?t&&t.isValid()&&!t.isExpired()?{action:"refresh",reason:"Access token expiring soon or aged"}:{action:"none",reason:"Access token expiring but refresh token invalid"}:{action:"none",reason:"All tokens valid and not expiring soon"}:t&&t.isValid()&&!t.isExpired()?{action:"refresh",reason:"Access token invalid/expired but refresh token valid"}:{action:"logout",reason:"Both access and refresh tokens are invalid/expired"}}async checkAndRefreshTokens(e){switch(this.checkTokenStatus().action){case"logout":return e.events.emit("auth:unauthorized"),this.stopAutoRefresh(),!0;case"refresh":return await this.refreshToken(e),!0;default:return!1}}startAutoRefresh(e){this.stopAutoRefresh(),this._tokenWatcher=setInterval(()=>{this.checkAndRefreshTokens(e)},6e4)}stopAutoRefresh(){this._tokenWatcher&&(clearInterval(this._tokenWatcher),this._tokenWatcher=null)}async refreshToken(e){const t=this.getRefreshTokenInstance();if(!t||!t.isValid()||t.isExpired())return e.events.emit("auth:unauthorized"),void this.stopAutoRefresh();try{const s=await e.rest.POST("/api/token/refresh",{refresh_token:t.token}),{access_token:n,refresh_token:i}=s.data.data;this.tokenInstance=null,this._refreshTokenInstance=null,this.setTokens(n,i),e.rest.setAuthToken(n),e.events.emit("auth:token:refreshed",{newToken:n,newRefreshToken:i})}catch(s){401===s.status||403===s.status?(e.events.emit("auth:unauthorized"),this.stopAutoRefresh()):e.events.emit("auth:token:refresh:failed",{error:s})}}},exports.TopNav=TopNav;
2
- //# sourceMappingURL=TokenManager-Bi5eDY8Y.js.map
1
+ "use strict";const e=require("./Rest-P-KCJpjB.js"),t=require("./Dialog-VyLukswR.js"),s=require("./ContextMenu-NNHmt1iq.js");class ResultsView extends e.View{constructor(e={}){super({className:"search-results-view flex-grow-1 overflow-auto d-flex flex-column",template:'\n <div id="results-container" class="flex-grow-1 overflow-auto">\n {{#data.loading}}\n <div class="text-center p-4">\n <div class="spinner-border spinner-border-sm text-muted" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <div class="mt-2 small text-muted">{{data.loadingText}}</div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <div class="simple-search-item position-relative"\n data-action="select-item"\n data-item-index="{{index}}">\n {{{itemContent}}}\n\n </div>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class="text-center p-4">\n <i class="bi bi-search text-muted mb-2" style="font-size: 1.5rem;"></i>\n <div class="text-muted small">{{data.noResultsText}}</div>\n <button type="button"\n class="btn btn-link btn-sm mt-2 p-0"\n data-action="clear-search">\n Clear search\n </button>\n </div>\n {{/data.showNoResults}}\n\n {{#data.showEmpty}}\n <div class="text-center p-4">\n <i class="{{data.emptyIcon}} text-muted mb-2" style="font-size: 2rem;"></i>\n <div class="text-muted small mb-2">{{data.emptyText}}</div>\n {{#data.emptySubtext}}\n <div class="text-muted" style="font-size: 0.75rem;">\n {{data.emptySubtext}}\n </div>\n {{/data.emptySubtext}}\n </div>\n {{/data.showEmpty}}\n {{/data.loading}}\n </div>\n\n {{#data.showResultsCount}}\n <div class="border-top bg-light p-2 text-center">\n <small class="text-muted">\n {{data.filteredCount}} of {{data.totalCount}}\n </small>\n </div>\n {{/data.showResultsCount}}\n ',...e}),this.parentView=e.parentView}async handleActionSelectItem(e,t){e.preventDefault();const s=parseInt(t.getAttribute("data-item-index"));this.parentView&&this.parentView.handleItemSelection(s)}async handleActionClearSearch(e,t){e.preventDefault(),this.parentView&&this.parentView.clearSearch()}async onAfterRender(){if(this.parentView&&this.parentView.maxHeight){const e=this.element.querySelector("#results-container");e&&(e.style.maxHeight=`${this.parentView.maxHeight}px`)}}}class SimpleSearchView extends e.View{constructor(e={}){super({className:"simple-search-view d-flex flex-column",template:'\n <div class="p-3 border-bottom bg-light">\n {{#data.headerText}}\n <div class="d-flex justify-content-between align-items-start mb-3">\n <h6 class="text-muted fw-semibold mb-0">\n {{#data.headerIcon}}<i class="{{data.headerIcon}} me-2"></i>{{/data.headerIcon}}\n {{{data.headerText}}}\n </h6>\n {{#data.showExitButton}}\n <button class="btn btn-link p-0 text-muted simple-search-exit-btn"\n type="button"\n data-action="exit-view"\n title="Exit"\n aria-label="Exit view">\n <i class="bi bi-x-lg" aria-hidden="true"></i>\n </button>\n {{/data.showExitButton}}\n </div>\n {{/data.headerText}}\n <div class="position-relative">\n <input type="text"\n class="form-control form-control-sm pe-5"\n placeholder="{{data.searchPlaceholder}}"\n value="{{data.searchValue}}"\n data-filter="live-search"\n data-filter-debounce="{{data.debounceMs}}"\n data-change-action="search-items">\n <button class="btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn"\n type="button"\n data-action="clear-search"\n title="Clear search"\n aria-label="Clear search">\n <i class="bi bi-x-circle-fill" aria-hidden="true"></i>\n </button>\n </div>\n </div>\n\n <div data-container="results"></div>\n\n {{#data.showFooter}}\n <div class="p-3 border-top bg-light">\n <small class="text-muted">\n <i class="{{data.footerIcon}} me-1"></i>\n {{{data.footerContent}}}\n </small>\n </div>\n {{/data.showFooter}}\n ',...e}),this.Collection=e.Collection,this.collection=e.collection,this.itemTemplate=e.itemTemplate||this.getDefaultItemTemplate(),this.searchFields=e.searchFields||["name"],this.collectionParams={size:25,...e.collectionParams},void 0===e.headerText&&(this.headerText="Select Item"),this.headerText=e.headerText,this.headerIcon=e.headerIcon||"bi bi-list",this.searchPlaceholder=e.searchPlaceholder||"Search...",this.loadingText=e.loadingText||"Loading items...",this.noResultsText=e.noResultsText||"No items match your search",this.emptyText=e.emptyText||"No items available",this.emptySubtext=e.emptySubtext||null,this.emptyIcon=e.emptyIcon||"bi bi-inbox",this.footerContent=e.footerContent||null,this.footerIcon=e.footerIcon||"bi bi-info-circle",this.showExitButton=e.showExitButton||!1,this.searchValue="",this.filteredItems=[],this.loading=!1,this.hasSearched=!1,this.searchTimer=null,this.debounceMs=e.debounceMs||300,e.maxHeight?this.maxHeight=e.maxHeight:this.addClass("h-100"),this.resultsView=new ResultsView({parentView:this}),!this.collection&&this.Collection&&(this.collection=new this.Collection),this.addChild(this.resultsView)}onInit(){this.collection&&this.setupCollection(),this.collection&&!1!==this.options.autoLoad&&this.loadItems()}setupCollection(){Object.assign(this.collection.params,this.collectionParams),this.collection.on("fetch:success",()=>{this.loading=!1,this.updateFilteredItems()}),this.collection.on("fetch:error",()=>{this.loading=!1})}async loadItems(){if(this.collection){this.loading=!0,this.updateResultsView();try{await this.collection.fetch(),this.updateFilteredItems()}catch(e){console.error("Error loading items:",e);const t=this.getApp();t?.showError?.("Failed to load items. Please try again.")}finally{this.loading=!1,this.updateFilteredItems()}}else console.warn("SimpleSearchView: No collection provided")}updateFilteredItems(){this.collection?(this.filteredItems=this.collection.toJSON(),this.updateResultsView()):this.filteredItems=[]}getNestedValue(e,t){return t.split(".").reduce((e,t)=>e?.[t],e)}async getViewData(){return{searchValue:this.searchValue,showFooter:!!this.footerContent,showExitButton:this.showExitButton,debounceMs:this.debounceMs,headerText:this.headerText,headerIcon:this.headerIcon,searchPlaceholder:this.searchPlaceholder,footerContent:this.footerContent,footerIcon:this.footerIcon}}updateResultsView(){if(!this.resultsView)return;const e=this.collection&&this.collection.length()>0,t=this.filteredItems.length>0,s=this.searchValue.length>0,n=this.filteredItems.map((e,t)=>({...e,index:t,itemContent:this.processItemTemplate(e)}));this.resultsView.data={loading:this.loading,items:n,showEmpty:!this.loading&&!e,showNoResults:!this.loading&&e&&!t&&s,showResultsCount:!this.loading&&e,filteredCount:this.filteredItems.length,totalCount:this.collection?.restEnabled?this.collection?.meta?.count||0:this.collection?.length()||0,loadingText:this.loadingText,noResultsText:this.noResultsText,emptyText:this.emptyText,emptySubtext:this.emptySubtext,emptyIcon:this.emptyIcon},this.resultsView.render()}processItemTemplate(e){let t=this.itemTemplate;return t=t.replace(/\{\{(\w+)\}\}/g,(t,s)=>this.getNestedValue(e,s)||""),t}getDefaultItemTemplate(){return'\n <div class="p-3 border-bottom">\n <div class="fw-semibold text-dark">{{name}}</div>\n <small class="text-muted">{{id}}</small>\n </div>\n '}async onPassThruActionSearchItems(e,t){const s=t.value||"";this.searchValue=s,this.hasSearched=!0,this.searchTimer&&clearTimeout(this.searchTimer),this.performSearch()}async performSearch(){const e={...this.collectionParams};this.searchValue&&this.searchValue.length>1&&(e.search=this.searchValue.trim()),this.collection.setParams(e,!0)}handleItemSelection(e){if(isNaN(e)||e<0||e>=this.filteredItems.length)return void console.error("Invalid item index:",e);const t=this.filteredItems[e];let s=this.collection?this.collection.get(t.id):null;if(!s){s=new this.collection.ModelClass({id:t.id});const n=this.getApp();return n.showLoading(),void s.fetch().then(()=>{n.hideLoading(),this.emit("item:selected",{item:t,model:s,index:e})})}this.emit("item:selected",{item:t,model:s,index:e})}setCollection(e){return this.collection=e,this.setupCollection(),this}setItemTemplate(e){return this.itemTemplate=e,this.updateResultsView(),this}setSearchFields(e){return this.searchFields=Array.isArray(e)?e:[e],this}async refresh(){await this.loadItems()}focusSearch(){const e=this.element?.querySelector('input[data-action="search-items"]');e&&e.focus()}async handleActionExitView(e,t){this.emit("exit",{view:this})}async handleActionClearSearch(e,t){this.clearSearch()}clearSearch(){this.searchValue="",this.hasSearched=!1;const e=this.element?.querySelector('input[data-change-action="search-items"]');e&&(e.value="",e.focus()),this.performSearch()}getItemCount(){return this.collection?this.collection.length():0}getFilteredItemCount(){return this.filteredItems.length}hasItems(){return this.getItemCount()>0}getSearchValue(){return this.searchValue}setSearchValue(e){this.searchValue=e||"",this.hasSearched=!!this.searchValue;const t=this.element?.querySelector('input[data-action="search-items"]');return t&&(t.value=this.searchValue),this.performSearch(),this}async onAfterRender(){if(await super.onAfterRender(),this.resultsView&&!this.resultsView.isMounted()){const e=this.element?.querySelector('[data-container="results"]');e&&await this.resultsView.render(!0,e)}this.updateResultsView()}async onBeforeDestroy(){this.searchTimer&&clearTimeout(this.searchTimer),this.collection&&this.collection.off("update"),await super.onBeforeDestroy()}}class GroupSelectorButton extends e.View{constructor(e={}){super({tagName:"div",className:"nav-item",...e});const t=this.getApp();this.Collection=e.Collection||t?.GroupCollection||s.GroupList,this.collection=e.collection||new this.Collection,this.currentGroup=void 0!==e.currentGroup?e.currentGroup:t?.activeGroup,this.buttonClass=e.buttonClass||"btn btn-link nav-link",this.buttonIcon=e.buttonIcon||"bi-building",this.defaultText=e.defaultText||"Select Group",this.itemTemplate=e.itemTemplate,this.searchFields=e.searchFields||["name"],this.headerText=e.headerText||"Select Group",this.searchPlaceholder=e.searchPlaceholder||"Search groups...",this.autoSetActiveGroup=!1!==e.autoSetActiveGroup,this.onGroupSelected=e.onGroupSelected,this.dialog=null,t?.events&&t.events.on("group:changed",e=>{e.group!==this.currentGroup&&this.setCurrentGroup(e.group)})}async getTemplate(){return'\n <button class="{{buttonClass}}" \n data-action="show-selector"\n type="button"\n style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">\n <i class="{{buttonIcon}} me-1"></i>\n <span class="group-name">{{displayName}}</span>\n </button>\n '}async onBeforeRender(){await super.onBeforeRender(),this.currentGroup?.get?.("name")||this.currentGroup,this.buttonClass=this.buttonClass,this.buttonIcon=this.buttonIcon,this.displayName=this.currentGroup?.get?.("name")||this.currentGroup?.name||this.defaultText}async onActionShowSelector(e){const s=new SimpleSearchView({Collection:this.Collection,collection:this.collection,itemTemplate:this.itemTemplate||this.getDefaultItemTemplate(),searchFields:this.searchFields,headerText:this.headerText,searchPlaceholder:this.searchPlaceholder,headerIcon:this.buttonIcon,showExitButton:!1});return this.dialog=new t.Dialog({title:this.headerText,body:s,size:"md",scrollable:!0,noBodyPadding:!0,buttons:[],closeButton:!0}),s.on("item:selected",e=>{this.handleGroupSelection(e.model||e.item),this.dialog&&this.dialog.hide()}),this.dialog.on("hidden",()=>{this.dialog.destroy(),this.dialog=null}),await this.dialog.render(!0,document.body),this.dialog.show(),!0}handleGroupSelection(e){this.currentGroup=e,this.displayName=e?.get?.("name")||e?.name||this.defaultText,this.render();const t=this.getApp();this.autoSetActiveGroup&&t?.setActiveGroup&&t.setActiveGroup(e),this.onGroupSelected&&this.onGroupSelected({group:e}),this.emit("group-selected",{group:e}),t?.events&&(t.events.emit("group:selected",{group:e}),t.events.emit("group:changed",{group:e}))}getDefaultItemTemplate(){return'\n <div class="d-flex align-items-center p-3 border-bottom">\n <div class="flex-grow-1">\n <div class="fw-semibold text-dark">{{name}}</div>\n <small class="text-muted">#{{id}} {{kind}}</small>\n </div>\n </div>\n '}setCurrentGroup(e){this.currentGroup=e,this.displayName=e?.get?.("name")||e?.name||this.defaultText,this.mounted&&this.render()}getCurrentGroup(){return this.currentGroup}}class TopNav extends e.View{constructor(e={}){const t={light:"navbar navbar-expand-lg navbar-light topnav-light",dark:"navbar navbar-expand-lg navbar-dark topnav-dark",clean:"navbar navbar-expand-lg navbar-light topnav-clean",gradient:"navbar navbar-expand-lg navbar-dark topnav-gradient"};let s=t[e.theme||"light"]||t.light;e.shadow&&(s+=` topnav-shadow-${e.shadow}`),super({tagName:"nav",className:s,enableTooltips:!0,style:"position: relative; z-index: 1030;",...e}),this.displayMode=e.displayMode||"both",this.showPageIcon=!1!==e.showPageIcon,this.showPageDescription=e.showPageDescription||!1,this.showBreadcrumbs=e.showBreadcrumbs||!1,this.groupIcon=e.groupIcon||"bi-building",this.currentPage=null,this.previousPage=null,this.config={brand:e.brand||"MOJO App",brandIcon:e.brandIcon||"bi bi-play-circle",brandRoute:e.brandRoute||"/",navItems:e.navItems||[],rightItems:e.rightItems||[],showSidebarToggle:e.showSidebarToggle||!1,sidebarToggleAction:e.sidebarToggleAction||"toggle-sidebar",...e},this.userMenu=e.userMenu||this.findMenuItem("user"),this.userMenu&&(this.userMenu.id="user"),this.loginMenu=e.loginMenu||this.findMenuItem("login"),this.setupPageListeners(),this.setupGroupListeners(),this.groupSelectorButton=null,this.currentGroup=null}findMenuItem(e){let t=this.config.navItems.find(t=>t.id===e);return t||(t=this.config.rightItems.find(t=>t.id===e)),t||null}replaceMenuItem(e,t){const s=this.config.navItems.findIndex(t=>t.id===e);if(-1!==s)return this.config.navItems[s]=t,!0;const n=this.config.rightItems.findIndex(t=>t.id===e);return-1!==n&&(this.config.rightItems[n]=t,!0)}setBrand(e,t=null){this.config.brand=e,this.config.brandIcon=t||this.config.brandIcon,this.render()}setUser(e){e?(this.userMenu.label=e.get("display_name"),this.replaceMenuItem("login",this.userMenu)):this.replaceMenuItem("user",this.loginMenu),this.setModel(e)}_onModelChange(){this.model&&(this.userMenu.label=this.model.get("display_name")),this.isMounted()&&this.render()}async getTemplate(){return'\n <div class="container-fluid">\n {{#data.showSidebarToggle}}\n <button class="topnav-sidebar-toggle me-2" data-action="{{data.sidebarToggleAction}}" aria-label="Toggle Sidebar">\n <i class="bi bi-chevron-right toggle-chevron"></i>\n </button>\n {{/data.showSidebarToggle}}\n\n {{#data.showGroupInfo}}\n <div class="navbar-brand d-flex align-items-center">\n {{#data.groupIcon}}<i class="{{data.groupIcon}} me-2"></i>{{/data.groupIcon}}\n <div>\n <span class="topnav-group-name"\n role="button"\n tabindex="0"\n data-action="open-group-selector"\n style="cursor: pointer;">\n {{data.currentGroupName}}\n </span>\n {{#data.showPageTitle}}\n <span class="text-muted mx-2">|</span>\n <span>{{data.currentPageName}}</span>\n {{/data.showPageTitle}}\n </div>\n </div>\n {{/data.showGroupInfo}}\n\n {{#data.showPageInfo}}\n <div class="navbar-brand d-flex align-items-center">\n {{#data.currentPageIcon}}<i class="{{data.currentPageIcon}} me-2"></i>{{/data.currentPageIcon}}\n <div>\n <span>{{data.currentPageName}}</span>\n {{#data.currentPageDescription}}\n <small class="d-block" style="font-size: 0.75rem; line-height: 1;">{{data.currentPageDescription}}</small>\n {{/data.currentPageDescription}}\n </div>\n </div>\n {{/data.showPageInfo}}\n\n {{#data.showBrand}}\n <a class="navbar-brand" href="{{data.brandRoute}}">\n {{#data.brandIcon}}<i class="{{data.brandIcon}} me-2"></i>{{/data.brandIcon}}\n {{data.brand}}\n </a>\n {{/data.showBrand}}\n\n <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#{{data.navbarId}}">\n <span class="navbar-toggler-icon"></span>\n </button>\n\n <div class="collapse navbar-collapse" id="{{data.navbarId}}">\n {{#data.showNavItems}}\n <ul class="navbar-nav me-auto mb-2 mb-lg-0">\n {{#data.navItems}}\n <li class="nav-item">\n <a class="nav-link {{#active}}active{{/active}}" href="{{route}}" {{#tooltip}}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{tooltip}}"{{/tooltip}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{text}}\n </a>\n </li>\n {{/data.navItems}}\n </ul>\n {{/data.showNavItems}}\n\n {{#data.hasRightItems}}\n <div class="navbar-nav ms-auto">\n {{#data.rightItems}}\n {{#isGroupSelector}}\n <div data-container="group-selector-{{id}}"></div>\n {{/isGroupSelector}}\n {{^isGroupSelector}}\n {{#isDropdown}}\n <div class="nav-item dropdown">\n <a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </a>\n <ul class="dropdown-menu dropdown-menu-end">\n {{#items}}\n {{#divider}}\n <li><hr class="dropdown-divider"></li>\n {{/divider}}\n {{^divider}}\n <li>\n <a class="dropdown-item" role="button" {{#action}}data-action="{{action}}"{{/action}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </a>\n </li>\n {{/divider}}\n {{/items}}\n </ul>\n </div>\n {{/isDropdown}}\n {{^isDropdown}}\n {{#isButton}}\n <button class="{{buttonClass}}" data-action="{{action}}" data-id="{{id}}" {{#tooltip}}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{tooltip}}"{{/tooltip}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </button>\n {{/isButton}}\n {{^isButton}}\n <a class="nav-link" href="{{href}}" {{#action}}data-action="{{action}}"{{/action}} {{#tooltip}}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{tooltip}}"{{/tooltip}}>\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </a>\n {{/isButton}}\n {{/isDropdown}}\n {{/isGroupSelector}}\n {{/data.rightItems}}\n </div>\n {{/data.hasRightItems}}\n </div>\n </div>\n '}async onBeforeRender(){await super.onBeforeRender();const e=this.getApp(),t=this.currentGroup||e?.activeGroup,s="group"===this.displayMode||"group_page_titles"===this.displayMode,n="group_page_titles"===this.displayMode,i="page"===this.displayMode||"both"===this.displayMode,a=!s&&!i,o="menu"===this.displayMode||"both"===this.displayMode,r=this.filterItemsByPermissions(this.config.navItems||[]),l=this.processRightItems(this.config.rightItems||[]);this.data={brand:this.config.brand,brandIcon:this.config.brandIcon,brandRoute:this.config.brandRoute,showBrand:a,navbarId:`navbar-${this.id}`,navItems:r,showNavItems:o,rightItems:l,hasRightItems:l.length>0,showGroupInfo:s,showPageTitle:n,currentGroupName:t?.get?.("name")||t?.name||"Select Group",groupIcon:this.groupIcon,showPageInfo:i,currentPageName:this.currentPage?.title||this.currentPage?.name||"",currentPageIcon:this.currentPage?.icon||this.currentPage?.pageIcon||"",currentPageDescription:this.showPageDescription?this.currentPage?.description:"",showSidebarToggle:this.config.showSidebarToggle,sidebarToggleAction:this.config.sidebarToggleAction,displayMode:this.displayMode}}processRightItems(e){return this.filterItemsByPermissions(e).map(e=>{const t={...e};if(e.items&&(t.items=this.filterItemsByPermissions(e.items)),"group-selector"===e.type){t.isGroupSelector=!0,t.isDropdown=!1,t.isButton=!1;const s={containerId:`group-selector-${e.id||"default"}`};void 0!==e.Collection&&(s.Collection=e.Collection),void 0!==e.collection&&(s.collection=e.collection),void 0!==e.currentGroup&&(s.currentGroup=e.currentGroup),void 0!==e.buttonClass&&(s.buttonClass=e.buttonClass),void 0!==e.buttonIcon&&(s.buttonIcon=e.buttonIcon),void 0!==e.defaultText&&(s.defaultText=e.defaultText),void 0!==e.itemTemplate&&(s.itemTemplate=e.itemTemplate),void 0!==e.searchFields&&(s.searchFields=e.searchFields),void 0!==e.headerText&&(s.headerText=e.headerText),void 0!==e.searchPlaceholder&&(s.searchPlaceholder=e.searchPlaceholder),void 0!==e.autoSetActiveGroup&&(s.autoSetActiveGroup=e.autoSetActiveGroup),void 0!==e.onGroupSelected&&(s.onGroupSelected=e.onGroupSelected);const n=new GroupSelectorButton(s);this.groupSelectorButton=n,this.addChild(n)}else t.items&&t.items.length>0?(t.isDropdown=!0,t.isButton=!1):e.buttonClass?(t.isButton=!0,t.isDropdown=!1):(t.isButton=!1,t.isDropdown=!1);return e.handler&&(this.rightItemHandlers=this.rightItemHandlers||/* @__PURE__ */new Map,this.rightItemHandlers.set(e.id,e.handler)),t})}setupPageListeners(){this.getApp().events.on("page:show",e=>{this.onPageChanged(e)})}setupGroupListeners(){const e=this.getApp();e?.events&&e.events.on(["group:changed","group:loaded"],e=>{e?.group&&(this.currentGroup=e.group),"group"!==this.displayMode&&"group_page_titles"!==this.displayMode||this.mounted&&this.render()})}onPageBeforeChange(e){"page"===this.displayMode||this.displayMode}onPageChanged(e){this.previousPage=this.currentPage,this.currentPage=e.page,"page"!==this.displayMode&&"both"!==this.displayMode||this.updatePageDisplay(),"menu"!==this.displayMode&&"both"!==this.displayMode||this.currentPage&&this.currentPage.route&&this.updateActiveItem(this.currentPage.route)}updatePageDisplay(){this.currentPage&&this.mounted&&this.render()}updateActiveItem(e){const t=e=>e?e.startsWith("/")?e:`/${e}`:"/",s=t(e),n=this.data.navItems.map(e=>{const n=t(e.route);let i=!1;return"/"===n&&"/"===s?i=!0:"/"!==n&&"/"!==s&&(i=s.startsWith(n)||s===n),{...e,active:i}});this.updateData({navItems:n},!0)}onPassThruActionProfile(){this.getApp().events.emit("portal:action",{action:"profile"})}onActionSettings(){this.getApp().events.emit("portal:action",{action:"settings"})}onActionLogout(){this.getApp().events.emit("auth:logout",{action:"logout"})}async onActionOpenGroupSelector(e){if(this.groupSelectorButton)return await this.groupSelectorButton.onActionShowSelector(e),!0;const{GroupList:t}=await Promise.resolve().then(()=>require("./ContextMenu-NNHmt1iq.js")).then(e=>e.Group$1),s=new GroupSelectorButton({Collection:t,currentGroup:this.getApp()?.activeGroup});return await s.onActionShowSelector(e),!0}async handleAction(e,t,s){const n=s.getAttribute("data-id");if(n&&this.rightItemHandlers&&this.rightItemHandlers.has(n)){const i=this.rightItemHandlers.get(n);if("function"==typeof i)return await i.call(this,e,t,s)}const i=`onAction${e.charAt(0).toUpperCase()+e.slice(1).replace(/-([a-z])/g,e=>e[1].toUpperCase())}`;if("function"==typeof this[i])return await this[i](t,s);this.emit("action",{action:e,event:t,element:s,topnav:this})}async onActionDefault(e,t,s){if(this.config.navItems)for(const n of this.config.navItems)if(n.action===e&&n.handler)return await n.handler.call(this,e,t,s),!0;if(this.config.rightItems)for(const n of this.config.rightItems){if(n.action===e&&n.handler)return await n.handler.call(this,e,t,s),!0;if(n.items)for(const i of n.items)if(i.action===e&&i.handler)return await i.handler.call(this,e,t,s),!0}return this.getApp().events.emit("portal:action",{action:e,event:t,el:s}),!1}filterItemsByPermissions(e){if(!e)return[];const t=this.getApp(),s=t?.activeUser;return e.filter(e=>!e.permissions||!s||s.hasPermission(e.permissions))}}class Token{constructor(e){this.token=e,this.payload=null,this.uid=null,this.email=null,this.name=null,this.exp=null,this.iat=null,this.isValidToken=!1,this._decode()}_decode(){if(this.token&&"string"==typeof this.token)try{const e=this.token.split(".");if(3!==e.length)return;let t=e[1].replace(/-/g,"+").replace(/_/g,"/");const s=4-t.length%4;4!==s&&(t+="=".repeat(s));const n=atob(t);this.payload=JSON.parse(n),this.uid=this.payload.uid||this.payload.sub||this.payload.user_id||null,this.email=this.payload.email||null,this.name=this.payload.name||this.payload.username||null,this.exp=this.payload.exp?new Date(1e3*this.payload.exp):null,this.iat=this.payload.iat?new Date(1e3*this.payload.iat):null,this.isValidToken=this._checkValidity()}catch(e){this.payload=null}}_checkValidity(){return!(!this.token||!this.payload)&&(!this.payload.exp||Math.floor(Date.now()/1e3)<this.payload.exp)}decode(){return this.payload}getUserId(){return this.uid}isValid(){return this.isValidToken}isExpiringSoon(e=5){if(!this.payload?.exp)return!1;const t=Math.floor(Date.now()/1e3),s=60*e;return this.payload.exp-t<=s}isExpired(){return!!this.payload?.exp&&Math.floor(Date.now()/1e3)>=this.payload.exp}getAgeMinutes(){if(!this.payload?.iat)return null;const e=Math.floor(Date.now()/1e3)-this.payload.iat;return Math.floor(e/60)}getAuthHeader(){return this.token?`Bearer ${this.token}`:null}getUserInfo(){return this.payload?{uid:this.uid,email:this.email,name:this.name,exp:this.exp,iat:this.iat}:null}}exports.SimpleSearchView=SimpleSearchView,exports.TokenManager=class{constructor(){this.tokenKey="access_token",this.refreshTokenKey="refresh_token",this.tokenInstance=null}setTokens(e,t=null,s=!0){const n=s?localStorage:sessionStorage;this.tokenInstance=new Token(e),e&&n.setItem(this.tokenKey,e),t&&n.setItem(this.refreshTokenKey,t)}getToken(){return localStorage.getItem(this.tokenKey)||sessionStorage.getItem(this.tokenKey)}getRefreshToken(){return localStorage.getItem(this.refreshTokenKey)||sessionStorage.getItem(this.refreshTokenKey)}clearTokens(){localStorage.removeItem(this.tokenKey),localStorage.removeItem(this.refreshTokenKey),sessionStorage.removeItem(this.tokenKey),sessionStorage.removeItem(this.refreshTokenKey)}getTokenInstance(){const e=this.getToken();return e?(this.tokenInstance&&this.tokenInstance.token===e||(this.tokenInstance=new Token(e)),this.tokenInstance):(this.tokenInstance=null,null)}getRefreshTokenInstance(){const e=this.getRefreshToken();return e?(this._refreshTokenInstance&&this._refreshTokenInstance.token===e||(this._refreshTokenInstance=new Token(e)),this._refreshTokenInstance):(this._refreshTokenInstance=null,null)}decode(e=null){const t=e||this.getToken();return new Token(t).decode()}getUserId(){const e=this.getTokenInstance();return e?e.getUserId():null}isValid(){const e=this.getTokenInstance();return!!e&&e.isValid()}isExpiringSoon(e=5){const t=this.getTokenInstance();return!!t&&t.isExpiringSoon(e)}getAuthHeader(){const e=this.getTokenInstance();return e?e.getAuthHeader():null}getUserInfo(){const e=this.getTokenInstance();return e?e.getUserInfo():null}checkTokenStatus(){const e=this.getTokenInstance(),t=this.getRefreshTokenInstance();return e&&e.isValid()&&!e.isExpired()?e.isExpiringSoon(10)||e.getAgeMinutes()&&e.getAgeMinutes()>60?t&&t.isValid()&&!t.isExpired()?{action:"refresh",reason:"Access token expiring soon or aged"}:{action:"none",reason:"Access token expiring but refresh token invalid"}:{action:"none",reason:"All tokens valid and not expiring soon"}:t&&t.isValid()&&!t.isExpired()?{action:"refresh",reason:"Access token invalid/expired but refresh token valid"}:{action:"logout",reason:"Both access and refresh tokens are invalid/expired"}}async checkAndRefreshTokens(e){switch(this.checkTokenStatus().action){case"logout":return e.events.emit("auth:unauthorized"),this.stopAutoRefresh(),!0;case"refresh":return await this.refreshToken(e),!0;default:return!1}}startAutoRefresh(e){this.stopAutoRefresh(),this._tokenWatcher=setInterval(()=>{this.checkAndRefreshTokens(e)},6e4)}stopAutoRefresh(){this._tokenWatcher&&(clearInterval(this._tokenWatcher),this._tokenWatcher=null)}async refreshToken(e){const t=this.getRefreshTokenInstance();if(!t||!t.isValid()||t.isExpired())return e.events.emit("auth:unauthorized"),void this.stopAutoRefresh();try{const s=await e.rest.POST("/api/token/refresh",{refresh_token:t.token}),{access_token:n,refresh_token:i}=s.data.data;this.tokenInstance=null,this._refreshTokenInstance=null,this.setTokens(n,i),e.rest.setAuthToken(n),e.events.emit("auth:token:refreshed",{newToken:n,newRefreshToken:i})}catch(s){401===s.status||403===s.status?(e.events.emit("auth:unauthorized"),this.stopAutoRefresh()):e.events.emit("auth:token:refresh:failed",{error:s})}}},exports.TopNav=TopNav;
2
+ //# sourceMappingURL=TokenManager-DH4-kElJ.js.map