web-mojo 2.2.14 → 2.2.15
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/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +22 -14
- 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-BJK6SF8T.js +2 -0
- package/dist/chunks/ChatView-BJK6SF8T.js.map +1 -0
- package/dist/chunks/{ChatView-eFzjsHBL.js → ChatView-BPVE1u2i.js} +18 -4
- package/dist/chunks/ChatView-BPVE1u2i.js.map +1 -0
- package/dist/chunks/{Collection-BlP54kxB.js → Collection-BQWznmwY.js} +2 -2
- package/dist/chunks/{Collection-BlP54kxB.js.map → Collection-BQWznmwY.js.map} +1 -1
- package/dist/chunks/{Collection-CTkDG1NZ.js → Collection-zmb3xHhH.js} +27 -3
- package/dist/chunks/{Collection-CTkDG1NZ.js.map → Collection-zmb3xHhH.js.map} +1 -1
- package/dist/chunks/{ContextMenu-Capwv7d-.js → ContextMenu-DPjJuxpq.js} +2 -2
- package/dist/chunks/{ContextMenu-Capwv7d-.js.map → ContextMenu-DPjJuxpq.js.map} +1 -1
- package/dist/chunks/{ContextMenu-BH5SaDXX.js → ContextMenu-NNHmt1iq.js} +2 -2
- package/dist/chunks/{ContextMenu-BH5SaDXX.js.map → ContextMenu-NNHmt1iq.js.map} +1 -1
- package/dist/chunks/{ListView-CNkYumcc.js → ListView-BBZw7GUS.js} +2 -2
- package/dist/chunks/{ListView-CNkYumcc.js.map → ListView-BBZw7GUS.js.map} +1 -1
- package/dist/chunks/{ListView-O9AO02Rf.js → ListView-DHQyUB3V.js} +2 -2
- package/dist/chunks/{ListView-O9AO02Rf.js.map → ListView-DHQyUB3V.js.map} +1 -1
- package/dist/chunks/{TokenManager-CCfcK4aA.js → TokenManager-Bi5eDY8Y.js} +2 -2
- package/dist/chunks/{TokenManager-CCfcK4aA.js.map → TokenManager-Bi5eDY8Y.js.map} +1 -1
- package/dist/chunks/{TokenManager-CBXqj6Iw.js → TokenManager-DFWJ-cuN.js} +3 -3
- package/dist/chunks/{TokenManager-CBXqj6Iw.js.map → TokenManager-DFWJ-cuN.js.map} +1 -1
- package/dist/chunks/{version-DnlcM3tJ.js → version-BzRzH4mJ.js} +2 -2
- package/dist/chunks/{version-DnlcM3tJ.js.map → version-BzRzH4mJ.js.map} +1 -1
- package/dist/chunks/{version-DCTYSNWj.js → version-S1OYxk1c.js} +4 -4
- package/dist/chunks/{version-DCTYSNWj.js.map → version-S1OYxk1c.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +4 -4
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +8 -8
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +2 -2
- package/package.json +1 -1
- package/dist/chunks/ChatView-DGulpthL.js +0 -2
- package/dist/chunks/ChatView-DGulpthL.js.map +0 -1
- package/dist/chunks/ChatView-eFzjsHBL.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const t=require("./Rest-P-KCJpjB.js"),e=require("./Collection-
|
|
2
|
-
//# sourceMappingURL=ListView-
|
|
1
|
+
"use strict";const t=require("./Rest-P-KCJpjB.js"),e=require("./Collection-BQWznmwY.js");class ListViewItem extends t.View{constructor(t={}){super({className:"list-view-item",...t}),this.selected=!1,this.index=t.index??0,this.listView=t.listView??null,this.template||(this.template='\n <div class="list-item-content" data-action="select">\n {{#model}}\n {{#id}}<span class="item-id">{{id}}</span>{{/id}}\n {{#name}}<span class="item-name">{{name}}</span>{{/name}}\n {{#title}}<span class="item-title">{{title}}</span>{{/title}}\n {{#label}}<span class="item-label">{{label}}</span>{{/label}}\n {{#description}}<p class="item-description">{{description}}</p>{{/description}}\n {{/model}}\n {{^model}}\n <span class="item-empty">No data</span>\n {{/model}}\n </div>\n ')}async onActionSelect(t,e){t.stopPropagation(),this.selected?this.deselect():this.select()}select(){this.selected||(this.selected=!0,this.addClass("selected"),this.emit("item:select",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:select",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}))}deselect(){this.selected&&(this.selected=!1,this.removeClass("selected"),this.emit("item:deselect",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:deselect",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}))}async onActionDefault(t,e,i){this.emit("item:click",{item:this,model:this.model,index:this.index,action:t,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,action:t,data:this.model?.toJSON?this.model.toJSON():this.model})}setIndex(t){return this.index=t,this.element.setAttribute("data-index",t),this}setSelected(t){return t?this.select():this.deselect(),this}async destroy(){this.listView=null,await super.destroy()}}class ListView extends t.View{constructor(t={}){super({className:"list-view",template:'\n <div class="list-view-container">\n {{#loading}}\n <div class="list-loading">\n <div class="spinner-border spinner-border-sm" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n Loading...\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="list-empty">\n {{emptyMessage}}\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class="list-items" data-container="items"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ',...t}),this.collection=null,this.itemViews=/* @__PURE__ */new Map,this.selectedItems=/* @__PURE__ */new Set,this.itemTemplate=t.itemTemplate||null,this.itemClass=t.itemClass||ListViewItem,this.selectionMode=t.selectionMode||"none",this.emptyMessage=t.emptyMessage||"No items to display",this.loading=!1,this.isEmpty=!0}async onInit(){this._initCollection(this.options.collection||this.options.Collection)}_initCollection(t){if(t)if(t instanceof e.Collection)this.setCollection(t);else if("function"==typeof t){const e=new t;this.setCollection(e)}else if(Array.isArray(t)){const i=new e.Collection(null,{},t);this.setCollection(i)}}setCollection(t){return this.collection===t||(this.collection&&(this.collection.off("add",this._onModelsAdded,this),this.collection.off("remove",this._onModelsRemoved,this),this.collection.off("reset",this._onCollectionReset,this),this.collection.off("fetch:start",this._onFetchStart,this),this.collection.off("fetch:end",this._onFetchEnd,this)),this.collection=t,this.options.defaultQuery&&!this.options.collectionParams&&(this.collection.params={...this.collection.params,...this.options.defaultQuery}),this.options.collectionParams&&(this.collection.params={...this.collection.params,...this.options.collectionParams}),this.collection&&(this.collection.on("add",this._onModelsAdded,this),this.collection.on("remove",this._onModelsRemoved,this),this.collection.on("reset",this._onCollectionReset,this),this.collection.on("fetch:start",this._onFetchStart,this),this.collection.on("fetch:end",this._onFetchEnd,this),this._buildItems())),this}async _renderChildren(){await super._renderChildren();const t=this.getChildElement("items");t&&this.forEachItem((e,i)=>{t.appendChild(e.element),e.render(!1)})}_buildItems(){if(this._clearItems(),!this.collection||this.collection.isEmpty())return this.isEmpty=!0,void this.emit("list:empty");this.isEmpty=!1,this.collection.forEach((t,e)=>{this._createItemView(t,e)}),this.emit("list:loaded",{count:this.collection.length()}),this.isMounted()&&this.render()}_createItemView(t,e){if(this.itemViews.has(t.id))return;const i=new this.itemClass({model:t,index:e,listView:this,template:this.itemTemplate});return this.itemViews.set(t.id,i),i.on("item:select",this._onItemSelect.bind(this)),i.on("item:deselect",this._onItemDeselect.bind(this)),i}_clearItems(){this.forEachItem(t=>{this.removeChild(t.id)}),this.itemViews.clear(),this.selectedItems.clear()}_onModelsAdded(t){const{models:e}=t;e.forEach(t=>{const e=this.collection.models.indexOf(t);this._createItemView(t,e)}),this.isEmpty=this.collection.isEmpty(),!this.loading&&this.isMounted()&&this.render()}_onModelsRemoved(t){const{models:e}=t;e.forEach(t=>{const e=this.itemViews.get(t.id);e&&(this.removeChild(e.id),this.itemViews.delete(t.id),this.selectedItems.delete(t.id))}),this.isEmpty=this.collection.isEmpty(),!this.loading&&this.isMounted()&&this.render(),this.isEmpty&&this.emit("list:empty")}_onCollectionReset(t){this._buildItems()}_onFetchStart(){this.loading=!0,this.isMounted()&&this.render()}_onFetchEnd(){this.loading=!1,this.isMounted()&&this.render()}_onItemSelect(t){const{model:e,item:i}=t;"none"!==this.selectionMode?("single"===this.selectionMode&&(this.itemViews.forEach((t,i)=>{i!==e.id&&t.selected&&t.deselect()}),this.selectedItems.clear()),this.selectedItems.add(e.id),this.emit("selection:change",{selected:Array.from(this.selectedItems),item:i,model:e})):i.deselect()}_onItemDeselect(t){const{model:e}=t;this.selectedItems.delete(e.id),this.emit("selection:change",{selected:Array.from(this.selectedItems),item:t.item,model:e})}getSelectedItems(){const t=[];return this.selectedItems.forEach(e=>{const i=this.itemViews.get(e);i&&t.push({view:i,model:i.model,data:i.model?.toJSON?i.model.toJSON():i.model})}),t}forEachItem(t,e){if("function"!=typeof t)throw new TypeError("Callback must be a function");let i=0;return this.itemViews.forEach((s,o)=>{t.call(e,s,s.model,i++)}),this}clearSelection(){this.forEachItem(t=>{t.selected&&t.deselect()}),this.selectedItems.clear(),this.emit("selection:change",{selected:[]})}selectItem(t){const e=this.itemViews.get(t);return e&&e.select(),this}deselectItem(t){const e=this.itemViews.get(t);return e&&e.deselect(),this}setItemTemplate(t,e=!1){return this.itemTemplate=t,e&&this.itemViews.size>0&&this.forEachItem(e=>{e.setTemplate(t),e.isMounted()&&e.render()}),this}async onAfterMount(){await super.onAfterMount(),!this.collection||!this.options.fetchOnMount&&this.collection.lastFetchTime||this.collection.fetch()}async refresh(){if(this.collection&&this.collection.restEnabled)return await this.collection.fetch();this._buildItems()}async destroy(){this.collection&&(this.collection.off("add",this._onModelsAdded,this),this.collection.off("remove",this._onModelsRemoved,this),this.collection.off("reset",this._onCollectionReset,this),this.collection.off("fetch:start",this._onFetchStart,this),this.collection.off("fetch:end",this._onFetchEnd,this)),this._clearItems(),await super.destroy()}}exports.ListView=ListView,exports.ListViewItem=ListViewItem;
|
|
2
|
+
//# sourceMappingURL=ListView-DHQyUB3V.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListView-O9AO02Rf.js","sources":["../../src/core/views/list/ListViewItem.js","../../src/core/views/list/ListView.js"],"sourcesContent":["/**\n * ListViewItem - Individual item view for ListView\n *\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n *\n * Events:\n * - 'item:click' - Emitted when item is clicked\n * - 'item:select' - Emitted when item is selected\n * - 'item:deselect' - Emitted when item is deselected\n *\n * @example\n * const item = new ListViewItem({\n * model: userModel,\n * template: '<div class=\"user-item\">{{name}} - {{email}}</div>'\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ListViewItem extends View {\n constructor(options = {}) {\n super({\n className: 'list-view-item',\n ...options\n });\n\n // Item-specific properties\n this.selected = false;\n this.index = options.index ?? 0;\n this.listView = options.listView ?? null;\n\n // Default template if none provided\n if (!this.template) {\n this.template = `\n <div class=\"list-item-content\" data-action=\"select\">\n {{#model}}\n {{#id}}<span class=\"item-id\">{{id}}</span>{{/id}}\n {{#name}}<span class=\"item-name\">{{name}}</span>{{/name}}\n {{#title}}<span class=\"item-title\">{{title}}</span>{{/title}}\n {{#label}}<span class=\"item-label\">{{label}}</span>{{/label}}\n {{#description}}<p class=\"item-description\">{{description}}</p>{{/description}}\n {{/model}}\n {{^model}}\n <span class=\"item-empty\">No data</span>\n {{/model}}\n </div>\n `;\n }\n }\n\n /**\n * Handle item selection action\n */\n async onActionSelect(event, _element) {\n event.stopPropagation();\n\n if (this.selected) {\n this.deselect();\n } else {\n this.select();\n }\n }\n\n /**\n * Select this item\n */\n select() {\n if (this.selected) return;\n\n this.selected = true;\n this.addClass('selected');\n\n // Emit selection event with item data\n this.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Deselect this item\n */\n deselect() {\n if (!this.selected) return;\n\n this.selected = false;\n this.removeClass('selected');\n\n // Emit deselection event\n this.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Handle click events on the item\n */\n async onActionDefault(action, _event, _element) {\n // Emit click event for any action not specifically handled\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Set the item's index in the list\n */\n setIndex(index) {\n this.index = index;\n this.element.setAttribute('data-index', index);\n return this;\n }\n\n /**\n * Update the item's selection state\n */\n setSelected(selected) {\n if (selected) {\n this.select();\n } else {\n this.deselect();\n }\n return this;\n }\n\n /**\n * Override destroy to clean up references\n */\n async destroy() {\n // Remove reference to parent ListView\n this.listView = null;\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListViewItem;\n","/**\n * ListView - Visual list component for Collections\n *\n * Manages a collection of ListViewItem views, each with its own model.\n * When a model changes, only its corresponding ListViewItem re-renders.\n *\n * Events:\n * - 'item:click' - Emitted when any item is clicked\n * - 'item:select' - Emitted when an item is selected\n * - 'item:deselect' - Emitted when an item is deselected\n * - 'selection:change' - Emitted when selection changes\n * - 'list:empty' - Emitted when list becomes empty\n * - 'list:loaded' - Emitted when list is populated\n *\n * @example\n * // Basic usage with custom item template\n * const listView = new ListView({\n * collection: userCollection,\n * itemTemplate: '<div class=\"user-item\">{{name}} - {{email}}</div>',\n * selectionMode: 'single'\n * });\n *\n * // Custom template with model fields\n * const productList = new ListView({\n * collection: productCollection,\n * itemTemplate: `\n * <div class=\"product-card\" data-action=\"select\">\n * <h4>{{name}}</h4>\n * <p class=\"price\">{{price|currency}}</p>\n * <p>{{description|truncate(100)}}</p>\n * </div>\n * `,\n * selectionMode: 'multiple'\n * });\n *\n * // Using custom item class with template\n * const customList = new ListView({\n * collection: myCollection,\n * itemClass: CustomListItem, // Your custom ListViewItem subclass\n * itemTemplate: '<div>{{title}}</div>', // Passed as 'template' to itemClass constructor\n * selectionMode: 'none'\n * });\n *\n * // Dynamic template update\n * listView.setItemTemplate('<div class=\"compact\">{{name}}</div>', true);\n */\n\nimport View from '@core/View.js';\nimport Collection from '@core/Collection.js';\nimport ListViewItem from './ListViewItem.js';\n\nclass ListView extends View {\n constructor(options = {}) {\n super({\n className: 'list-view',\n template: `\n <div class=\"list-view-container\">\n {{#loading}}\n <div class=\"list-loading\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n Loading...\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"list-empty\">\n {{emptyMessage}}\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"list-items\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // ListView specific properties\n this.collection = null;\n this.itemViews = new Map(); // Map of model.id -> ListViewItem\n this.selectedItems = new Set(); // Set of selected item IDs\n\n // Configuration\n this.itemTemplate = options.itemTemplate || null; // Template passed to each item's view\n this.itemClass = options.itemClass || ListViewItem; // Class for creating item views\n this.selectionMode = options.selectionMode || 'none'; // none, single, multiple\n this.emptyMessage = options.emptyMessage || 'No items to display';\n this.loading = false;\n this.isEmpty = true;\n\n }\n\n /**\n * Override onInit to set up initial state\n */\n async onInit() {\n // Initial render will happen automatically\n this._initCollection(this.options.collection || this.options.Collection);\n }\n\n\n /**\n * Initialize the collection\n */\n _initCollection(collectionOrClass) {\n if (!collectionOrClass) {\n console.log('Collection not provided');\n return;\n };\n\n // Check if it's already a Collection instance\n if (collectionOrClass instanceof Collection) {\n this.setCollection(collectionOrClass);\n }\n // Check if it's a Collection class\n else if (typeof collectionOrClass === 'function') {\n const collection = new collectionOrClass();\n this.setCollection(collection);\n }\n // Check if it's an array of data\n else if (Array.isArray(collectionOrClass)) {\n const collection = new Collection(null, {}, collectionOrClass);\n this.setCollection(collection);\n }\n }\n\n /**\n * Set the collection for this list view\n */\n setCollection(collection) {\n if (this.collection === collection) return this;\n\n // Clean up old collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n this.collection = collection;\n\n if (this.options.defaultQuery && !this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.defaultQuery };\n }\n\n if (this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.collectionParams };\n }\n\n // Set up new collection listeners\n if (this.collection) {\n this.collection.on('add', this._onModelsAdded, this);\n this.collection.on('remove', this._onModelsRemoved, this);\n this.collection.on('reset', this._onCollectionReset, this);\n this.collection.on('fetch:start', this._onFetchStart, this);\n this.collection.on('fetch:end', this._onFetchEnd, this);\n\n // Build items for existing models\n this._buildItems();\n }\n\n return this;\n }\n\n async _renderChildren() {\n await super._renderChildren();\n const itemsContainer = this.getChildElement(\"items\");\n if (!itemsContainer) {\n // console.warn('ListView: items container not found');\n return;\n }\n this.forEachItem((item, index) => {\n itemsContainer.appendChild(item.element);\n item.render(false);\n });\n }\n\n /**\n * Build item views for all models in collection\n */\n _buildItems() {\n // Clear existing items\n this._clearItems();\n\n if (!this.collection || this.collection.isEmpty()) {\n this.isEmpty = true;\n this.emit('list:empty');\n return;\n }\n\n this.isEmpty = false;\n\n // Create item views for each model\n this.collection.forEach((model, index) => {\n this._createItemView(model, index);\n });\n\n this.emit('list:loaded', { count: this.collection.length() });\n\n // Render if already mounted\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Create an item view for a model\n * The itemTemplate is passed as the template option to the itemClass constructor\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate, // Pass the itemTemplate to the item view\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', this._onItemSelect.bind(this));\n itemView.on('item:deselect', this._onItemDeselect.bind(this));\n\n return itemView;\n }\n\n /**\n * Clear all item views\n */\n _clearItems() {\n this.forEachItem(itemView => {\n this.removeChild(itemView.id);\n });\n this.itemViews.clear();\n this.selectedItems.clear();\n }\n\n /**\n * Handle models added to collection\n */\n _onModelsAdded(event) {\n const { models } = event;\n\n models.forEach(model => {\n const index = this.collection.models.indexOf(model);\n this._createItemView(model, index);\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to show new items\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle models removed from collection\n */\n _onModelsRemoved(event) {\n const { models } = event;\n\n models.forEach(model => {\n const itemView = this.itemViews.get(model.id);\n if (itemView) {\n this.removeChild(itemView.id);\n this.itemViews.delete(model.id);\n this.selectedItems.delete(model.id);\n }\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to update display\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n\n if (this.isEmpty) {\n this.emit('list:empty');\n }\n }\n\n /**\n * Handle collection reset\n */\n _onCollectionReset(_event) {\n this._buildItems();\n }\n\n /**\n * Handle fetch start\n */\n _onFetchStart() {\n this.loading = true;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle fetch end\n */\n _onFetchEnd() {\n this.loading = false;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle item selection\n */\n _onItemSelect(event) {\n const { model, item } = event;\n\n if (this.selectionMode === 'none') {\n item.deselect();\n return;\n }\n\n if (this.selectionMode === 'single') {\n // Deselect all other items\n this.itemViews.forEach((view, id) => {\n if (id !== model.id && view.selected) {\n view.deselect();\n }\n });\n this.selectedItems.clear();\n }\n\n this.selectedItems.add(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: item,\n model: model\n });\n }\n\n /**\n * Handle item deselection\n */\n _onItemDeselect(event) {\n const { model } = event;\n\n this.selectedItems.delete(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: event.item,\n model: model\n });\n }\n\n /**\n * Get selected items\n */\n getSelectedItems() {\n const selected = [];\n this.selectedItems.forEach(id => {\n const itemView = this.itemViews.get(id);\n if (itemView) {\n selected.push({\n view: itemView,\n model: itemView.model,\n data: itemView.model?.toJSON ? itemView.model.toJSON() : itemView.model\n });\n }\n });\n return selected;\n }\n\n /**\n * Iterate over each item view in the list\n * @param {function} callback - Function to execute for each item (itemView, model, index)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {ListView} Returns the ListView for chaining\n */\n forEachItem(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n let index = 0;\n this.itemViews.forEach((itemView, modelId) => {\n callback.call(thisArg, itemView, itemView.model, index++);\n });\n\n return this;\n }\n\n /**\n * Clear selection\n */\n clearSelection() {\n this.forEachItem(itemView => {\n if (itemView.selected) {\n itemView.deselect();\n }\n });\n this.selectedItems.clear();\n\n this.emit('selection:change', {\n selected: []\n });\n }\n\n /**\n * Select item by model ID\n */\n selectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.select();\n }\n return this;\n }\n\n /**\n * Deselect item by model ID\n */\n deselectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.deselect();\n }\n return this;\n }\n\n /**\n * Set or update the item template\n * @param {string} template - New template string for items\n * @param {boolean} rerender - Whether to re-render existing items with new template\n * @returns {ListView} Returns the ListView for chaining\n */\n setItemTemplate(template, rerender = false) {\n this.itemTemplate = template;\n\n if (rerender && this.itemViews.size > 0) {\n // Update template for all existing item views\n this.forEachItem((itemView) => {\n itemView.setTemplate(template);\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n }\n\n return this;\n }\n\n async onAfterMount() {\n await super.onAfterMount();\n if (this.collection && (this.options.fetchOnMount || !this.collection.lastFetchTime)) {\n this.collection.fetch();\n }\n }\n\n /**\n * Refresh the list (re-fetch if collection supports it)\n */\n async refresh() {\n if (this.collection && this.collection.restEnabled) {\n return await this.collection.fetch();\n }\n this._buildItems();\n }\n\n /**\n * Override destroy to clean up\n */\n async destroy() {\n // Clean up collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n // Clear items\n this._clearItems();\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListView;\n"],"names":["ListViewItem","View","constructor","options","super","className","this","selected","index","listView","template","onActionSelect","event","_element","stopPropagation","deselect","select","addClass","emit","item","model","data","toJSON","removeClass","onActionDefault","action","_event","setIndex","element","setAttribute","setSelected","destroy","ListView","collection","itemViews","Map","selectedItems","Set","itemTemplate","itemClass","selectionMode","emptyMessage","loading","isEmpty","onInit","_initCollection","Collection","collectionOrClass","setCollection","Array","isArray","off","_onModelsAdded","_onModelsRemoved","_onCollectionReset","_onFetchStart","_onFetchEnd","defaultQuery","collectionParams","params","on","_buildItems","_renderChildren","itemsContainer","getChildElement","forEachItem","appendChild","render","_clearItems","forEach","_createItemView","count","length","isMounted","has","id","itemView","set","_onItemSelect","bind","_onItemDeselect","removeChild","clear","models","indexOf","get","delete","view","add","from","getSelectedItems","push","callback","thisArg","TypeError","modelId","call","clearSelection","selectItem","deselectItem","setItemTemplate","rerender","size","setTemplate","onAfterMount","fetchOnMount","lastFetchTime","fetch","refresh","restEnabled"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ListView-DHQyUB3V.js","sources":["../../src/core/views/list/ListViewItem.js","../../src/core/views/list/ListView.js"],"sourcesContent":["/**\n * ListViewItem - Individual item view for ListView\n *\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n *\n * Events:\n * - 'item:click' - Emitted when item is clicked\n * - 'item:select' - Emitted when item is selected\n * - 'item:deselect' - Emitted when item is deselected\n *\n * @example\n * const item = new ListViewItem({\n * model: userModel,\n * template: '<div class=\"user-item\">{{name}} - {{email}}</div>'\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ListViewItem extends View {\n constructor(options = {}) {\n super({\n className: 'list-view-item',\n ...options\n });\n\n // Item-specific properties\n this.selected = false;\n this.index = options.index ?? 0;\n this.listView = options.listView ?? null;\n\n // Default template if none provided\n if (!this.template) {\n this.template = `\n <div class=\"list-item-content\" data-action=\"select\">\n {{#model}}\n {{#id}}<span class=\"item-id\">{{id}}</span>{{/id}}\n {{#name}}<span class=\"item-name\">{{name}}</span>{{/name}}\n {{#title}}<span class=\"item-title\">{{title}}</span>{{/title}}\n {{#label}}<span class=\"item-label\">{{label}}</span>{{/label}}\n {{#description}}<p class=\"item-description\">{{description}}</p>{{/description}}\n {{/model}}\n {{^model}}\n <span class=\"item-empty\">No data</span>\n {{/model}}\n </div>\n `;\n }\n }\n\n /**\n * Handle item selection action\n */\n async onActionSelect(event, _element) {\n event.stopPropagation();\n\n if (this.selected) {\n this.deselect();\n } else {\n this.select();\n }\n }\n\n /**\n * Select this item\n */\n select() {\n if (this.selected) return;\n\n this.selected = true;\n this.addClass('selected');\n\n // Emit selection event with item data\n this.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Deselect this item\n */\n deselect() {\n if (!this.selected) return;\n\n this.selected = false;\n this.removeClass('selected');\n\n // Emit deselection event\n this.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Handle click events on the item\n */\n async onActionDefault(action, _event, _element) {\n // Emit click event for any action not specifically handled\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Set the item's index in the list\n */\n setIndex(index) {\n this.index = index;\n this.element.setAttribute('data-index', index);\n return this;\n }\n\n /**\n * Update the item's selection state\n */\n setSelected(selected) {\n if (selected) {\n this.select();\n } else {\n this.deselect();\n }\n return this;\n }\n\n /**\n * Override destroy to clean up references\n */\n async destroy() {\n // Remove reference to parent ListView\n this.listView = null;\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListViewItem;\n","/**\n * ListView - Visual list component for Collections\n *\n * Manages a collection of ListViewItem views, each with its own model.\n * When a model changes, only its corresponding ListViewItem re-renders.\n *\n * Events:\n * - 'item:click' - Emitted when any item is clicked\n * - 'item:select' - Emitted when an item is selected\n * - 'item:deselect' - Emitted when an item is deselected\n * - 'selection:change' - Emitted when selection changes\n * - 'list:empty' - Emitted when list becomes empty\n * - 'list:loaded' - Emitted when list is populated\n *\n * @example\n * // Basic usage with custom item template\n * const listView = new ListView({\n * collection: userCollection,\n * itemTemplate: '<div class=\"user-item\">{{name}} - {{email}}</div>',\n * selectionMode: 'single'\n * });\n *\n * // Custom template with model fields\n * const productList = new ListView({\n * collection: productCollection,\n * itemTemplate: `\n * <div class=\"product-card\" data-action=\"select\">\n * <h4>{{name}}</h4>\n * <p class=\"price\">{{price|currency}}</p>\n * <p>{{description|truncate(100)}}</p>\n * </div>\n * `,\n * selectionMode: 'multiple'\n * });\n *\n * // Using custom item class with template\n * const customList = new ListView({\n * collection: myCollection,\n * itemClass: CustomListItem, // Your custom ListViewItem subclass\n * itemTemplate: '<div>{{title}}</div>', // Passed as 'template' to itemClass constructor\n * selectionMode: 'none'\n * });\n *\n * // Dynamic template update\n * listView.setItemTemplate('<div class=\"compact\">{{name}}</div>', true);\n */\n\nimport View from '@core/View.js';\nimport Collection from '@core/Collection.js';\nimport ListViewItem from './ListViewItem.js';\n\nclass ListView extends View {\n constructor(options = {}) {\n super({\n className: 'list-view',\n template: `\n <div class=\"list-view-container\">\n {{#loading}}\n <div class=\"list-loading\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n Loading...\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"list-empty\">\n {{emptyMessage}}\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"list-items\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // ListView specific properties\n this.collection = null;\n this.itemViews = new Map(); // Map of model.id -> ListViewItem\n this.selectedItems = new Set(); // Set of selected item IDs\n\n // Configuration\n this.itemTemplate = options.itemTemplate || null; // Template passed to each item's view\n this.itemClass = options.itemClass || ListViewItem; // Class for creating item views\n this.selectionMode = options.selectionMode || 'none'; // none, single, multiple\n this.emptyMessage = options.emptyMessage || 'No items to display';\n this.loading = false;\n this.isEmpty = true;\n\n }\n\n /**\n * Override onInit to set up initial state\n */\n async onInit() {\n // Initial render will happen automatically\n this._initCollection(this.options.collection || this.options.Collection);\n }\n\n\n /**\n * Initialize the collection\n */\n _initCollection(collectionOrClass) {\n if (!collectionOrClass) {\n console.log('Collection not provided');\n return;\n };\n\n // Check if it's already a Collection instance\n if (collectionOrClass instanceof Collection) {\n this.setCollection(collectionOrClass);\n }\n // Check if it's a Collection class\n else if (typeof collectionOrClass === 'function') {\n const collection = new collectionOrClass();\n this.setCollection(collection);\n }\n // Check if it's an array of data\n else if (Array.isArray(collectionOrClass)) {\n const collection = new Collection(null, {}, collectionOrClass);\n this.setCollection(collection);\n }\n }\n\n /**\n * Set the collection for this list view\n */\n setCollection(collection) {\n if (this.collection === collection) return this;\n\n // Clean up old collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n this.collection = collection;\n\n if (this.options.defaultQuery && !this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.defaultQuery };\n }\n\n if (this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.collectionParams };\n }\n\n // Set up new collection listeners\n if (this.collection) {\n this.collection.on('add', this._onModelsAdded, this);\n this.collection.on('remove', this._onModelsRemoved, this);\n this.collection.on('reset', this._onCollectionReset, this);\n this.collection.on('fetch:start', this._onFetchStart, this);\n this.collection.on('fetch:end', this._onFetchEnd, this);\n\n // Build items for existing models\n this._buildItems();\n }\n\n return this;\n }\n\n async _renderChildren() {\n await super._renderChildren();\n const itemsContainer = this.getChildElement(\"items\");\n if (!itemsContainer) {\n // console.warn('ListView: items container not found');\n return;\n }\n this.forEachItem((item, index) => {\n itemsContainer.appendChild(item.element);\n item.render(false);\n });\n }\n\n /**\n * Build item views for all models in collection\n */\n _buildItems() {\n // Clear existing items\n this._clearItems();\n\n if (!this.collection || this.collection.isEmpty()) {\n this.isEmpty = true;\n this.emit('list:empty');\n return;\n }\n\n this.isEmpty = false;\n\n // Create item views for each model\n this.collection.forEach((model, index) => {\n this._createItemView(model, index);\n });\n\n this.emit('list:loaded', { count: this.collection.length() });\n\n // Render if already mounted\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Create an item view for a model\n * The itemTemplate is passed as the template option to the itemClass constructor\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate, // Pass the itemTemplate to the item view\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', this._onItemSelect.bind(this));\n itemView.on('item:deselect', this._onItemDeselect.bind(this));\n\n return itemView;\n }\n\n /**\n * Clear all item views\n */\n _clearItems() {\n this.forEachItem(itemView => {\n this.removeChild(itemView.id);\n });\n this.itemViews.clear();\n this.selectedItems.clear();\n }\n\n /**\n * Handle models added to collection\n */\n _onModelsAdded(event) {\n const { models } = event;\n\n models.forEach(model => {\n const index = this.collection.models.indexOf(model);\n this._createItemView(model, index);\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to show new items\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle models removed from collection\n */\n _onModelsRemoved(event) {\n const { models } = event;\n\n models.forEach(model => {\n const itemView = this.itemViews.get(model.id);\n if (itemView) {\n this.removeChild(itemView.id);\n this.itemViews.delete(model.id);\n this.selectedItems.delete(model.id);\n }\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to update display\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n\n if (this.isEmpty) {\n this.emit('list:empty');\n }\n }\n\n /**\n * Handle collection reset\n */\n _onCollectionReset(_event) {\n this._buildItems();\n }\n\n /**\n * Handle fetch start\n */\n _onFetchStart() {\n this.loading = true;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle fetch end\n */\n _onFetchEnd() {\n this.loading = false;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle item selection\n */\n _onItemSelect(event) {\n const { model, item } = event;\n\n if (this.selectionMode === 'none') {\n item.deselect();\n return;\n }\n\n if (this.selectionMode === 'single') {\n // Deselect all other items\n this.itemViews.forEach((view, id) => {\n if (id !== model.id && view.selected) {\n view.deselect();\n }\n });\n this.selectedItems.clear();\n }\n\n this.selectedItems.add(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: item,\n model: model\n });\n }\n\n /**\n * Handle item deselection\n */\n _onItemDeselect(event) {\n const { model } = event;\n\n this.selectedItems.delete(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: event.item,\n model: model\n });\n }\n\n /**\n * Get selected items\n */\n getSelectedItems() {\n const selected = [];\n this.selectedItems.forEach(id => {\n const itemView = this.itemViews.get(id);\n if (itemView) {\n selected.push({\n view: itemView,\n model: itemView.model,\n data: itemView.model?.toJSON ? itemView.model.toJSON() : itemView.model\n });\n }\n });\n return selected;\n }\n\n /**\n * Iterate over each item view in the list\n * @param {function} callback - Function to execute for each item (itemView, model, index)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {ListView} Returns the ListView for chaining\n */\n forEachItem(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n let index = 0;\n this.itemViews.forEach((itemView, modelId) => {\n callback.call(thisArg, itemView, itemView.model, index++);\n });\n\n return this;\n }\n\n /**\n * Clear selection\n */\n clearSelection() {\n this.forEachItem(itemView => {\n if (itemView.selected) {\n itemView.deselect();\n }\n });\n this.selectedItems.clear();\n\n this.emit('selection:change', {\n selected: []\n });\n }\n\n /**\n * Select item by model ID\n */\n selectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.select();\n }\n return this;\n }\n\n /**\n * Deselect item by model ID\n */\n deselectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.deselect();\n }\n return this;\n }\n\n /**\n * Set or update the item template\n * @param {string} template - New template string for items\n * @param {boolean} rerender - Whether to re-render existing items with new template\n * @returns {ListView} Returns the ListView for chaining\n */\n setItemTemplate(template, rerender = false) {\n this.itemTemplate = template;\n\n if (rerender && this.itemViews.size > 0) {\n // Update template for all existing item views\n this.forEachItem((itemView) => {\n itemView.setTemplate(template);\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n }\n\n return this;\n }\n\n async onAfterMount() {\n await super.onAfterMount();\n if (this.collection && (this.options.fetchOnMount || !this.collection.lastFetchTime)) {\n this.collection.fetch();\n }\n }\n\n /**\n * Refresh the list (re-fetch if collection supports it)\n */\n async refresh() {\n if (this.collection && this.collection.restEnabled) {\n return await this.collection.fetch();\n }\n this._buildItems();\n }\n\n /**\n * Override destroy to clean up\n */\n async destroy() {\n // Clean up collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n // Clear items\n this._clearItems();\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListView;\n"],"names":["ListViewItem","View","constructor","options","super","className","this","selected","index","listView","template","onActionSelect","event","_element","stopPropagation","deselect","select","addClass","emit","item","model","data","toJSON","removeClass","onActionDefault","action","_event","setIndex","element","setAttribute","setSelected","destroy","ListView","collection","itemViews","Map","selectedItems","Set","itemTemplate","itemClass","selectionMode","emptyMessage","loading","isEmpty","onInit","_initCollection","Collection","collectionOrClass","setCollection","Array","isArray","off","_onModelsAdded","_onModelsRemoved","_onCollectionReset","_onFetchStart","_onFetchEnd","defaultQuery","collectionParams","params","on","_buildItems","_renderChildren","itemsContainer","getChildElement","forEachItem","appendChild","render","_clearItems","forEach","_createItemView","count","length","isMounted","has","id","itemView","set","_onItemSelect","bind","_onItemDeselect","removeChild","clear","models","indexOf","get","delete","view","add","from","getSelectedItems","push","callback","thisArg","TypeError","modelId","call","clearSelection","selectItem","deselectItem","setItemTemplate","rerender","size","setTemplate","onAfterMount","fetchOnMount","lastFetchTime","fetch","refresh","restEnabled"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("./Rest-P-KCJpjB.js"),t=require("./Dialog--hl_Uh6X.js"),s=require("./ContextMenu-BH5SaDXX.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-BH5SaDXX.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-
|
|
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
|