web-mojo 2.1.380 → 2.1.443
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 +32 -43
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.cjs.js.map +1 -1
- package/dist/auth.es.js +3 -3
- package/dist/auth.es.js.map +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +2 -2
- package/dist/chunks/{ContextMenu-BPJJR6l6.js → ContextMenu-Cao6A-Lu.js} +4 -2
- package/dist/chunks/{ContextMenu-BPJJR6l6.js.map → ContextMenu-Cao6A-Lu.js.map} +1 -1
- package/dist/chunks/ContextMenu-TK4-wzDb.js +3 -0
- package/dist/chunks/ContextMenu-TK4-wzDb.js.map +1 -0
- package/dist/chunks/DataView-CHoPxbYT.js +2 -0
- package/dist/chunks/DataView-CHoPxbYT.js.map +1 -0
- package/dist/chunks/{DataView-ED8X_cvp.js → DataView-Qnk-TnHd.js} +17 -3
- package/dist/chunks/DataView-Qnk-TnHd.js.map +1 -0
- package/dist/chunks/{Dialog-hiboYc1m.js → Dialog-B_ZJYlVS.js} +2 -2
- package/dist/chunks/{Dialog-hiboYc1m.js.map → Dialog-B_ZJYlVS.js.map} +1 -1
- package/dist/chunks/{Dialog-DmEPwI5D.js → Dialog-DbZ2jB_m.js} +5 -5
- package/dist/chunks/{Dialog-DmEPwI5D.js.map → Dialog-DbZ2jB_m.js.map} +1 -1
- package/dist/chunks/FilePreviewView-DeGgRRbW.js +2 -0
- package/dist/chunks/FilePreviewView-DeGgRRbW.js.map +1 -0
- package/dist/chunks/{FilePreviewView-D39a1bWs.js → FilePreviewView-nuZku1yG.js} +68 -19
- package/dist/chunks/FilePreviewView-nuZku1yG.js.map +1 -0
- package/dist/chunks/{FormView-CCrutTVr.js → FormView-BDKrUX1-.js} +41 -23
- package/dist/chunks/FormView-BDKrUX1-.js.map +1 -0
- package/dist/chunks/FormView-BZInlDfZ.js +2 -0
- package/dist/chunks/FormView-BZInlDfZ.js.map +1 -0
- package/dist/chunks/{MetricsChart-D9j0JkfY.js → MetricsChart-Aqum5Znr.js} +3 -3
- package/dist/chunks/{MetricsChart-D9j0JkfY.js.map → MetricsChart-Aqum5Znr.js.map} +1 -1
- package/dist/chunks/{MetricsChart-B7yBoTzr.js → MetricsChart-BbaBczOZ.js} +2 -2
- package/dist/chunks/{MetricsChart-B7yBoTzr.js.map → MetricsChart-BbaBczOZ.js.map} +1 -1
- package/dist/chunks/{PDFViewer--7dBYfiB.js → PDFViewer-Bq-NM6JH.js} +2 -2
- package/dist/chunks/{PDFViewer--7dBYfiB.js.map → PDFViewer-Bq-NM6JH.js.map} +1 -1
- package/dist/chunks/{PDFViewer-BPFrZSlU.js → PDFViewer-CGj7cZut.js} +3 -3
- package/dist/chunks/{PDFViewer-BPFrZSlU.js.map → PDFViewer-CGj7cZut.js.map} +1 -1
- package/dist/chunks/{Page-DY5i-6Wq.js → Page-BkkXMFuP.js} +2 -2
- package/dist/chunks/{Page-DY5i-6Wq.js.map → Page-BkkXMFuP.js.map} +1 -1
- package/dist/chunks/{Page-D2Yq8RbO.js → Page-QhPYRY-q.js} +2 -2
- package/dist/chunks/{Page-D2Yq8RbO.js.map → Page-QhPYRY-q.js.map} +1 -1
- package/dist/chunks/{TopNav-BN3TAAxN.js → TopNav-D6c7gsox.js} +2 -2
- package/dist/chunks/{TopNav-BN3TAAxN.js.map → TopNav-D6c7gsox.js.map} +1 -1
- package/dist/chunks/{TopNav-BzKZzgE6.js → TopNav-DUwNnOuM.js} +2 -2
- package/dist/chunks/{TopNav-BzKZzgE6.js.map → TopNav-DUwNnOuM.js.map} +1 -1
- package/dist/chunks/{WebApp-p0nOftj0.js → WebApp-BPWJVv80.js} +19 -13
- package/dist/chunks/{WebApp-p0nOftj0.js.map → WebApp-BPWJVv80.js.map} +1 -1
- package/dist/chunks/WebApp-CYqazUo5.js +2 -0
- package/dist/chunks/{WebApp-Czng9Krl.js.map → WebApp-CYqazUo5.js.map} +1 -1
- package/dist/core.css +10 -0
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +5 -5
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +11 -11
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +4 -4
- package/package.json +1 -1
- package/dist/chunks/ContextMenu-YwW7txyQ.js +0 -3
- package/dist/chunks/ContextMenu-YwW7txyQ.js.map +0 -1
- package/dist/chunks/DataView-ED8X_cvp.js.map +0 -1
- package/dist/chunks/DataView-ryKnMcUa.js +0 -2
- package/dist/chunks/DataView-ryKnMcUa.js.map +0 -1
- package/dist/chunks/FilePreviewView-C3beZ-UX.js +0 -2
- package/dist/chunks/FilePreviewView-C3beZ-UX.js.map +0 -1
- package/dist/chunks/FilePreviewView-D39a1bWs.js.map +0 -1
- package/dist/chunks/FormView-CCrutTVr.js.map +0 -1
- package/dist/chunks/FormView-wKzv7Fey.js +0 -2
- package/dist/chunks/FormView-wKzv7Fey.js.map +0 -1
- package/dist/chunks/WebApp-Czng9Krl.js +0 -2
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";const e=require("./WebApp-CYqazUo5.js");class Model{constructor(t={},s={}){this.endpoint=s.endpoint||this.constructor.endpoint||"",this.id=t.id||null,this.attributes={...t},this._=this.attributes,this.originalAttributes={...t},this.errors={},this.loading=!1,this.rest=e.rest,this.options={idAttribute:"id",timestamps:!0,...s}}getContextValue(e){return this.get(e)}get(t){return t.includes(".")||t.includes("|")||void 0===this[t]?e.MOJOUtils.getContextData(this.attributes,t):"function"==typeof this[t]?this[t]():this[t]}set(e,t,s={}){const i=JSON.parse(JSON.stringify(this.attributes));let a=!1;if("object"==typeof e){for(const[t,s]of Object.entries(e))a=this._setNestedAttribute(t,s)||a;void 0!==e.id&&(this.id=e.id)}else"id"===e?(this.id=t,a=!0):a=this._setNestedAttribute(e,t);if(a&&!s.silent)if(this.emit("change",this),"string"==typeof e)this.emit(`change:${e}`,t,this);else for(const[r,o]of Object.entries(e)){const e=this._getNestedValue(r);JSON.stringify(this._getNestedValue(r,i))!==JSON.stringify(e)&&this.emit(`change:${r}`,e,this)}}_setNestedAttribute(e,t){if(!e.includes(".")){const s=this.attributes[e];return this.attributes[e]=t,this[e]=t,s!==t}const s=e.split("."),i=s[0];this.attributes[i]&&"object"==typeof this.attributes[i]||(this.attributes[i]={}),this[i]&&"object"==typeof this[i]||(this[i]={});const a=this._getNestedValue(e);let r=this.attributes[i],o=this[i];for(let l=1;l<s.length-1;l++){const e=s[l];r[e]&&"object"==typeof r[e]||(r[e]={}),o[e]&&"object"==typeof o[e]||(o[e]={}),r=r[e],o=o[e]}const n=s[s.length-1];return r[n]=t,o[n]=t,JSON.stringify(a)!==JSON.stringify(t)}_getNestedValue(e,t=this.attributes){if(!e.includes("."))return t[e];const s=e.split(".");let i=t;for(const a of s){if(null==i||"object"!=typeof i)return;i=i[a]}return i}getData(){return this.attributes}getId(){return this.id}async fetch(e={}){let t=e.url;if(!t){const s=e.id||this.getId();if(!s&&!1!==this.options.requiresId)throw new Error("Model: ID is required for fetching");t=this.buildUrl(s)}const s=JSON.stringify({url:t,params:e.params});if(e.debounceMs&&e.debounceMs>0)return this._debouncedFetch(s,e);if(this.currentRequest&&this.currentRequestKey!==s&&(console.info("Model: Cancelling previous request for new parameters"),this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===s)return console.info("Model: Duplicate request in progress, returning existing promise"),this.currentRequest;const i=Date.now();if(this.lastFetchTime&&i-this.lastFetchTime<100)return console.info("Model: Rate limited, skipping fetch"),this;this.loading=!0,this.errors={},this.lastFetchTime=i,this.currentRequestKey=s,this.abortController=new AbortController,this.currentRequest=this._performFetch(t,e,this.abortController);try{return await this.currentRequest}catch(a){if("AbortError"===a.name)return console.info("Model: Request was cancelled"),this;throw a}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _debouncedFetch(e,t){return this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((e,s)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const s=await this.fetch({...t,debounceMs:0});e(s)}catch(i){s(i)}},t.debounceMs)})}async _performFetch(e,t,s){try{!t.graph||t.params&&t.params.graph||(t.params||(t.params={}),t.params.graph=t.graph);const i=await this.rest.GET(e,t.params,{signal:s.signal});return i.success?i.data.status?(this.originalAttributes={...this.attributes},this.set(i.data.data),this.errors={}):this.errors=i.data:this.errors=i.errors||{},i}catch(i){if("AbortError"===i.name)throw console.info("Model: Fetch was cancelled"),i;return this.errors={fetch:i.message},{success:!1,error:i.message,status:i.status||500}}finally{this.loading=!1}}async save(e,t={}){const s=!this.id,i=s?"POST":"PUT",a=s?this.buildUrl():this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest[i](a,e,t.params);return s.success?s.data.status?(this.originalAttributes={...this.attributes},this.set(s.data.data),this.errors={}):this.errors=s.data:this.errors=s.errors||{},s}catch(r){return{success:!1,error:r.message,status:r.status||500}}finally{this.loading=!1}}async destroy(e={}){if(!this.id)return this.errors={destroy:"Cannot destroy model without ID"},{success:!1,error:"Cannot destroy model without ID",status:400};const t=this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest.DELETE(t,e.params);return s.success?(this.attributes={},this.originalAttributes={},this.id=null,this.errors={}):this.errors=s.errors||{},s}catch(s){return this.errors={destroy:s.message},{success:!1,error:s.message,status:s.status||500}}finally{this.loading=!1}}isDirty(){return JSON.stringify(this.attributes)!==JSON.stringify(this.originalAttributes)}getChangedAttributes(){const e={};for(const[t,s]of Object.entries(this.attributes))this.originalAttributes[t]!==s&&(e[t]=s);return e}reset(){this.attributes={...this.originalAttributes},this._=this.attributes,this.errors={}}buildUrl(e=null){let t=this.endpoint;return e&&(t=t.endsWith("/")?`${t}${e}`:`${t}/${e}`),t}toJSON(){return{id:this.id,...this.attributes}}validate(){if(this.errors={},this.constructor.validations)for(const[e,t]of Object.entries(this.constructor.validations))this.validateField(e,t);return 0===Object.keys(this.errors).length}validateField(e,t){const s=this.get(e),i=Array.isArray(t)?t:[t];for(const a of i)if("function"==typeof a){const t=a(s,this);if(!0!==t){this.errors[e]=t||`${e} is invalid`;break}}else if("object"==typeof a){if(a.required&&(null==s||""===s)){this.errors[e]=a.message||`${e} is required`;break}if(a.minLength&&s&&s.length<a.minLength){this.errors[e]=a.message||`${e} must be at least ${a.minLength} characters`;break}if(a.maxLength&&s&&s.length>a.maxLength){this.errors[e]=a.message||`${e} must be no more than ${a.maxLength} characters`;break}if(a.pattern&&s&&!a.pattern.test(s)){this.errors[e]=a.message||`${e} format is invalid`;break}}}static async find(e,t={}){const s=new this({},t);return await s.fetch({id:e,...t}),s}static create(e={},t={}){return new this(e,t)}cancel(){return this.currentRequest&&this.abortController?(console.info("Model: Manually cancelling active request"),this.abortController.abort(),!0):!!this.debouncedFetchTimeout&&(clearTimeout(this.debouncedFetchTimeout),this.debouncedFetchTimeout=null,!0)}isFetching(){return!!this.currentRequest}async showError(e){await Dialog.alert(e,"Error",{size:"md",class:"text-danger"})}}Object.assign(Model.prototype,e.EventEmitter);class Collection{constructor(t={},s=null){if(Array.isArray(t)?t=(s=t)||{}:s=s||t.data||[],this.ModelClass=t.ModelClass||Model,this.models=[],this.loading=!1,this.errors={},this.meta={},this.rest=e.rest,s&&this.add(s),this.params={start:0,size:t.size||10,...t.params},this.endpoint=t.endpoint||this.ModelClass.endpoint||"",!this.endpoint){let e=new this.ModelClass;this.endpoint=e.endpoint}this.restEnabled=!!this.endpoint,void 0!==t.restEnabled&&(this.restEnabled=t.restEnabled),this.options={parse:!0,reset:!0,preloaded:!1,...t}}getModelName(){return this.ModelClass.name}async fetch(e={}){const t=JSON.stringify({...this.params,...e});if(this.currentRequest&&this.currentRequestKey!==t&&(console.info("Collection: Cancelling previous request for new parameters"),this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===t)return console.info("Collection: Duplicate request in progress, returning existing promise"),this.currentRequest;const s=Date.now();if(this.options.rateLimiting&&this.lastFetchTime&&s-this.lastFetchTime<100)return console.info("Collection: Rate limited, skipping fetch"),{success:!0,message:"Rate limited, skipping fetch",data:{data:this.toJSON()}};if(!this.restEnabled)return console.info("Collection: REST disabled, skipping fetch"),{success:!0,message:"REST disabled, skipping fetch",data:{data:this.toJSON()}};if(this.options.preloaded&&this.models.length>0)return console.info("Collection: Using preloaded data, skipping fetch"),{success:!0,message:"Using preloaded data, skipping fetch",data:{data:this.toJSON()}};const i=this.buildUrl();this.loading=!0,this.errors={},this.lastFetchTime=s,this.currentRequestKey=t,this.abortController=new AbortController,this.currentRequest=this._performFetch(i,e,this.abortController);try{return await this.currentRequest}catch(a){return"AbortError"===a.name?(console.info("Collection: Request was cancelled"),{success:!1,error:"Request cancelled",status:0}):{success:!1,error:a.message,status:a.status||500}}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _performFetch(e,t,s){const i={...this.params,...t};console.log("Fetching collection data from",e,i);try{this.emit("fetch:start");const a=await this.rest.GET(e,i,{signal:s.signal});if(a.success&&a.data.status){const e=this.options.parse?this.parse(a):a.data;(this.options.reset||!1!==t.reset)&&this.reset(),this.add(e,{silent:t.silent}),this.errors={},this.emit("fetch:success")}else a.data&&a.data.error?(this.errors=a.data,this.emit("fetch:error",{message:a.data.error,error:a.data})):(this.errors=a.errors||{},this.emit("fetch:error",{error:a.errors}));return a}catch(a){return"AbortError"===a.name?(console.info("Collection: Fetch was cancelled"),{success:!1,error:"Request cancelled",status:0}):(this.errors={fetch:a.message},this.emit("fetch:error",{message:a.message,error:a}),{success:!1,error:a.message,status:a.status||500})}finally{this.loading=!1,this.emit("fetch:end")}}async updateParams(e,t=!1,s=0){return await this.setParams({...this.params,...e},t,s)}async setParams(e,t=!1,s=0){return this.params=e,t&&this.restEnabled?s>0?(this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((e,t)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const t=await this.fetch();e(t)}catch(s){t(s)}},s)})):this.fetch():Promise.resolve(this)}async fetchOne(e,t={}){if(!e)return console.warn("Collection: fetchOne requires an ID"),null;if(!this.restEnabled)return console.info("Collection: REST disabled, cannot fetch single item"),null;try{const s=new this.ModelClass({id:e},{endpoint:this.endpoint,collection:this}),i=await s.fetch(t);if(i.success){if(!0===t.addToCollection){const e=this.get(s.id);e?!1!==t.merge&&e.set(s.attributes):this.add(s,{silent:t.silent})}return s}return console.warn("Collection: fetchOne failed -",i.error||"Unknown error"),null}catch(s){return console.error("Collection: fetchOne error -",s.message),null}}async download(e="json",t={}){if(!this.restEnabled)return console.warn("Collection: REST is not enabled, cannot download from remote."),{success:!1,message:"Remote downloads are not enabled for this collection."};const s=this.buildUrl(),i={...this.params};delete i.start,delete i.size,i.download_format=e;const a=`export-${this.getModelName().toLowerCase()}.${e}`,r={json:"application/json",csv:"text/csv"}[e]||"*/*";return this.rest.download(s,i,{...t,filename:a,headers:{Accept:r}})}parse(e){return e.data&&Array.isArray(e.data.data)?(this.meta={size:e.data.size||10,start:e.data.start||0,count:e.data.count||0,status:e.data.status,graph:e.data.graph,...e.meta},e.data.data):Array.isArray(e.data)?e.data:Array.isArray(e)?e:[e]}add(e,t={}){const s=Array.isArray(e)?e:[e],i=[];for(const a of s){let e;e=a instanceof this.ModelClass?a:new this.ModelClass(a,{endpoint:this.endpoint,collection:this});const s=this.models.findIndex(t=>t.id===e.id);-1!==s?!1!==t.merge&&this.models[s].set(e.attributes):(this.models.push(e),i.push(e))}return!t.silent&&i.length>0&&(this.emit("add",{models:i,collection:this}),this.emit("update",{collection:this})),i}remove(e,t={}){const s=Array.isArray(e)?e:[e],i=[];for(const a of s){let e=-1;if(e="string"==typeof a||"number"==typeof a?this.models.findIndex(e=>e.id==a):this.models.indexOf(a),-1!==e){const t=this.models.splice(e,1)[0];i.push(t)}}return!t.silent&&i.length>0&&(this.emit("remove",{models:i,collection:this}),this.emit("update",{collection:this})),i}reset(e=null,t={}){const s=[...this.models];return this.models=[],e&&this.add(e,{silent:!0,...t}),t.silent||this.emit("reset",{collection:this,previousModels:s}),this}get(e){return this.models.find(t=>t.id==e)}at(e){return this.models[e]}length(){return this.models.length}isEmpty(){return 0===this.models.length}where(e){return"function"==typeof e?this.models.filter(e):"object"==typeof e?this.models.filter(t=>Object.entries(e).every(([e,s])=>t.get(e)===s)):[]}findWhere(e){const t=this.where(e);return t.length>0?t[0]:void 0}forEach(e,t){if("function"!=typeof e)throw new TypeError("Callback must be a function");return this.models.forEach((s,i)=>{e.call(t,s,i,this)}),this}sort(e,t={}){if("string"==typeof e){const t=e;e=(e,s)=>{const i=e.get(t),a=s.get(t);return i<a?-1:i>a?1:0}}return this.models.sort(e),t.silent||this.trigger("sort",{collection:this}),this}toJSON(){return this.models.map(e=>e.toJSON())}cancel(){return!(!this.currentRequest||!this.abortController||(console.info("Collection: Manually cancelling active request"),this.abortController.abort(),0))}isFetching(){return!!this.currentRequest}buildUrl(){return this.endpoint}*[Symbol.iterator](){for(const e of this.models)yield e}static fromArray(e,t=[],s={}){const i=new this(e,s);return i.add(t,{silent:!0}),i}}Object.assign(Collection.prototype,e.EventEmitter);class Group extends Model{constructor(e={}){super(e,{endpoint:"/api/group"})}}class GroupList extends Collection{constructor(e={}){super({ModelClass:Group,endpoint:"/api/group",size:10,...e})}}const t={org:"Organization",division:"Division",department:"Department",team:"Team",merchant:"Merchant",partner:"Partner",client:"Client",iso:"ISO",sales:"Sales",reseller:"Reseller",location:"Location",region:"Region",route:"Route",project:"Project",inventory:"Inventory",test:"Testing",misc:"Miscellaneous",qa:"Quality Assurance"},s=Object.entries(t).map(([e,t])=>({value:e,label:t})),i={create:{title:"Create Group",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name"},{name:"kind",type:"select",label:"Group Kind",required:!0,options:s},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300}]},edit:{title:"Edit Group",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name"},{name:"kind",type:"select",label:"Group Kind",required:!0,options:s},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300},{name:"metadata.domain",type:"text",label:"Default Domain",placeholder:"Enter Domain"},{name:"metadata.portal",type:"text",label:"Default Portal",placeholder:"Enter Portal URL"},{name:"is_active",type:"switch",label:"Is Active",cols:4}]},detailed:{title:"Group Details",fields:[{type:"header",text:"Profile Information",level:4,class:"text-primary mb-3"},{type:"group",columns:{xs:12,md:4},fields:[{type:"image",name:"avatar",size:"lg",imageSize:{width:200,height:200},placeholder:"Upload your avatar",help:"Square images work best",columns:12},{name:"is_active",type:"switch",label:"Is Active",columns:12}]},{type:"group",columns:{xs:12,md:8},title:"Details",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name",columns:12},{name:"kind",type:"select",label:"Group Kind",required:!0,columns:12,options:[{value:"org",label:"Organization"},{value:"team",label:"Team"},{value:"department",label:"Department"},{value:"merchant",label:"Merchant"},{value:"iso",label:"ISO"},{value:"group",label:"Group"}]},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300,columns:12}]},{type:"group",columns:12,title:"Account Settings",class:"pt-3",fields:[{type:"select",name:"metadata.timezone",label:"Timezone",columns:6,options:[{value:"America/New_York",text:"Eastern Time"},{value:"America/Chicago",text:"Central Time"},{value:"America/Denver",text:"Mountain Time"},{value:"America/Los_Angeles",text:"Pacific Time"},{value:"UTC",text:"UTC"}]},{type:"select",name:"metadata.language",label:"Language",columns:6,options:[{value:"en",text:"English"},{value:"es",text:"Spanish"},{value:"fr",text:"French"},{value:"de",text:"German"}]},{type:"switch",name:"metadata.notify.email",label:"Email Notifications",columns:4},{type:"switch",name:"metadata.profile_public",label:"Public Profile",columns:4}]}]}};Group.EDIT_FORM=i.edit,Group.CREATE_FORM=i.create,Group.GroupKindOptions=s,Group.GroupKinds=t;class User extends Model{constructor(e={}){super(e,{endpoint:"/api/user"})}hasPermission(e){if(Array.isArray(e))return e.some(e=>this.hasPermission(e));const t=e.startsWith("sys."),s=t?e.substring(4):e;return!!this._hasPermission(s)||!(t||!this.member||!this.member.hasPermission(e))}_hasPermission(e){const t=this.get("permissions");return!!t&&1==t[e]}hasPerm(e){return this.hasPermission(e)}}User.PERMISSIONS=[{name:"manage_users",label:"Manage Users"},{name:"view_groups",label:"View Groups"},{name:"manage_groups",label:"Manage Groups"},{name:"view_metrics",label:"View System Metrics"},{name:"manage_metrics",label:"Manage System Metrics"},{name:"view_logs",label:"View Logs"},{name:"view_incidents",label:"View Incidents"},{name:"manage_incidents",label:"Manage Incidents"},{name:"view_tickets",label:"View Tickets"},{name:"manage_tickets",label:"Manage Tickets"},{name:"view_admin",label:"View Admin"},{name:"view_jobs",label:"View Jobs"},{name:"manage_jobs",label:"Manage Jobs"},{name:"view_global",label:"View Global"},{name:"manage_notifications",label:"Manage Notifications"},{name:"manage_files",label:"Manage Files"},{name:"force_single_session",label:"Force Single Session"},{name:"file_vault",label:"Access File Vault"},{name:"manage_aws",label:"Manage AWS"},{name:"manage_docit",label:"Manage DocIt"}],User.PERMISSION_FIELDS=[...User.PERMISSIONS.map(e=>({name:`permissions.${e.name}`,type:"switch",label:e.label,columns:4}))];const a={create:{title:"Create User",fields:[{name:"email",type:"text",label:"Email",required:!0},{name:"phone_number",type:"text",label:"Phone number",columns:12},{name:"display_name",type:"text",label:"Display Name"}]},edit:{title:"Edit User",fields:[{name:"email",type:"email",label:"Email",columns:12},{name:"display_name",type:"text",label:"Display Name",columns:12},{name:"phone_number",type:"text",label:"Phone number",columns:12},{type:"collection",name:"org",label:"Organization",Collection:GroupList,labelField:"name",valueField:"id",columns:12}]},permissions:{title:"Edit Permissions",fields:User.PERMISSIONS_FIELDS}},r={profile:{title:"User Profile",columns:2,fields:[{name:"id",label:"User ID",type:"number",columns:4},{name:"username",label:"Username",type:"text",format:"lowercase",columns:4},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",columns:4},{name:"email",label:"Email",type:"email",columns:4},{name:"display_name",label:"Display Name",type:"text",columns:4},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",columns:4},{name:"org.name",label:"Organization",type:"text",columns:4},{name:"phone_number",label:"Phone Number",type:"text",columns:4}]},activity:{title:"User Activity",columns:2,fields:[{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6}]},detailed:{title:"Detailed User Information",columns:2,showEmptyValues:!0,emptyValueText:"Not set",fields:[{name:"id",label:"User ID",type:"number",colSize:3},{name:"display_name",label:"Display Name",type:"text",format:'capitalize|default("Unnamed User")',colSize:9},{name:"username",label:"Username",type:"text",format:"lowercase",colSize:6},{name:"email",label:"Email Address",type:"email",colSize:6},{name:"phone_number",label:"Phone Number",type:"phone",format:'phone|default("Not provided")',colSize:6},{name:"is_active",label:"Account Status",type:"boolean",colSize:6},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6},{name:"avatar.url",label:"Avatar",type:"url",colSize:12},{name:"permissions",label:"User Permissions",type:"dataview",dataViewColumns:2,showEmptyValues:!1},{name:"metadata",label:"User Metadata",type:"dataview",dataViewColumns:1},{name:"avatar",label:"Avatar Details",type:"dataview",dataViewColumns:1}]},permissions:{title:"User Permissions",columns:1,fields:[{name:"display_name",label:"User",type:"text",format:"capitalize",columns:12},{name:"permissions",label:"Assigned Permissions",type:"dataview",dataViewColumns:3,showEmptyValues:!1,colSize:12}]},summary:{title:"User Summary",columns:3,fields:[{name:"display_name",label:"Name",type:"text",format:"capitalize|truncate(30)"},{name:"email",label:"Email",type:"email"},{name:"is_active",label:"Status",type:"boolean"},{name:"last_activity",label:"Last Seen",type:"datetime",format:"relative",colSize:12}]}};User.DATA_VIEW=r.detailed,User.EDIT_FORM=a.edit,User.ADD_FORM=a.create;class UserDevice extends Model{constructor(e={}){super(e,{endpoint:"/api/user/device"})}static async getByDuid(e){const t=new UserDevice,s=await t.rest.GET("/api/user/device/lookup",{duid:e});return s.success&&s.data&&s.data.data?new UserDevice(s.data.data):null}}class UserDeviceLocation extends Model{constructor(e={}){super(e,{endpoint:"/api/user/device/location"})}}class ContextMenu extends e.View{constructor(e={}){super({tagName:"div",className:"context-menu-view dropdown",...e}),this.config=e.contextMenu||e.config||{},this.context=e.context||{}}async renderTemplate(){const e=this.config.items||[];if(0===e.length)return"";const t=this.config.icon||"bi-three-dots-horizontal",s=this.config.buttonClass||"btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1",i=`context-menu-${this.id}`;return`\n <button class="${s}" type="button" id="${i}" data-bs-toggle="dropdown" aria-expanded="false">\n <i class="${t}"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="${i}">\n ${e.map(e=>this.buildMenuItemHTML(e)).join("")}\n </ul>\n `}buildMenuItemHTML(e){if("divider"===e.type||e.separator)return'<li><hr class="dropdown-divider"></li>';const t=e.icon?`<i class="${e.icon} me-2"></i>`:"",s=e.label||"",i=`dropdown-item ${e.danger?"text-danger":""} ${e.disabled?"disabled":""}`,a=e.action||"";return e.href?`<li><a class="${i}" href="${e.href}" target="${e.target||"_self"}">${t}${s}</a></li>`:`<li><a class="${i}" href="#" data-action="menu-item-click" data-item-action="${a}">${t}${s}</a></li>`}async onActionMenuItemClick(e,t){e.preventDefault();const s=t.getAttribute("data-item-action");if(!s)return;const i=this.config.items.find(e=>e.action===s);i&&!i.disabled&&("function"==typeof i.handler?i.handler(this.context,e,t):this.parent.events.dispatch(s,e,t),this.closeDropdown())}closeDropdown(){const e=this.element.querySelector('[data-bs-toggle="dropdown"]');if(e){const t=window.bootstrap?.Dropdown.getInstance(e);t?.hide()}}}exports.Collection=Collection,exports.ContextMenu=ContextMenu,exports.Group=Group,exports.GroupForms=i,exports.GroupList=GroupList,exports.Model=Model,exports.ToastService=class{constructor(e={}){this.options={containerId:"toast-container",position:"top-end",autohide:!0,defaultDelay:5e3,maxToasts:5,...e},this.toasts=/* @__PURE__ */new Map,this.toastCounter=0,this.init()}init(){this.createContainer()}createContainer(){let e=document.getElementById(this.options.containerId);e||(e=document.createElement("div"),e.id=this.options.containerId,e.className=`toast-container position-fixed ${this.getPositionClasses()}`,e.style.zIndex="1070",e.setAttribute("aria-live","polite"),e.setAttribute("aria-atomic","true"),document.body.appendChild(e)),this.container=e}getPositionClasses(){const e={"top-start":"top-0 start-0 p-3","top-center":"top-0 start-50 translate-middle-x p-3","top-end":"top-0 end-0 p-3","middle-start":"top-50 start-0 translate-middle-y p-3","middle-center":"top-50 start-50 translate-middle p-3","middle-end":"top-50 end-0 translate-middle-y p-3","bottom-start":"bottom-0 start-0 p-3","bottom-center":"bottom-0 start-50 translate-middle-x p-3","bottom-end":"bottom-0 end-0 p-3"};return e[this.options.position]||e["top-end"]}success(e,t={}){return this.show(e,"success",{icon:"bi-check-circle-fill",...t})}error(e,t={}){return this.show(e,"error",{icon:"bi-exclamation-triangle-fill",autohide:!1,...t})}info(e,t={}){return this.show(e,"info",{icon:"bi-info-circle-fill",...t})}warning(e,t={}){return this.show(e,"warning",{icon:"bi-exclamation-triangle-fill",...t})}plain(e,t={}){return this.show(e,"plain",{...t})}show(e,t="info",s={}){this.enforceMaxToasts();const i="toast-"+ ++this.toastCounter,a={title:this.getDefaultTitle(t),icon:this.getDefaultIcon(t),autohide:this.options.autohide,delay:this.options.defaultDelay,dismissible:!0,...s},r=this.createToastElement(i,e,t,a);if(this.container.appendChild(r),"undefined"==typeof bootstrap)throw new Error("Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.");const o=new bootstrap.Toast(r,{autohide:a.autohide,delay:a.delay});return this.toasts.set(i,{element:r,bootstrap:o,type:t,message:e}),r.addEventListener("hidden.bs.toast",()=>{this.cleanup(i)}),o.show(),{id:i,hide:()=>{try{o.hide()}catch(e){console.warn("Error hiding toast:",e)}},dispose:()=>this.cleanup(i),updateProgress:s.updateProgress||null}}showView(e,t="info",s={}){this.enforceMaxToasts();const i="toast-"+ ++this.toastCounter,a={title:s.title||this.getDefaultTitle(t),icon:s.icon||this.getDefaultIcon(t),autohide:this.options.autohide,delay:this.options.defaultDelay,dismissible:!0,...s},r=this.createViewToastElement(i,e,t,a);if(this.container.appendChild(r),"undefined"==typeof bootstrap)throw new Error("Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.");const o=new bootstrap.Toast(r,{autohide:a.autohide,delay:a.delay});this.toasts.set(i,{element:r,bootstrap:o,type:t,view:e,message:"View toast"}),r.addEventListener("hidden.bs.toast",()=>{this.cleanupView(i)});const n=r.querySelector(".toast-view-body");return n&&e&&e.render(!0,n),o.show(),{id:i,view:e,hide:()=>{try{o.hide()}catch(e){console.warn("Error hiding view toast:",e)}},dispose:()=>this.cleanupView(i),updateProgress:t=>{e&&"function"==typeof e.updateProgress&&e.updateProgress(t)}}}createToastElement(e,t,s,i){const a=document.createElement("div");a.id=e,a.className=`toast toast-service-${s}`,a.setAttribute("role","alert"),a.setAttribute("aria-live","assertive"),a.setAttribute("aria-atomic","true");const r=i.title||i.icon?this.createToastHeader(i,s):"",o=this.createToastBody(t,i.icon&&!i.title);return a.innerHTML=`\n ${r}\n ${o}\n `,a}createViewToastElement(e,t,s,i){const a=document.createElement("div");a.id=e,a.className=`toast toast-service-${s}`,a.setAttribute("role","alert"),a.setAttribute("aria-live","assertive"),a.setAttribute("aria-atomic","true");const r=i.title||i.icon?this.createToastHeader(i,s):"",o=this.createViewToastBody();return a.innerHTML=`\n ${r}\n ${o}\n `,a}createViewToastBody(){return'\n <div class="toast-body p-0">\n <div class="toast-view-body p-3"></div>\n </div>\n '}createToastHeader(e,t){const s=e.icon?`<i class="${e.icon} toast-service-icon me-2"></i>`:"",i=e.title?`<strong class="me-auto">${s}${this.escapeHtml(e.title)}</strong>`:"",a=e.showTime?`<small class="text-muted">${this.getTimeString()}</small>`:"",r=e.dismissible?'<button type="button" class="btn-close toast-service-close" data-bs-dismiss="toast" aria-label="Close"></button>':"";return i||a||r?`\n <div class="toast-header">\n ${i}\n ${a}\n ${r}\n </div>\n `:""}createToastBody(e,t=!1){return`\n <div class="toast-body d-flex align-items-center">\n ${t?`<i class="${this.getDefaultIcon("info")} toast-service-icon me-2"></i>`:""}\n <span>${this.escapeHtml(e)}</span>\n </div>\n `}getDefaultTitle(e){return{success:"Success",error:"Error",warning:"Warning",info:"Information",plain:""}[e]||"Notification"}getDefaultIcon(e){return{success:"bi-check-circle-fill",error:"bi-exclamation-triangle-fill",warning:"bi-exclamation-triangle-fill",info:"bi-info-circle-fill",plain:""}[e]||"bi-info-circle-fill"}enforceMaxToasts(){if(Array.from(this.toasts.values()).length>=this.options.maxToasts){const e=this.toasts.keys().next().value,t=this.toasts.get(e);t&&t.bootstrap.hide()}}cleanup(e){const t=this.toasts.get(e);if(t){try{t.bootstrap.dispose()}catch(s){console.warn("Error disposing toast:",s)}t.element&&t.element.parentNode&&t.element.parentNode.removeChild(t.element),this.toasts.delete(e)}}cleanupView(e){const t=this.toasts.get(e);if(t){if(t.view&&"function"==typeof t.view.dispose)try{t.view.dispose()}catch(s){console.warn("Error disposing view in toast:",s)}try{t.bootstrap.dispose()}catch(s){console.warn("Error disposing toast:",s)}t.element&&t.element.parentNode&&t.element.parentNode.removeChild(t.element),this.toasts.delete(e)}}hideAll(){this.toasts.forEach((e,t)=>{e.bootstrap.hide()})}clearAll(){this.toasts.forEach((e,t)=>{this.cleanup(t)})}getTimeString(){/* @__PURE__ */
|
|
2
|
+
return(new Date).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}dispose(){this.clearAll(),this.container&&this.container.parentNode&&this.container.parentNode.removeChild(this.container)}getStats(){const e={total:this.toasts.size,byType:{}};return this.toasts.forEach(t=>{e.byType[t.type]=(e.byType[t.type]||0)+1}),e}setOptions(e){this.options={...this.options,...e},e.position&&this.container&&(this.container.className=`toast-container position-fixed ${this.getPositionClasses()}`)}},exports.User=User,exports.UserDataView=r,exports.UserDevice=UserDevice,exports.UserDeviceList=class extends Collection{constructor(e={}){super({ModelClass:UserDevice,endpoint:"/api/user/device",...e})}},exports.UserDeviceLocation=UserDeviceLocation,exports.UserDeviceLocationList=class extends Collection{constructor(e={}){super({ModelClass:UserDeviceLocation,endpoint:"/api/user/device/location",...e})}},exports.UserForms=a,exports.UserList=class extends Collection{constructor(e={}){super({ModelClass:User,endpoint:"/api/user",...e})}};
|
|
3
|
+
//# sourceMappingURL=ContextMenu-TK4-wzDb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextMenu-TK4-wzDb.js","sources":["../../src/core/Model.js","../../src/core/Collection.js","../../src/core/models/Group.js","../../src/core/models/User.js","../../src/core/views/feedback/ContextMenu.js","../../src/core/services/ToastService.js"],"sourcesContent":["/**\n * Model - Base class for models with REST API support\n * Provides CRUD operations for API resources with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits 'change' events when data is modified via set()\n * Emits 'change:attributeName' for specific attribute changes\n *\n * Standard Events:\n * - 'change' - Emitted when any model data changes\n * - 'change:fieldName' - Emitted when specific field changes\n *\n * @example\n * const user = new User({ name: 'John', email: 'john@example.com' });\n *\n * // Listen for any changes\n * user.on('change', (model) => {\n * console.log('User model changed');\n * view.render();\n * });\n *\n * // Listen for specific field changes\n * user.on('change:name', (newName, model) => {\n * console.log('Name changed to:', newName);\n * });\n *\n * // Trigger events by changing data\n * user.set('name', 'Jane'); // Emits 'change' and 'change:name'\n * user.set({ name: 'Bob', email: 'bob@example.com' }); // Emits 'change' and individual field events\n */\n\nimport MOJOUtils from '@core/utils/MOJOUtils.js';\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport rest from '@core/Rest.js';\n\nclass Model {\n constructor(data = {}, options = {}) {\n this.endpoint = options.endpoint || this.constructor.endpoint || '';\n this.id = data.id || null;\n this.attributes = { ...data };\n this._ = this.attributes;\n this.originalAttributes = { ...data };\n this.errors = {};\n this.loading = false;\n this.rest = rest;\n\n // Event system via EventEmitter mixin (applied to prototype)\n\n // Configuration options\n this.options = {\n idAttribute: 'id',\n timestamps: true,\n ...options\n };\n }\n\n getContextValue(key) {\n return this.get(key);\n }\n\n /**\n * Get attribute value with support for dot notation and pipe formatting\n * @param {string} key - Attribute key with optional pipes (e.g., \"name|uppercase\")\n * @returns {*} Attribute value, possibly formatted\n */\n get(key) {\n // Check if key exists as an instance field first (for 'id', 'endpoint', etc.)\n if (!key.includes('.') && !key.includes('|') && this[key] !== undefined) {\n // If it's a function, call it and return the result\n if (typeof this[key] === 'function') {\n return this[key]();\n }\n return this[key];\n }\n\n // Use MOJOUtils for all attribute access with pipes and dot notation\n return MOJOUtils.getContextData(this.attributes, key);\n }\n\n /**\n * Set attribute value(s)\n * @param {string|object} key - Attribute key or object of key-value pairs\n * @param {*} value - Attribute value (if key is string)\n * @param {object} options - Options (silent: true to not trigger change event)\n */\n set(key, value, options = {}) {\n const previousAttributes = JSON.parse(JSON.stringify(this.attributes)); // Deep copy\n let hasChanged = false;\n\n if (typeof key === 'object') {\n // Set multiple attributes\n for (const [attrKey, attrValue] of Object.entries(key)) {\n hasChanged = this._setNestedAttribute(attrKey, attrValue) || hasChanged;\n }\n if (key.id !== undefined) {\n this.id = key.id;\n }\n } else {\n // Set single attribute\n if (key === 'id') {\n this.id = value;\n hasChanged = true;\n } else {\n hasChanged = this._setNestedAttribute(key, value);\n }\n }\n\n // Trigger change event if data changed and not silent\n if (hasChanged && !options.silent) {\n this.emit('change', this);\n\n // Trigger specific attribute change events\n if (typeof key === 'string') {\n this.emit(`change:${key}`, value, this);\n } else {\n for (const [attr, val] of Object.entries(key)) {\n // Get the final value that was actually set (after nested expansion)\n const finalValue = this._getNestedValue(attr);\n if (JSON.stringify(this._getNestedValue(attr, previousAttributes)) !== JSON.stringify(finalValue)) {\n this.emit(`change:${attr}`, finalValue, this);\n }\n }\n }\n }\n }\n\n /**\n * Set a nested attribute using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {*} value - Value to set\n * @returns {boolean} - Whether the value changed\n */\n _setNestedAttribute(key, value) {\n if (!key.includes('.')) {\n // Simple attribute\n const oldValue = this.attributes[key];\n this.attributes[key] = value;\n this[key] = value;\n return oldValue !== value;\n }\n\n // Nested attribute with dot notation\n const keys = key.split('.');\n const topLevelKey = keys[0];\n \n // Ensure the top-level object exists\n if (!this.attributes[topLevelKey] || typeof this.attributes[topLevelKey] !== 'object') {\n this.attributes[topLevelKey] = {};\n }\n if (!this[topLevelKey] || typeof this[topLevelKey] !== 'object') {\n this[topLevelKey] = {};\n }\n\n // Get the old value for comparison\n const oldValue = this._getNestedValue(key);\n\n // Navigate to the nested location and set the value\n let attrTarget = this.attributes[topLevelKey];\n let instanceTarget = this[topLevelKey];\n \n for (let i = 1; i < keys.length - 1; i++) {\n const currentKey = keys[i];\n \n if (!attrTarget[currentKey] || typeof attrTarget[currentKey] !== 'object') {\n attrTarget[currentKey] = {};\n }\n if (!instanceTarget[currentKey] || typeof instanceTarget[currentKey] !== 'object') {\n instanceTarget[currentKey] = {};\n }\n \n attrTarget = attrTarget[currentKey];\n instanceTarget = instanceTarget[currentKey];\n }\n\n // Set the final value\n const finalKey = keys[keys.length - 1];\n attrTarget[finalKey] = value;\n instanceTarget[finalKey] = value;\n\n return JSON.stringify(oldValue) !== JSON.stringify(value);\n }\n\n /**\n * Get a nested value using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {object} source - Source object (defaults to this.attributes)\n * @returns {*} - The nested value\n */\n _getNestedValue(key, source = this.attributes) {\n if (!key.includes('.')) {\n return source[key];\n }\n\n const keys = key.split('.');\n let current = source;\n \n for (const k of keys) {\n if (current == null || typeof current !== 'object') {\n return undefined;\n }\n current = current[k];\n }\n \n return current;\n }\n\n getData() {\n return this.attributes;\n }\n\n getId() {\n return this.id;\n }\n\n /**\n * Fetch model data from API with request deduplication and cancellation\n * @param {object} options - Request options\n * @param {number} options.debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(options = {}) {\n let url = options.url;\n if (!url) {\n const id = options.id || this.getId();\n if (!id && this.options.requiresId !== false) {\n throw new Error('Model: ID is required for fetching');\n }\n url = this.buildUrl(id);\n }\n const requestKey = JSON.stringify({url, params: options.params});\n\n // Handle debounced fetch\n if (options.debounceMs && options.debounceMs > 0) {\n return this._debouncedFetch(requestKey, options);\n }\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Model: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Model: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Model: Rate limited, skipping fetch');\n return this;\n }\n\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, options, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Model: Request was cancelled');\n return this;\n }\n throw error;\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Handle debounced fetch requests\n * @param {string} requestKey - Unique key for this request\n * @param {object} options - Fetch options\n * @returns {Promise} Promise that resolves with REST response\n */\n async _debouncedFetch(requestKey, options) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch({ ...options, debounceMs: 0 });\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, options.debounceMs);\n });\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} options - Request options\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, options, abortController) {\n try {\n if (options.graph && (!options.params || !options.params.graph)) {\n if (!options.params) options.params = {};\n options.params.graph = options.graph;\n }\n const response = await this.rest.GET(url, options.params, {\n signal: abortController.signal\n });\n\n if (response.success) {\n if (response.data.status) {\n this.originalAttributes = { ...this.attributes };\n this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Model: Fetch was cancelled');\n throw error;\n }\n\n this.errors = { fetch: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Save model to API (create or update)\n * @param {object} data - Data to save to the model\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async save(data, options = {}) {\n const isNew = !this.id;\n const method = isNew ? 'POST' : 'PUT';\n const url = isNew ? this.buildUrl() : this.buildUrl(this.id);\n\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest[method](url, data, options.params);\n\n if (response.success) {\n if (response.data.status) {\n // Update model on success\n this.originalAttributes = { ...this.attributes };\n this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response; // Always return the full response\n\n } catch (error) {\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n\n /**\n * Delete model from API\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async destroy(options = {}) {\n if (!this.id) {\n this.errors = { destroy: 'Cannot destroy model without ID' };\n return {\n success: false,\n error: 'Cannot destroy model without ID',\n status: 400\n };\n }\n\n const url = this.buildUrl(this.id);\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest.DELETE(url, options.params);\n\n if (response.success) {\n // Clear model data on success\n this.attributes = {};\n this.originalAttributes = {};\n this.id = null;\n this.errors = {};\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n\n } catch (error) {\n this.errors = { destroy: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Check if model has been modified\n * @returns {boolean} True if model has unsaved changes\n */\n isDirty() {\n return JSON.stringify(this.attributes) !== JSON.stringify(this.originalAttributes);\n }\n\n /**\n * Get attributes that have changed since last save\n * @returns {object} Object containing only changed attributes\n */\n getChangedAttributes() {\n const changed = {};\n\n for (const [key, value] of Object.entries(this.attributes)) {\n if (this.originalAttributes[key] !== value) {\n changed[key] = value;\n }\n }\n\n return changed;\n }\n\n /**\n * Reset model to original state\n */\n reset() {\n this.attributes = { ...this.originalAttributes };\n this._ = this.attributes;\n this.errors = {};\n }\n\n /**\n * Build URL for API requests\n * @param {string|number} id - Optional ID to append to URL\n * @returns {string} Complete API URL\n */\n buildUrl(id = null) {\n let url = this.endpoint;\n if (id) {\n url = url.endsWith('/') ? `${url}${id}` : `${url}/${id}`;\n }\n return url;\n }\n\n /**\n * Convert model to JSON\n * @returns {object} Model attributes as plain object\n */\n toJSON() {\n return {\n id: this.id,\n ...this.attributes\n };\n }\n\n /**\n * Validate model attributes\n * @returns {boolean} True if valid, false if validation errors exist\n */\n validate() {\n this.errors = {};\n\n // Override in subclasses for custom validation\n if (this.constructor.validations) {\n for (const [field, rules] of Object.entries(this.constructor.validations)) {\n this.validateField(field, rules);\n }\n }\n\n return Object.keys(this.errors).length === 0;\n }\n\n /**\n * Validate a single field\n * @param {string} field - Field name\n * @param {object|array} rules - Validation rules\n */\n validateField(field, rules) {\n const value = this.get(field);\n const rulesArray = Array.isArray(rules) ? rules : [rules];\n\n for (const rule of rulesArray) {\n if (typeof rule === 'function') {\n const result = rule(value, this);\n if (result !== true) {\n this.errors[field] = result || `${field} is invalid`;\n break;\n }\n } else if (typeof rule === 'object') {\n if (rule.required && (value === undefined || value === null || value === '')) {\n this.errors[field] = rule.message || `${field} is required`;\n break;\n }\n if (rule.minLength && value && value.length < rule.minLength) {\n this.errors[field] = rule.message || `${field} must be at least ${rule.minLength} characters`;\n break;\n }\n if (rule.maxLength && value && value.length > rule.maxLength) {\n this.errors[field] = rule.message || `${field} must be no more than ${rule.maxLength} characters`;\n break;\n }\n if (rule.pattern && value && !rule.pattern.test(value)) {\n this.errors[field] = rule.message || `${field} format is invalid`;\n break;\n }\n }\n }\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Static method to create and fetch a model by ID\n * @param {string|number} id - Model ID\n * @param {object} options - Options\n * @returns {Promise<RestModel>} Promise that resolves with fetched model\n */\n static async find(id, options = {}) {\n const model = new this({}, options);\n await model.fetch({ id, ...options });\n return model;\n }\n\n /**\n * Static method to create a new model with data\n * @param {object} data - Model data\n * @param {object} options - Options\n * @returns {RestModel} New model instance\n */\n static create(data = {}, options = {}) {\n return new this(data, options);\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Model: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n\n // Cancel debounced fetch if exists\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n this.debouncedFetchTimeout = null;\n return true;\n }\n\n return false;\n }\n\n /**\n * Check if model has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n async showError(message) {\n await Dialog.alert(message, 'Error', {\n size: 'md',\n class: 'text-danger'\n });\n }\n}\n\nObject.assign(Model.prototype, EventEmitter);\n\nexport default Model;\n","/**\n * Collection - Class for managing arrays of Model instances\n * Provides methods for fetching and managing collections of models with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits events when collection is modified\n *\n * Standard Events:\n * - 'add' - Emitted when models are added to the collection\n * - 'remove' - Emitted when models are removed from the collection\n * - 'update' - Emitted when collection is modified (after add/remove)\n * - 'reset' - Emitted when collection is reset (all models replaced)\n *\n * @example\n * const users = new UserCollection();\n *\n * // Listen for collection changes\n * users.on('add', ({ models, collection }) => {\n * console.log('Added', models.length, 'users');\n * updateUI();\n * });\n *\n * users.on('remove', ({ models, collection }) => {\n * console.log('Removed', models.length, 'users');\n * updateUI();\n * });\n *\n * // Add models - triggers 'add' and 'update' events\n * users.add([\n * new User({ name: 'John' }),\n * new User({ name: 'Jane' })\n * ]);\n *\n * Usage Examples:\n *\n * // Preloaded Data (no REST fetching)\n * const collection = new MyCollection({ preloaded: true });\n * collection.add(new MyModel({...}));\n * // collection.fetch() will be skipped if data already exists\n *\n * // REST Data (fetch from API)\n * const collection = new MyCollection({ preloaded: false }); // default\n * await collection.fetch(); // Will make API call\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\nclass Collection {\n constructor(options = {}, data = null) {\n // Handle case where first argument is data instead of ModelClass\n if (Array.isArray(options)) {\n // First argument is an array, treat it as data\n data = options;\n options = data || {};\n } else {\n data = data || options.data || [];\n }\n this.ModelClass = options.ModelClass || Model;\n this.models = [];\n this.loading = false;\n this.errors = {};\n this.meta = {};\n this.rest = rest;\n if (data) {\n this.add(data);\n }\n\n // Initialize params with defaults - single source of truth for query state\n this.params = {\n start: 0,\n size: options.size || 10,\n ...options.params\n };\n\n // Set up endpoint\n this.endpoint = options.endpoint || this.ModelClass.endpoint || '';\n if (!this.endpoint) {\n let tmp = new this.ModelClass();\n this.endpoint = tmp.endpoint;\n }\n\n // Automatic REST detection based on endpoint\n this.restEnabled = this.endpoint ? true : false;\n\n // Allow explicit override\n if (options.restEnabled !== undefined) {\n this.restEnabled = options.restEnabled;\n }\n\n // Configuration\n this.options = {\n parse: true,\n reset: true,\n preloaded: false,\n ...options\n };\n\n // Event system via EventEmitter mixin (applied to prototype)\n }\n\n getModelName() {\n return this.ModelClass.name;\n }\n\n /**\n * Fetch collection data from API\n * @param {object} additionalParams - Additional parameters to merge for this fetch only\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(additionalParams = {}) {\n const requestKey = JSON.stringify({ ...this.params, ...additionalParams });\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Collection: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Collection: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.options.rateLimiting && this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Collection: Rate limited, skipping fetch');\n return { success: true, message: 'Rate limited, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if not REST enabled\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, skipping fetch');\n return { success: true, message: 'REST disabled, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if preloaded is true and we already have data\n if (this.options.preloaded && this.models.length > 0) {\n console.info('Collection: Using preloaded data, skipping fetch');\n return { success: true, message: 'Using preloaded data, skipping fetch', data: { data: this.toJSON() } };\n }\n\n const url = this.buildUrl();\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, additionalParams, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Collection: Request was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} additionalParams - Additional parameters\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, additionalParams, abortController) {\n const fetchParams = { ...this.params, ...additionalParams };\n console.log('Fetching collection data from', url, fetchParams);\n try {\n this.emit(\"fetch:start\");\n const response = await this.rest.GET(url, fetchParams, {\n signal: abortController.signal\n });\n\n if (response.success && response.data.status) {\n const data = this.options.parse ? this.parse(response) : response.data;\n\n if (this.options.reset || additionalParams.reset !== false) {\n this.reset();\n }\n\n this.add(data, { silent: additionalParams.silent });\n this.errors = {};\n this.emit(\"fetch:success\");\n } else {\n if (response.data && response.data.error) {\n this.errors = response.data;\n this.emit(\"fetch:error\", { message: response.data.error, error: response.data });\n } else {\n this.errors = response.errors || {};\n this.emit(\"fetch:error\", { error: response.errors });\n }\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Collection: Fetch was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n\n this.errors = { fetch: error.message };\n this.emit(\"fetch:error\", { message: error.message, error: error });\n\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n this.emit(\"fetch:end\");\n }\n }\n\n /**\n * Update collection parameters and optionally fetch new data\n * @param {object} newParams - Parameters to update\n * @param {boolean} autoFetch - Whether to automatically fetch after updating params\n * @param {number} debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response if autoFetch=true, or collection if autoFetch=false\n */\n async updateParams(newParams, autoFetch = false, debounceMs = 0) {\n return await this.setParams({ ...this.params, ...newParams }, autoFetch, debounceMs);\n }\n\n async setParams(newParams, autoFetch = false, debounceMs = 0) {\n this.params = newParams;\n if (autoFetch && this.restEnabled) {\n if (debounceMs > 0) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch();\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, debounceMs);\n });\n } else {\n // For immediate fetches, the fetch method will handle cancellation\n return this.fetch();\n }\n }\n\n return Promise.resolve(this);\n }\n\n /**\n * Fetch a single model by ID\n * @param {string|number} id - Model ID to fetch\n * @param {object} options - Additional fetch options\n * @returns {Promise<Model|null>} Promise that resolves with model instance or null if not found\n */\n async fetchOne(id, options = {}) {\n if (!id) {\n console.warn('Collection: fetchOne requires an ID');\n return null;\n }\n\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, cannot fetch single item');\n return null;\n }\n\n try {\n // Create model instance with the ID and use its fetch method\n const model = new this.ModelClass({ id }, {\n endpoint: this.endpoint,\n collection: this\n });\n\n const response = await model.fetch(options);\n\n if (response.success) {\n // Optionally add to collection if not already present\n if (options.addToCollection === true) {\n const existingModel = this.get(model.id);\n if (!existingModel) {\n this.add(model, { silent: options.silent });\n } else if (options.merge !== false) {\n existingModel.set(model.attributes);\n }\n }\n\n return model;\n } else {\n console.warn('Collection: fetchOne failed -', response.error || 'Unknown error');\n return null;\n }\n } catch (error) {\n console.error('Collection: fetchOne error -', error.message);\n return null;\n }\n }\n\n /**\n * Download collection data in a specified format\n * @param {string} format - The format for the download (e.g., 'csv', 'json')\n * @param {object} options - Download options\n * @returns {Promise} Promise that resolves with the download result\n */\n async download(format = 'json', options = {}) {\n if (!this.restEnabled) {\n console.warn('Collection: REST is not enabled, cannot download from remote.');\n // Here we could implement local data export in the future.\n return { success: false, message: 'Remote downloads are not enabled for this collection.' };\n }\n\n const url = this.buildUrl();\n const downloadParams = { ...this.params };\n\n // Remove pagination params for full export\n delete downloadParams.start;\n delete downloadParams.size;\n\n // Add format param\n downloadParams.download_format = format;\n\n // Provide a default filename and content type\n const filename = `export-${this.getModelName().toLowerCase()}.${format}`;\n const contentTypes = {\n json: 'application/json',\n csv: 'text/csv'\n };\n const acceptHeader = contentTypes[format] || '*/*';\n\n return this.rest.download(url, downloadParams, {\n ...options,\n filename,\n headers: { 'Accept': acceptHeader }\n });\n }\n\n /**\n * Parse response data - override in subclasses for custom parsing\n * @param {object} response - API response\n * @returns {array} Array of model data objects\n */\n parse(response) {\n // Handle standard paginated responses with size/start/count\n if (response.data && Array.isArray(response.data.data)) {\n this.meta = {\n size: response.data.size || 10,\n start: response.data.start || 0,\n count: response.data.count || 0,\n status: response.data.status,\n graph: response.data.graph,\n ...response.meta\n };\n return response.data.data;\n }\n\n // Handle direct array responses\n if (Array.isArray(response.data)) {\n return response.data;\n }\n\n // Fallback - assume response itself is the data array\n return Array.isArray(response) ? response : [response];\n }\n\n /**\n * Add model(s) to the collection\n * @param {object|array} data - Model data or array of model data\n * @param {object} options - Options for adding models\n */\n add(data, options = {}) {\n const modelsData = Array.isArray(data) ? data : [data];\n const addedModels = [];\n\n for (const modelData of modelsData) {\n let model;\n\n if (modelData instanceof this.ModelClass) {\n model = modelData;\n } else {\n model = new this.ModelClass(modelData, {\n endpoint: this.endpoint,\n collection: this\n });\n }\n\n // Check for duplicates\n const existingIndex = this.models.findIndex(m => m.id === model.id);\n if (existingIndex !== -1) {\n if (options.merge !== false) {\n // Update existing model\n this.models[existingIndex].set(model.attributes);\n }\n } else {\n // Add new model\n this.models.push(model);\n addedModels.push(model);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && addedModels.length > 0) {\n this.emit('add', { models: addedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return addedModels;\n }\n\n /**\n * Remove model(s) from the collection\n * @param {Model|array|string|number} models - Model(s) to remove or ID(s)\n * @param {object} options - Options\n */\n remove(models, options = {}) {\n const modelsToRemove = Array.isArray(models) ? models : [models];\n const removedModels = [];\n\n for (const model of modelsToRemove) {\n let index = -1;\n\n if (typeof model === 'string' || typeof model === 'number') {\n // Remove by ID\n index = this.models.findIndex(m => m.id == model);\n } else {\n // Remove by model instance\n index = this.models.indexOf(model);\n }\n\n if (index !== -1) {\n const removedModel = this.models.splice(index, 1)[0];\n removedModels.push(removedModel);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && removedModels.length > 0) {\n this.emit('remove', { models: removedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return removedModels;\n }\n\n /**\n * Reset the collection (remove all models)\n * @param {array} models - Optional new models to set\n * @param {object} options - Options\n */\n reset(models = null, options = {}) {\n const previousModels = [...this.models];\n this.models = [];\n\n if (models) {\n this.add(models, { silent: true, ...options });\n }\n\n if (!options.silent) {\n this.emit('reset', {\n collection: this,\n previousModels\n });\n }\n\n return this;\n }\n\n /**\n * Get model by ID\n * @param {string|number} id - Model ID\n * @returns {Model|undefined} Model instance or undefined\n */\n get(id) {\n return this.models.find(model => model.id == id);\n }\n\n /**\n * Get model by index\n * @param {number} index - Model index\n * @returns {Model|undefined} Model instance or undefined\n */\n at(index) {\n return this.models[index];\n }\n\n /**\n * Get collection length\n * @returns {number} Number of models in collection\n */\n length() {\n return this.models.length;\n }\n\n /**\n * Check if collection is empty\n * @returns {boolean} True if collection has no models\n */\n isEmpty() {\n return this.models.length === 0;\n }\n\n /**\n * Find models matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {array} Array of matching models\n */\n where(criteria) {\n if (typeof criteria === 'function') {\n return this.models.filter(criteria);\n }\n\n if (typeof criteria === 'object') {\n return this.models.filter(model => {\n return Object.entries(criteria).every(([key, value]) => {\n return model.get(key) === value;\n });\n });\n }\n\n return [];\n }\n\n /**\n * Find first model matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {Model|undefined} First matching model or undefined\n */\n findWhere(criteria) {\n const results = this.where(criteria);\n return results.length > 0 ? results[0] : undefined;\n }\n\n /**\n * Iterate over each model in the collection\n * @param {function} callback - Function to execute for each model (model, index, collection)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {Collection} Returns the collection for chaining\n */\n forEach(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n this.models.forEach((model, index) => {\n callback.call(thisArg, model, index, this);\n });\n\n return this;\n }\n\n /**\n * Sort collection by comparator function\n * @param {function|string} comparator - Comparison function or attribute name\n * @param {object} options - Sort options\n */\n sort(comparator, options = {}) {\n if (typeof comparator === 'string') {\n const attr = comparator;\n comparator = (a, b) => {\n const aVal = a.get(attr);\n const bVal = b.get(attr);\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n };\n }\n\n this.models.sort(comparator);\n\n if (!options.silent) {\n this.trigger('sort', { collection: this });\n }\n\n return this;\n }\n\n /**\n * Convert collection to JSON array\n * @returns {array} Array of model JSON representations\n */\n toJSON() {\n return this.models.map(model => model.toJSON());\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Collection: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n return false;\n }\n\n /**\n * Check if collection has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n /**\n * Build URL for collection endpoint\n * @returns {string} Collection API URL\n */\n buildUrl() {\n return this.endpoint;\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Iterator support for for...of loops\n */\n *[Symbol.iterator]() {\n for (const model of this.models) {\n yield model;\n }\n }\n\n /**\n * Static method to create collection from array data\n * @param {function} ModelClass - Model class constructor\n * @param {array} data - Array of model data\n * @param {object} options - Collection options\n * @returns {Collection} New collection instance\n */\n static fromArray(ModelClass, data = [], options = {}) {\n const collection = new this(ModelClass, options);\n collection.add(data, { silent: true });\n return collection;\n }\n}\n\nObject.assign(Collection.prototype, EventEmitter);\n\nexport default Collection;\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/**\n * Group Model - Represents an organization, team, or group entity\n *\n * Features:\n * - Hierarchical group support (parent/child relationships)\n * - Member management\n * - Search and filtering capabilities\n * - Role-based permissions within groups\n * - Metadata and settings management\n */\nclass Group extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/group'\n });\n }\n}\n\n/**\n * GroupCollection - Enhanced collection for managing groups with advanced search and filtering\n */\nclass GroupList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Group,\n endpoint: '/api/group',\n size: 10,\n ...options\n });\n }\n}\n\nconst GroupKinds = {\n 'org': 'Organization',\n 'division': 'Division',\n 'department': 'Department',\n 'team': 'Team',\n 'merchant': 'Merchant',\n 'partner': 'Partner',\n 'client': 'Client',\n 'iso': 'ISO',\n 'sales': 'Sales',\n 'reseller': 'Reseller',\n 'location': 'Location',\n 'region': 'Region',\n 'route': 'Route',\n 'project': 'Project',\n \"inventory\": \"Inventory\",\n 'test': 'Testing',\n 'misc': 'Miscellaneous',\n 'qa': 'Quality Assurance'\n};\n\n// Convert GroupKinds to select options\nconst GroupKindOptions = Object.entries(GroupKinds).map(([key, label]) => ({\n value: key,\n label: label\n}));\n\n/**\n * Form configurations for group management\n */\nconst GroupForms = {\n create: {\n title: 'Create Group',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Group Name',\n required: true,\n placeholder: 'Enter group name'\n },\n {\n name: 'kind',\n type: 'select',\n label: 'Group Kind',\n required: true,\n options: GroupKindOptions\n },\n {\n type: 'collection',\n name: 'parent',\n label: 'Parent Group',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n }\n ]\n },\n\n edit: {\n title: 'Edit Group',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Group Name',\n required: true,\n placeholder: 'Enter group name',\n },\n {\n name: 'kind',\n type: 'select',\n label: 'Group Kind',\n required: true,\n options: GroupKindOptions\n },\n {\n type: 'collection',\n name: 'parent',\n label: 'Parent Group',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n {\n name: 'metadata.domain',\n type: 'text',\n label: 'Default Domain',\n placeholder: 'Enter Domain',\n },\n {\n name: 'metadata.portal',\n type: 'text',\n label: 'Default Portal',\n placeholder: 'Enter Portal URL',\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n cols: 4\n },\n ]\n },\n\n detailed: {\n title: 'Group Details',\n fields: [\n // Profile Header\n {\n type: 'header',\n text: 'Profile Information',\n level: 4,\n class: 'text-primary mb-3'\n },\n\n // Avatar and Basic Info\n {\n type: 'group',\n columns: { xs: 12, md: 4 },\n fields: [\n {\n type: 'image',\n name: 'avatar',\n size: 'lg',\n imageSize: { width: 200, height: 200 },\n placeholder: 'Upload your avatar',\n help: 'Square images work best',\n columns: 12\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n columns: 12\n },\n ]\n },\n\n // Profile Details\n {\n type: 'group',\n columns: { xs: 12, md: 8 },\n title: 'Details',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Group Name',\n required: true,\n placeholder: 'Enter group name',\n columns: 12\n },\n {\n name: 'kind',\n type: 'select',\n label: 'Group Kind',\n required: true,\n columns: 12,\n options: [\n { value: 'org', label: 'Organization' },\n { value: 'team', label: 'Team' },\n { value: 'department', label: 'Department' },\n { value: 'merchant', label: 'Merchant' },\n { value: 'iso', label: 'ISO' },\n { value: 'group', label: 'Group' }\n ]\n },\n {\n type: 'collection',\n name: 'parent',\n label: 'Parent Group',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n columns: 12\n }\n ]\n },\n\n // Account Settings\n {\n type: 'group',\n columns: 12,\n title: 'Account Settings',\n class: \"pt-3\",\n fields: [\n {\n type: 'select',\n name: 'metadata.timezone',\n label: 'Timezone',\n columns: 6,\n options: [\n { value: 'America/New_York', text: 'Eastern Time' },\n { value: 'America/Chicago', text: 'Central Time' },\n { value: 'America/Denver', text: 'Mountain Time' },\n { value: 'America/Los_Angeles', text: 'Pacific Time' },\n { value: 'UTC', text: 'UTC' }\n ]\n },\n {\n type: 'select',\n name: 'metadata.language',\n label: 'Language',\n columns: 6,\n options: [\n { value: 'en', text: 'English' },\n { value: 'es', text: 'Spanish' },\n { value: 'fr', text: 'French' },\n { value: 'de', text: 'German' }\n ]\n },\n {\n type: 'switch',\n name: 'metadata.notify.email',\n label: 'Email Notifications',\n columns: 4\n },\n {\n type: 'switch',\n name: 'metadata.profile_public',\n label: 'Public Profile',\n columns: 4\n }\n ]\n }\n ]\n },\n};\n\nGroup.EDIT_FORM = GroupForms.edit;\nGroup.CREATE_FORM = GroupForms.create;\nGroup.GroupKindOptions = GroupKindOptions;\nGroup.GroupKinds = GroupKinds;\nexport { Group, GroupList, GroupForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\nimport { GroupList } from './Group.js';\n\nclass User extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/user'\n });\n }\n\n hasPermission(permission) {\n if (Array.isArray(permission)) {\n return permission.some(p => this.hasPermission(p));\n }\n\n // Check if permission has \"sys.\" prefix\n const isSysPermission = permission.startsWith('sys.');\n const permissionToCheck = isSysPermission ? permission.substring(4) : permission;\n\n if (this._hasPermission(permissionToCheck)) {\n return true;\n }\n\n // Only check member permissions if it's not a system permission\n if (!isSysPermission && this.member && this.member.hasPermission(permission)) {\n return true;\n }\n\n return false;\n }\n\n _hasPermission(permission) {\n const permissions = this.get(\"permissions\");\n if (!permissions) {\n return false;\n }\n return permissions[permission] == true;\n }\n\n hasPerm(p) {\n return this.hasPermission(p);\n }\n}\n\nclass UserList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: User,\n endpoint: '/api/user',\n ...options\n });\n }\n}\n\nUser.PERMISSIONS = [\n { name: \"manage_users\", label: \"Manage Users\" },\n { name: \"view_groups\", label: \"View Groups\" },\n { name: \"manage_groups\", label: \"Manage Groups\" },\n { name: \"view_metrics\", label: \"View System Metrics\" },\n { name: \"manage_metrics\", label: \"Manage System Metrics\" },\n { name: \"view_logs\", label: \"View Logs\" },\n { name: \"view_incidents\", label: \"View Incidents\" },\n { name: \"manage_incidents\", label: \"Manage Incidents\" },\n { name: \"view_tickets\", label: \"View Tickets\" },\n { name: \"manage_tickets\", label: \"Manage Tickets\" },\n { name: \"view_admin\", label: \"View Admin\" },\n { name: \"view_jobs\", label: \"View Jobs\" },\n { name: \"manage_jobs\", label: \"Manage Jobs\" },\n { name: \"view_global\", label: \"View Global\" },\n { name: \"manage_notifications\", label: \"Manage Notifications\" },\n { name: \"manage_files\", label: \"Manage Files\" },\n { name: \"force_single_session\", label: \"Force Single Session\" },\n { name: \"file_vault\", label: \"Access File Vault\" },\n { name: \"manage_aws\", label: \"Manage AWS\" },\n { name: \"manage_docit\", label: \"Manage DocIt\" }\n];\n\n\nUser.PERMISSION_FIELDS = [\n ...User.PERMISSIONS.map(permission => ({\n name: `permissions.${permission.name}`,\n type: 'switch',\n label: permission.label,\n columns: 4\n }))\n];\n\nconst UserForms = {\n create: {\n title: 'Create User',\n fields: [\n { name: 'email', type: 'text', label: 'Email', required: true },\n { name: 'phone_number', type: 'text', label: 'Phone number', columns: 12 },\n { name: 'display_name', type: 'text', label: 'Display Name' }\n ]\n },\n edit: {\n title: 'Edit User',\n fields: [\n { name: 'email', type: 'email', label: 'Email', columns: 12 },\n { name: 'display_name', type: 'text', label: 'Display Name', columns: 12},\n { name: 'phone_number', type: 'text', label: 'Phone number', columns: 12 },\n { type: 'collection', name: 'org', label: 'Organization', Collection: GroupList, labelField: 'name', valueField: 'id', columns: 12 },\n ]\n },\n permissions: {\n title: 'Edit Permissions',\n fields: User.PERMISSIONS_FIELDS\n }\n};\n\n\n// DataView configuration for User model\nconst UserDataView = {\n // Basic user profile view\n profile: {\n title: 'User Profile',\n columns: 2,\n fields: [\n {\n name: 'id',\n label: 'User ID',\n type: 'number',\n columns: 4\n },\n {\n name: 'username',\n label: 'Username',\n type: 'text',\n format: 'lowercase',\n columns: 4\n },\n {\n name: 'last_login',\n label: 'Last Login',\n type: 'datetime',\n format: 'relative',\n columns: 4\n },\n {\n name: 'email',\n label: 'Email',\n type: 'email',\n columns: 4\n },\n {\n name: 'display_name',\n label: 'Display Name',\n type: 'text',\n columns: 4\n },\n {\n name: 'last_activity',\n label: 'Last Activity',\n type: 'datetime',\n format: 'relative',\n columns: 4\n },\n {\n name: 'org.name',\n label: 'Organization',\n type: 'text',\n columns: 4\n },\n {\n name: 'phone_number',\n label: 'Phone Number',\n type: 'text',\n columns: 4\n }\n ]\n },\n\n // Activity tracking view\n activity: {\n title: 'User Activity',\n columns: 2,\n fields: [\n {\n name: 'last_login',\n label: 'Last Login',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n },\n {\n name: 'last_activity',\n label: 'Last Activity',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n }\n ]\n },\n\n // Comprehensive view with all data\n detailed: {\n title: 'Detailed User Information',\n columns: 2,\n showEmptyValues: true,\n emptyValueText: 'Not set',\n fields: [\n // Basic Info Section\n {\n name: 'id',\n label: 'User ID',\n type: 'number',\n colSize: 3\n },\n {\n name: 'display_name',\n label: 'Display Name',\n type: 'text',\n format: 'capitalize|default(\"Unnamed User\")',\n colSize: 9\n },\n {\n name: 'username',\n label: 'Username',\n type: 'text',\n format: 'lowercase',\n colSize: 6\n },\n {\n name: 'email',\n label: 'Email Address',\n type: 'email',\n colSize: 6\n },\n {\n name: 'phone_number',\n label: 'Phone Number',\n type: 'phone',\n format: 'phone|default(\"Not provided\")',\n colSize: 6\n },\n {\n name: 'is_active',\n label: 'Account Status',\n type: 'boolean',\n colSize: 6\n },\n\n // Activity Info\n {\n name: 'last_login',\n label: 'Last Login',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n },\n {\n name: 'last_activity',\n label: 'Last Activity',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n },\n\n // Avatar Info\n {\n name: 'avatar.url',\n label: 'Avatar',\n type: 'url',\n colSize: 12\n },\n\n // Complex Data (will use full width automatically)\n {\n name: 'permissions',\n label: 'User Permissions',\n type: 'dataview',\n dataViewColumns: 2,\n showEmptyValues: false\n },\n {\n name: 'metadata',\n label: 'User Metadata',\n type: 'dataview',\n dataViewColumns: 1\n },\n {\n name: 'avatar',\n label: 'Avatar Details',\n type: 'dataview',\n dataViewColumns: 1\n }\n ]\n },\n\n // Permissions-focused view\n permissions: {\n title: 'User Permissions',\n columns: 1,\n fields: [\n {\n name: 'display_name',\n label: 'User',\n type: 'text',\n format: 'capitalize',\n columns: 12\n },\n {\n name: 'permissions',\n label: 'Assigned Permissions',\n type: 'dataview',\n dataViewColumns: 3,\n showEmptyValues: false,\n colSize: 12\n }\n ]\n },\n\n // Compact summary view\n summary: {\n title: 'User Summary',\n columns: 3,\n fields: [\n {\n name: 'display_name',\n label: 'Name',\n type: 'text',\n format: 'capitalize|truncate(30)'\n },\n {\n name: 'email',\n label: 'Email',\n type: 'email'\n },\n {\n name: 'is_active',\n label: 'Status',\n type: 'boolean'\n },\n {\n name: 'last_activity',\n label: 'Last Seen',\n type: 'datetime',\n format: 'relative',\n colSize: 12\n }\n ]\n }\n};\n\nUser.DATA_VIEW = UserDataView.detailed;\nUser.EDIT_FORM = UserForms.edit;\nUser.ADD_FORM = UserForms.create;\n\n/* =========================\n * UserDevice\n * ========================= */\nclass UserDevice extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/user/device',\n });\n }\n\n static async getByDuid(duid) {\n const model = new UserDevice();\n const resp = await model.rest.GET('/api/user/device/lookup', { duid: duid });\n if (resp.success && resp.data && resp.data.data) {\n // A direct lookup should return a single object\n return new UserDevice(resp.data.data);\n }\n return null;\n }\n}\n\nclass UserDeviceList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: UserDevice,\n endpoint: '/api/user/device',\n ...options,\n });\n }\n}\n\n/* =========================\n * UserDeviceLocation\n * ========================= */\nclass UserDeviceLocation extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/user/device/location',\n });\n }\n}\n\nclass UserDeviceLocationList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: UserDeviceLocation,\n endpoint: '/api/user/device/location',\n ...options,\n });\n }\n}\n\nexport { User, UserList, UserForms, UserDataView, UserDevice, UserDeviceList, UserDeviceLocation, UserDeviceLocationList };\n","/**\n * ContextMenu - A reusable context menu component for MOJO\n *\n * Renders a Bootstrap 5 dropdown menu based on a configuration object.\n * This component is designed to be easily embedded in any other View.\n * It supports the same configuration syntax as the Dialog's contextMenu.\n *\n * Features:\n * - Renders a dropdown button with a configurable icon.\n * - Generates menu items from a configuration array.\n * - Supports dividers, icons, labels, and links.\n * - Handles actions via inline handlers or by emitting an 'action' event.\n *\n * @example\n * const contextMenu = new ContextMenu({\n * config: {\n * icon: 'bi-gear', // Optional: custom trigger icon\n * items: [\n * { label: 'Edit', action: 'edit', icon: 'bi-pencil' },\n * { label: 'Delete', action: 'delete', icon: 'bi-trash', danger: true },\n * { type: 'divider' },\n * {\n * label: 'Custom Action',\n * action: 'custom',\n * icon: 'bi-star',\n * handler: (context) => {\n * console.log('Inline handler called with context:', context);\n * }\n * }\n * ]\n * },\n * context: { id: 123, name: 'My Item' } // Optional data to pass to handlers/events\n * });\n *\n * // Listen for actions from the parent view\n * contextMenu.on('action', (data) => {\n * console.log(`Action '${data.action}' triggered for context:`, data.context);\n * if (data.action === 'edit') {\n * // handle edit\n * }\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ContextMenu extends View {\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'context-menu-view dropdown',\n ...options\n });\n\n this.config = options.contextMenu || options.config || {};\n this.context = options.context || {}; // Optional data to pass to handlers/events\n }\n\n /**\n * Build the dropdown menu HTML from the configuration.\n */\n async renderTemplate() {\n const menuItems = this.config.items || [];\n if (menuItems.length === 0) {\n return ''; // Don't render anything if there are no items\n }\n\n const triggerIcon = this.config.icon || 'bi-three-dots-horizontal';\n const buttonClass = this.config.buttonClass || 'btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1';\n const dropdownId = `context-menu-${this.id}`;\n\n const menuItemsHtml = menuItems.map(item => this.buildMenuItemHTML(item)).join('');\n\n return `\n <button class=\"${buttonClass}\" type=\"button\" id=\"${dropdownId}\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"${triggerIcon}\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\" aria-labelledby=\"${dropdownId}\">\n ${menuItemsHtml}\n </ul>\n `;\n }\n\n /**\n * Build the HTML for a single menu item.\n * @param {object} item - The menu item configuration.\n * @returns {string} The HTML string for the list item.\n */\n buildMenuItemHTML(item) {\n if (item.type === 'divider' || item.separator) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n const icon = item.icon ? `<i class=\"${item.icon} me-2\"></i>` : '';\n const label = item.label || '';\n const itemClass = `dropdown-item ${item.danger ? 'text-danger' : ''} ${item.disabled ? 'disabled' : ''}`;\n const action = item.action || '';\n\n if (item.href) {\n return `<li><a class=\"${itemClass}\" href=\"${item.href}\" target=\"${item.target || '_self'}\">${icon}${label}</a></li>`;\n }\n\n return `<li><a class=\"${itemClass}\" href=\"#\" data-action=\"menu-item-click\" data-item-action=\"${action}\">${icon}${label}</a></li>`;\n }\n\n /**\n * Handle clicks on menu items.\n * @param {Event} event - The click event.\n * @param {HTMLElement} element - The clicked anchor element.\n */\n async onActionMenuItemClick(event, element) {\n event.preventDefault();\n const action = element.getAttribute('data-item-action');\n if (!action) return;\n\n const menuItem = this.config.items.find(item => item.action === action);\n if (!menuItem || menuItem.disabled) return;\n\n // Support for inline handlers\n if (typeof menuItem.handler === 'function') {\n menuItem.handler(this.context, event, element);\n } else {\n // Emit a general event for parent views to listen to\n // this.emit('action', {\n // action: action,\n // context: this.context,\n // sourceEvent: event\n // });\n this.parent.events.dispatch(action, event, element);\n }\n this.closeDropdown();\n }\n\n closeDropdown() {\n const dropdownTrigger = this.element.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (dropdownTrigger) {\n const dropdownInstance = window.bootstrap?.Dropdown.getInstance(dropdownTrigger);\n dropdownInstance?.hide();\n }\n }\n}\n\nexport default ContextMenu;\n","/**\n * ToastService - Bootstrap 5 Toast Notification Service\n * Provides methods to display toast notifications with different types and options\n *\n * Features:\n * - Bootstrap 5 toast integration\n * - Multiple toast types (success, error, info, warning)\n * - Auto-dismiss with customizable delays\n * - Toast container management\n * - Event integration\n * - Proper cleanup and memory management\n *\n * @example\n * const toastService = new ToastService();\n * toastService.success('Operation completed successfully!');\n * toastService.error('Something went wrong');\n * toastService.info('FYI: This is informational');\n * toastService.warning('Please be careful');\n */\n\nclass ToastService {\n constructor(options = {}) {\n this.options = {\n containerId: 'toast-container',\n position: 'top-end', // top-start, top-center, top-end, middle-start, etc.\n autohide: true,\n defaultDelay: 5000, // 5 seconds\n maxToasts: 5, // Maximum number of toasts to show at once\n ...options\n };\n\n this.toasts = new Map(); // Track active toasts\n this.toastCounter = 0; // For unique IDs\n \n this.init();\n }\n\n /**\n * Initialize the toast service\n */\n init() {\n this.createContainer();\n }\n\n /**\n * Create the toast container if it doesn't exist\n */\n createContainer() {\n let container = document.getElementById(this.options.containerId);\n \n if (!container) {\n container = document.createElement('div');\n container.id = this.options.containerId;\n container.className = `toast-container position-fixed ${this.getPositionClasses()}`;\n container.style.zIndex = '1070'; // Bootstrap toast z-index\n container.setAttribute('aria-live', 'polite');\n container.setAttribute('aria-atomic', 'true');\n \n document.body.appendChild(container);\n }\n \n this.container = container;\n }\n\n /**\n * Get CSS classes for toast positioning\n */\n getPositionClasses() {\n const positionMap = {\n 'top-start': 'top-0 start-0 p-3',\n 'top-center': 'top-0 start-50 translate-middle-x p-3',\n 'top-end': 'top-0 end-0 p-3',\n 'middle-start': 'top-50 start-0 translate-middle-y p-3',\n 'middle-center': 'top-50 start-50 translate-middle p-3',\n 'middle-end': 'top-50 end-0 translate-middle-y p-3',\n 'bottom-start': 'bottom-0 start-0 p-3',\n 'bottom-center': 'bottom-0 start-50 translate-middle-x p-3',\n 'bottom-end': 'bottom-0 end-0 p-3'\n };\n \n return positionMap[this.options.position] || positionMap['top-end'];\n }\n\n\n\n /**\n * Show a success toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n success(message, options = {}) {\n return this.show(message, 'success', {\n icon: 'bi-check-circle-fill',\n ...options\n });\n }\n\n /**\n * Show an error toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n error(message, options = {}) {\n return this.show(message, 'error', {\n icon: 'bi-exclamation-triangle-fill',\n autohide: false, // Keep error toasts visible until manually dismissed\n ...options\n });\n }\n\n /**\n * Show an info toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n info(message, options = {}) {\n return this.show(message, 'info', {\n icon: 'bi-info-circle-fill',\n ...options\n });\n }\n\n /**\n * Show a warning toast\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n warning(message, options = {}) {\n return this.show(message, 'warning', {\n icon: 'bi-exclamation-triangle-fill',\n ...options\n });\n }\n\n /**\n * Show a plain toast without specific styling\n * @param {string} message - The message to display\n * @param {object} options - Additional options\n */\n plain(message, options = {}) {\n return this.show(message, 'plain', {\n ...options\n });\n }\n\n /**\n * Show a toast with specified type and options\n * @param {string} message - The message to display\n * @param {string} type - Toast type (success, error, info, warning)\n * @param {object} options - Additional options\n */\n show(message, type = 'info', options = {}) {\n // Enforce max toasts limit\n this.enforceMaxToasts();\n \n const toastId = `toast-${++this.toastCounter}`;\n const config = {\n title: this.getDefaultTitle(type),\n icon: this.getDefaultIcon(type),\n autohide: this.options.autohide,\n delay: this.options.defaultDelay,\n dismissible: true,\n ...options\n };\n\n const toastElement = this.createToastElement(toastId, message, type, config);\n this.container.appendChild(toastElement);\n\n // Initialize Bootstrap toast\n if (typeof bootstrap === 'undefined') {\n throw new Error('Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.');\n }\n const bsToast = new bootstrap.Toast(toastElement, {\n autohide: config.autohide,\n delay: config.delay\n });\n\n // Store toast reference\n this.toasts.set(toastId, {\n element: toastElement,\n bootstrap: bsToast,\n type: type,\n message: message\n });\n\n // Setup cleanup on hide\n toastElement.addEventListener('hidden.bs.toast', () => {\n this.cleanup(toastId);\n });\n\n // Show the toast\n bsToast.show();\n\n return {\n id: toastId,\n hide: () => {\n try {\n bsToast.hide();\n } catch (error) {\n console.warn('Error hiding toast:', error);\n }\n },\n dispose: () => this.cleanup(toastId),\n updateProgress: options.updateProgress || null\n };\n }\n\n /**\n * Show a toast with a View component in the body\n * @param {View} view - The View component to display\n * @param {string} type - Toast type (success, error, info, warning, plain)\n * @param {object} options - Additional options\n */\n showView(view, type = 'info', options = {}) {\n // Enforce max toasts limit\n this.enforceMaxToasts();\n \n const toastId = `toast-${++this.toastCounter}`;\n const config = {\n title: options.title || this.getDefaultTitle(type),\n icon: options.icon || this.getDefaultIcon(type),\n autohide: this.options.autohide,\n delay: this.options.defaultDelay,\n dismissible: true,\n ...options\n };\n\n const toastElement = this.createViewToastElement(toastId, view, type, config);\n this.container.appendChild(toastElement);\n\n // Initialize Bootstrap toast\n if (typeof bootstrap === 'undefined') {\n throw new Error('Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.');\n }\n const bsToast = new bootstrap.Toast(toastElement, {\n autohide: config.autohide,\n delay: config.delay\n });\n\n // Store toast reference with view\n this.toasts.set(toastId, {\n element: toastElement,\n bootstrap: bsToast,\n type: type,\n view: view,\n message: 'View toast'\n });\n\n // Setup cleanup on hide - dispose view properly\n toastElement.addEventListener('hidden.bs.toast', () => {\n this.cleanupView(toastId);\n });\n\n // Mount and render the view\n const bodyContainer = toastElement.querySelector('.toast-view-body');\n if (bodyContainer && view) {\n view.render(true, bodyContainer);\n }\n\n // Show the toast\n bsToast.show();\n\n return {\n id: toastId,\n view: view,\n hide: () => {\n try {\n bsToast.hide();\n } catch (error) {\n console.warn('Error hiding view toast:', error);\n }\n },\n dispose: () => this.cleanupView(toastId),\n updateProgress: (progressInfo) => {\n if (view && typeof view.updateProgress === 'function') {\n view.updateProgress(progressInfo);\n }\n }\n };\n }\n\n /**\n * Create toast DOM element\n */\n createToastElement(id, message, type, config) {\n const toast = document.createElement('div');\n toast.id = id;\n toast.className = `toast toast-service-${type}`;\n toast.setAttribute('role', 'alert');\n toast.setAttribute('aria-live', 'assertive');\n toast.setAttribute('aria-atomic', 'true');\n\n const header = config.title || config.icon ? this.createToastHeader(config, type) : '';\n const body = this.createToastBody(message, config.icon && !config.title);\n\n toast.innerHTML = `\n ${header}\n ${body}\n `;\n\n return toast;\n }\n\n /**\n * Create toast DOM element for View component\n */\n createViewToastElement(id, view, type, config) {\n const toast = document.createElement('div');\n toast.id = id;\n toast.className = `toast toast-service-${type}`;\n toast.setAttribute('role', 'alert');\n toast.setAttribute('aria-live', 'assertive');\n toast.setAttribute('aria-atomic', 'true');\n\n const header = config.title || config.icon ? this.createToastHeader(config, type) : '';\n const body = this.createViewToastBody();\n\n toast.innerHTML = `\n ${header}\n ${body}\n `;\n\n return toast;\n }\n\n /**\n * Create toast body for View component\n */\n createViewToastBody() {\n return `\n <div class=\"toast-body p-0\">\n <div class=\"toast-view-body p-3\"></div>\n </div>\n `;\n }\n\n /**\n * Create toast header with title and icon\n */\n createToastHeader(config, _type) {\n const iconHtml = config.icon ? \n `<i class=\"${config.icon} toast-service-icon me-2\"></i>` : '';\n \n const titleHtml = config.title ? \n `<strong class=\"me-auto\">${iconHtml}${this.escapeHtml(config.title)}</strong>` : '';\n\n const timeHtml = config.showTime ? \n `<small class=\"text-muted\">${this.getTimeString()}</small>` : '';\n\n const closeButton = config.dismissible ? \n `<button type=\"button\" class=\"btn-close toast-service-close\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>` : '';\n\n if (!titleHtml && !timeHtml && !closeButton) {\n return '';\n }\n\n return `\n <div class=\"toast-header\">\n ${titleHtml}\n ${timeHtml}\n ${closeButton}\n </div>\n `;\n }\n\n /**\n * Create toast body with message\n */\n createToastBody(message, showIcon = false) {\n const iconHtml = showIcon ? \n `<i class=\"${this.getDefaultIcon('info')} toast-service-icon me-2\"></i>` : '';\n \n return `\n <div class=\"toast-body d-flex align-items-center\">\n ${iconHtml}\n <span>${this.escapeHtml(message)}</span>\n </div>\n `;\n }\n\n /**\n * Get default title for toast type\n */\n getDefaultTitle(type) {\n const titles = {\n success: 'Success',\n error: 'Error',\n warning: 'Warning',\n info: 'Information',\n plain: ''\n };\n return titles[type] || 'Notification';\n }\n\n /**\n * Get default icon for toast type\n */\n getDefaultIcon(type) {\n const icons = {\n success: 'bi-check-circle-fill',\n error: 'bi-exclamation-triangle-fill',\n warning: 'bi-exclamation-triangle-fill',\n info: 'bi-info-circle-fill',\n plain: ''\n };\n return icons[type] || 'bi-info-circle-fill';\n }\n\n /**\n * Enforce maximum number of toasts\n */\n enforceMaxToasts() {\n const activeToasts = Array.from(this.toasts.values());\n \n if (activeToasts.length >= this.options.maxToasts) {\n // Remove oldest toast\n const oldestId = this.toasts.keys().next().value;\n const oldest = this.toasts.get(oldestId);\n \n if (oldest) {\n oldest.bootstrap.hide();\n }\n }\n }\n\n /**\n * Clean up toast resources\n */\n cleanup(toastId) {\n const toast = this.toasts.get(toastId);\n \n if (toast) {\n // Dispose Bootstrap toast\n try {\n toast.bootstrap.dispose();\n } catch (e) {\n console.warn('Error disposing toast:', e);\n }\n \n // Remove from DOM\n if (toast.element && toast.element.parentNode) {\n toast.element.parentNode.removeChild(toast.element);\n }\n \n // Remove from tracking\n this.toasts.delete(toastId);\n }\n }\n\n /**\n * Clean up view toast resources with proper view disposal\n */\n cleanupView(toastId) {\n const toast = this.toasts.get(toastId);\n \n if (toast) {\n // Dispose view first if it exists\n if (toast.view && typeof toast.view.dispose === 'function') {\n try {\n toast.view.dispose();\n } catch (e) {\n console.warn('Error disposing view in toast:', e);\n }\n }\n \n // Dispose Bootstrap toast\n try {\n toast.bootstrap.dispose();\n } catch (e) {\n console.warn('Error disposing toast:', e);\n }\n \n // Remove from DOM\n if (toast.element && toast.element.parentNode) {\n toast.element.parentNode.removeChild(toast.element);\n }\n \n // Remove from tracking\n this.toasts.delete(toastId);\n }\n }\n\n /**\n * Hide all active toasts\n */\n hideAll() {\n this.toasts.forEach((toast, _id) => {\n toast.bootstrap.hide();\n });\n }\n\n /**\n * Clear all toasts immediately\n */\n clearAll() {\n this.toasts.forEach((toast, id) => {\n this.cleanup(id);\n });\n }\n\n /**\n * Get current time string\n */\n getTimeString() {\n return new Date().toLocaleTimeString([], { \n hour: '2-digit', \n minute: '2-digit' \n });\n }\n\n /**\n * Escape HTML to prevent XSS\n */\n escapeHtml(str) {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n /**\n * Dispose of the entire toast service\n */\n dispose() {\n this.clearAll();\n \n if (this.container && this.container.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n }\n\n /**\n * Get statistics about active toasts\n */\n getStats() {\n const stats = {\n total: this.toasts.size,\n byType: {}\n };\n \n this.toasts.forEach(toast => {\n stats.byType[toast.type] = (stats.byType[toast.type] || 0) + 1;\n });\n \n return stats;\n }\n\n /**\n * Set global options\n */\n setOptions(newOptions) {\n this.options = { ...this.options, ...newOptions };\n \n // Recreate container if position changed\n if (newOptions.position) {\n if (this.container) {\n this.container.className = `toast-container position-fixed ${this.getPositionClasses()}`;\n }\n }\n }\n}\n\nexport default ToastService;"],"names":["Model","constructor","data","options","this","endpoint","id","attributes","_","originalAttributes","errors","loading","rest","idAttribute","timestamps","getContextValue","key","get","includes","MOJOUtils","getContextData","set","value","previousAttributes","JSON","parse","stringify","hasChanged","attrKey","attrValue","Object","entries","_setNestedAttribute","silent","emit","attr","val","finalValue","_getNestedValue","oldValue","keys","split","topLevelKey","attrTarget","instanceTarget","i","length","currentKey","finalKey","source","current","k","getData","getId","fetch","url","requiresId","Error","buildUrl","requestKey","params","debounceMs","_debouncedFetch","currentRequest","currentRequestKey","console","info","abortController","abort","now","Date","lastFetchTime","AbortController","_performFetch","error","name","debouncedFetchTimeout","clearTimeout","cancel","Promise","resolve","reject","setTimeout","async","result","graph","response","GET","signal","success","status","message","save","isNew","method","destroy","DELETE","isDirty","getChangedAttributes","changed","reset","endsWith","toJSON","validate","validations","field","rules","validateField","rulesArray","Array","isArray","rule","required","minLength","maxLength","pattern","test","find","model","create","isFetching","showError","Dialog","alert","size","class","assign","prototype","EventEmitter","Collection","ModelClass","models","meta","add","start","tmp","restEnabled","preloaded","getModelName","additionalParams","rateLimiting","fetchParams","log","updateParams","newParams","autoFetch","setParams","fetchOne","warn","collection","addToCollection","existingModel","merge","download","format","downloadParams","download_format","filename","toLowerCase","acceptHeader","json","csv","headers","Accept","count","modelsData","addedModels","modelData","existingIndex","findIndex","m","push","remove","modelsToRemove","removedModels","index","indexOf","removedModel","splice","previousModels","at","isEmpty","where","criteria","filter","every","findWhere","results","forEach","callback","thisArg","TypeError","call","sort","comparator","a","b","aVal","bVal","trigger","map","Symbol","iterator","fromArray","Group","super","GroupList","GroupKinds","org","division","department","team","merchant","partner","client","iso","sales","reseller","location","region","route","project","inventory","misc","qa","GroupKindOptions","label","GroupForms","title","fields","type","placeholder","labelField","valueField","maxItems","emptyFetch","edit","cols","detailed","text","level","columns","xs","md","imageSize","width","height","help","EDIT_FORM","CREATE_FORM","User","hasPermission","permission","some","p","isSysPermission","startsWith","permissionToCheck","substring","_hasPermission","member","permissions","hasPerm","PERMISSIONS","PERMISSION_FIELDS","UserForms","PERMISSIONS_FIELDS","UserDataView","profile","activity","colSize","showEmptyValues","emptyValueText","dataViewColumns","summary","DATA_VIEW","ADD_FORM","UserDevice","getByDuid","duid","resp","UserDeviceLocation","ContextMenu","View","tagName","className","config","contextMenu","context","renderTemplate","menuItems","items","triggerIcon","icon","buttonClass","dropdownId","item","buildMenuItemHTML","join","separator","itemClass","danger","disabled","action","href","target","onActionMenuItemClick","event","element","preventDefault","getAttribute","menuItem","handler","parent","events","dispatch","closeDropdown","dropdownTrigger","querySelector","dropdownInstance","window","bootstrap","Dropdown","getInstance","hide","containerId","position","autohide","defaultDelay","maxToasts","toasts","Map","toastCounter","init","createContainer","container","document","getElementById","createElement","getPositionClasses","style","zIndex","setAttribute","body","appendChild","positionMap","show","warning","plain","enforceMaxToasts","toastId","getDefaultTitle","getDefaultIcon","delay","dismissible","toastElement","createToastElement","bsToast","Toast","addEventListener","cleanup","dispose","updateProgress","showView","view","createViewToastElement","cleanupView","bodyContainer","render","progressInfo","toast","header","createToastHeader","createToastBody","innerHTML","createViewToastBody","_type","iconHtml","titleHtml","escapeHtml","timeHtml","showTime","getTimeString","closeButton","showIcon","from","values","oldestId","next","oldest","e","parentNode","removeChild","delete","hideAll","_id","clearAll","toLocaleTimeString","hour","minute","str","div","textContent","getStats","stats","total","byType","setOptions","newOptions"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./WebApp-CYqazUo5.js");class DataView extends e.View{constructor(e={}){const{data:t,model:s,fields:i,columns:a,responsive:n,showEmptyValues:l,emptyValueText:r,...o}=e;super({tagName:"div",className:"data-view",...o}),this.data=t||{},this.fields=i||[],this.model=s||null,this.model&&(this.data=this.model),this.dataViewOptions={columns:a||2,responsive:!1!==n,showEmptyValues:l||!1,emptyValueText:r||"—",rowClass:"row g-3",itemClass:"data-view-item",labelClass:"data-view-label fw-semibold text-muted small text-uppercase",valueClass:"data-view-value"}}async onBeforeRender(){0===this.fields.length&&this.getData()&&this.generateFieldsFromData()}async renderTemplate(){const e=this.buildItemsHTML();return`\n <div class="${this.dataViewOptions.rowClass}">\n ${e}\n </div>\n `}generateFieldsFromData(){const e=this.getData();e&&"object"==typeof e&&(this.fields=Object.keys(e).map(t=>{const s=e[t],i=this.inferFieldType(s,t),a=this.inferFormatter(s,t,i);return{name:t,label:this.formatLabel(t),type:i,format:a}}))}formatLabel(e){return e.replace(/([A-Z])/g," $1").replace(/[_-]/g," ").replace(/\b\w/g,e=>e.toUpperCase()).trim()}inferFieldType(e,t=""){if(null==e)return"text";const s=t.toLowerCase(),i=typeof e;if(s.includes("date")||s.includes("time")||s.includes("created")||s.includes("updated")||s.includes("modified")||s.includes("last_login")||s.includes("expires")||s.includes("last_activity"))return"datetime";if(s.includes("email")||s.includes("mail"))return"email";if(s.includes("url")||s.includes("link")||s.includes("website")||s.includes("homepage"))return"url";if(s.includes("phone")||s.includes("tel")||s.includes("mobile")||s.includes("cell"))return"phone";if(s.includes("price")||s.includes("cost")||s.includes("amount")||s.includes("fee")||s.includes("salary")||s.includes("revenue"))return"currency";if(s.includes("size")||s.includes("bytes"))return"filesize";if(s.includes("percent")||s.includes("rate")||s.includes("ratio")&&"number"===i)return"percent";if("boolean"===i)return"boolean";if("number"===i)return"number";if("object"===i)return Array.isArray(e)?"array":e&&e.renditions?"file":this.shouldUseDataView(e,s)?"dataview":"object";if("string"===i){if(e.includes("@")&&e.includes("."))return"email";if(e.match(/^\d{4}-\d{2}-\d{2}/))return"date";if(e.match(/^https?:\/\//))return"url";if(e.match(/^\+?[\d\s\-\(\)]+$/))return"phone"}return"text"}inferFormatter(e,t,s){const i=t.toLowerCase(),a=[];switch(s){case"datetime":i.includes("time")&&!i.includes("date")?a.push("time"):i.includes("relative")||i.includes("ago")||i.includes("last_")?a.push("relative"):i.includes("created")||i.includes("updated")||i.includes("modified")?a.push('date("MMM D, YYYY")'):a.push('date("MMMM D, YYYY")');break;case"date":i.includes("birth")||i.includes("dob")?a.push('date("MMMM D, YYYY")'):a.push('date("MMM D, YYYY")');break;case"email":case"url":case"boolean":case"array":case"object":case"dataview":break;case"phone":a.push("phone");break;case"currency":a.push("currency"),i.includes("eur")||i.includes("euro")?a[a.length-1]='currency("EUR")':(i.includes("gbp")||i.includes("pound"))&&(a[a.length-1]='currency("GBP")');break;case"filesize":a.push("filesize");break;case"percent":a.push("percent");break;case"number":if("number"==typeof e)if(i.includes("count")||i.includes("total")||i.includes("followers")||i.includes("views"))e>=1e3?a.push("compact"):a.push("number");else if(i.includes("score")||i.includes("rating"))a.push("number"),e%1!=0&&(a[a.length-1]="number(1)");else{if(i.includes("version")||i.includes("id"))return null;a.push("number")}break;case"text":"string"==typeof e&&(i.includes("description")||i.includes("content")||i.includes("body")?e.length>200?a.push("truncate(200)"):e.length>100&&a.push("truncate(100)"):i.includes("summary")||i.includes("excerpt")?e.length>150&&a.push("truncate(150)"):i.includes("name")||i.includes("title")||i.includes("label")?(a.push("capitalize"),e.length>50&&a.unshift("truncate(50)")):i.includes("slug")||i.includes("handle")||i.includes("username")?a.push("slug"):i.includes("code")||i.includes("token")||i.includes("key")?e.length>20&&a.push("mask"):e.length>100&&a.push("truncate(100)"));break;default:"string"==typeof e&&e.length>100&&a.push("truncate(100)")}return a.length>0?a.join("|"):null}shouldUseDataView(e,t){if(!e||"object"!=typeof e||Array.isArray(e))return!1;if(window.utils&&window.utils.isObject(e)&&e.id)return!0;if(["permissions","perms","access","rights","settings","config","configuration","options","profile","info","details","data","metadata","meta","attributes","props","preferences","prefs","user_data","contact","address","location","stats","statistics","metrics","counts"].some(e=>t.includes(e))){const t=Object.keys(e);if(t.length>=2&&t.length<=20&&!t.some(t=>"object"==typeof e[t]&&null!==e[t]&&!Array.isArray(e[t])&&Object.keys(e[t]).length>3))return!0}return!1}getData(){return this.model&&this.model.attributes?{...this.model.attributes}:this.data||{}}getFieldValue(t){let s;if(s=this.model&&"function"==typeof this.model.get?this.model.get(t.name):this.getData()[t.name],t.format&&(s=e.dataFormatter.pipe(s,t.format)),null==s||""===s)return this.dataViewOptions.showEmptyValues?this.dataViewOptions.emptyValueText:null;if(t.template){const e=this.model?this.model:this.data;return this.renderTemplateString(t.template,e)}return s}renderTemplateString(t,s){return t&&s?t.replace(/\{\{([^}]+)\}\}/g,(t,i)=>{let a;const n=i.trim().split("|"),l=n[0],r=n.slice(1).join("|");return a=this.model&&"function"==typeof this.model.get?this.model.get(l):l.split(".").reduce((e,t)=>e?e[t]:void 0,s),r&&(a=e.dataFormatter.pipe(a,r)),null!=a?a:""}):""}getColumnClasses(e){let t=this.getColumnSizeClasses(e);return"right"==e.justify?t+=" d-flex justify-content-end":"center"==e.justify&&(t+=" d-flex justify-content-center"),t}getColumnSizeClasses(e){if("array"===e.type||"object"===e.type||"dataview"===e.type)return"col-12";const t=e.columns||e.colSize||e.cols||Math.floor(12/this.dataViewOptions.columns);return this.dataViewOptions.responsive?`col-12 col-md-${t}`:`col-${t}`}buildItemsHTML(){return this.fields.map(e=>this.buildItemHTML(e)).filter(Boolean).join("")}buildItemHTML(e){const t=this.getFieldValue(e);if(null===t&&!this.dataViewOptions.showEmptyValues)return"";const s=e.label||this.formatLabel(e.name);return`\n <div class="${this.getColumnClasses(e)}">\n <div class="${this.dataViewOptions.itemClass} ${e.className}" data-field="${e.name}">\n ${this.buildLabelHTML(s,e)}\n ${this.buildValueHTML(t,e)}\n </div>\n </div>\n `}buildLabelHTML(e,t){return`<div class="${t.labelClass||this.dataViewOptions.labelClass}">${this.escapeHtml(e)}:</div>`}buildValueHTML(e,t){return`<div class="${t.valueClass||this.dataViewOptions.valueClass}">${this.formatDisplayValue(e,t)}</div>`}formatDisplayValue(t,s){if(null==t)return this.dataViewOptions.emptyValueText;if(s.template)return String(t);if(s.format){const i=["badge","email","url","icon","status","image","avatar","phone","highlight","pre"],a=e.dataFormatter.parsePipeString(s.format),n=a.length>0?a[a.length-1].name.toLowerCase():null;return n&&i.includes(n)?String(t):this.escapeHtml(String(t))}const i=this.getData()[s.name];switch(s.type){case"boolean":return i?'<span class="badge bg-success">Yes</span>':'<span class="badge bg-secondary">No</span>';case"email":const e=String(t);return`<a href="mailto:${this.escapeHtml(e)}" class="text-decoration-none">${this.escapeHtml(e)}</a>`;case"url":const a=String(t);return`<a href="${this.escapeHtml(a)}" target="_blank" rel="noopener" class="text-decoration-none">${this.escapeHtml(a)} <i class="bi bi-box-arrow-up-right"></i></a>`;case"array":case"object":return this.formatAsJson(i);case"dataview":return this.formatAsDataView(i,s);case"phone":const n=String(t);return`<a href="tel:${n.replace(/[^\d\+]/g,"")}" class="text-decoration-none">${this.escapeHtml(n)}</a>`;default:return this.escapeHtml(String(t))}}formatAsJson(e){try{const t=JSON.stringify(e,null,2),s=this.escapeHtml(t),i=t.split("\n").length,a=i>10||t.length>500,n=`json-${Math.random().toString(36).substr(2,9)}`;if(a){const a=JSON.stringify(e).substring(0,100)+(JSON.stringify(e).length>100?"...":""),l=this.escapeHtml(a);return`\n <div class="json-container">\n <div class="d-flex align-items-center justify-content-between mb-1">\n <small class="text-muted">${Array.isArray(e)?"Array":"Object"} (${i} lines)</small>\n <div class="btn-group btn-group-sm" role="group">\n <button type="button" class="btn btn-outline-secondary btn-sm json-toggle" data-bs-toggle="collapse" data-bs-target="#${n}" aria-expanded="false">\n <i class="bi bi-eye"></i> Show\n </button>\n <button type="button" class="btn btn-outline-secondary btn-sm json-copy" data-json='${this.escapeHtml(t)}' title="Copy JSON">\n <i class="bi bi-clipboard"></i>\n </button>\n </div>\n </div>\n <div class="json-preview bg-light p-2 rounded small border" style="font-family: 'Courier New', monospace;">\n <code class="text-muted">${l}</code>\n </div>\n <div class="collapse mt-2" id="${n}">\n <pre class="json-display p-3 rounded small mb-0" style="max-height: 400px; overflow-y: auto; white-space: pre-wrap; font-family: 'Courier New', monospace;"><code>${this.syntaxHighlightJson(s)}</code></pre>\n </div>\n </div>\n `}return`\n <div class="json-container">\n <div class="d-flex align-items-center justify-content-between mb-1">\n <small class="text-muted">${Array.isArray(e)?"Array":"Object"}</small>\n <button type="button" class="btn btn-outline-secondary btn-sm json-copy" data-json='${this.escapeHtml(t)}' title="Copy JSON">\n <i class="bi bi-clipboard"></i>\n </button>\n </div>\n <pre class="json-display bg-light p-2 rounded small mb-0 border" style="white-space: pre-wrap; font-family: 'Courier New', monospace;"><code>${this.syntaxHighlightJson(s)}</code></pre>\n </div>\n `}catch(t){return`<span class="text-muted fst-italic">[Object: ${typeof e}] - Cannot display as JSON</span>`}}syntaxHighlightJson(e){return e.replace(/("([^"\\]|\\.)*")\s*:/g,'<span style="color: #0969da;">$1</span>:').replace(/:\s*("([^"\\]|\\.)*")/g,': <span style="color: #0a3069;">$1</span>').replace(/:\s*(true|false)/g,': <span style="color: #8250df;">$1</span>').replace(/:\s*(null)/g,': <span style="color: #656d76;">$1</span>').replace(/:\s*(-?\d+\.?\d*)/g,': <span style="color: #0550ae;">$1</span>')}bindEvents(){super.bindEvents(),this.element&&this.element.addEventListener("click",e=>{const t=e.target.closest("[data-field]");if(t){const s=t.dataset.field,i=this.fields.find(e=>e.name===s);this.emit("field:click",{field:i,fieldName:s,element:t,event:e})}e.target.closest(".json-copy")&&(e.preventDefault(),e.stopPropagation(),this.handleJsonCopy(e.target.closest(".json-copy"))),e.target.closest(".json-toggle")&&this.handleJsonToggle(e.target.closest(".json-toggle"))})}handleJsonCopy(e){const t=e.getAttribute("data-json");if(t)try{if(navigator.clipboard&&window.isSecureContext)navigator.clipboard.writeText(t).then(()=>{this.showCopyFeedback(e)});else{const s=document.createElement("textarea");s.value=t,document.body.appendChild(s),s.select(),document.execCommand("copy"),document.body.removeChild(s),this.showCopyFeedback(e)}}catch(s){console.warn("Failed to copy JSON:",s)}}handleJsonToggle(e){const t=e.querySelector("i"),s="true"===e.getAttribute("aria-expanded");setTimeout(()=>{s?(t.className="bi bi-eye-slash",e.innerHTML='<i class="bi bi-eye-slash"></i> Hide'):(t.className="bi bi-eye",e.innerHTML='<i class="bi bi-eye"></i> Show')},10)}showCopyFeedback(e){const t=e.querySelector("i").className,s=e.querySelector("i");s.className="bi bi-check text-success",e.classList.add("btn-success"),e.classList.remove("btn-outline-secondary"),setTimeout(()=>{s.className=t,e.classList.remove("btn-success"),e.classList.add("btn-outline-secondary")},1e3)}formatAsDataView(e,t){if(!e||"object"!=typeof e)return'<span class="text-muted fst-italic">No data available</span>';try{const s=new this.constructor({data:e,columns:t.dataViewColumns||2,showEmptyValues:t.showEmptyValues??!0,emptyValueText:t.emptyValueText||"Not set",...t.dataViewOptions||{}});s.onInit(),s.generateFieldsFromData();const i=s.buildItemsHTML();return`\n <div class="nested-dataview border rounded p-3 bg-light">\n <div class="${s.dataViewOptions.rowClass}">\n ${i}\n </div>\n </div>\n `}catch(s){return console.error("Error creating nested DataView:",s),'<span class="text-danger">Error displaying nested data</span>'}}async updateData(e){return this.data=e,this.model&&"function"==typeof this.model.set&&this.model.set(e),this.fields.length>0&&!this.options.fields&&(this.fields=[]),await this.render(),this.emit("data:updated",{data:e}),this}async updateFields(e){return this.fields=e,await this.render(),this.emit("fields:updated",{fields:e}),this}async updateConfig(e){return this.dataViewOptions={...this.dataViewOptions,...e},await this.render(),this.emit("config:updated",{options:this.dataViewOptions}),this}async refresh(){if(this.model&&"function"==typeof this.model.fetch)try{await this.model.fetch(),this.emit("data:refreshed",{model:this.model})}catch(e){throw this.emit("error",{error:e,message:"Failed to refresh data"}),e}return this}getCurrentData(){return this.getData()}getField(e){return this.fields.find(t=>t.name===e)||null}setFieldFormat(e,t){const s=this.getField(e);return s?s.format=t:this.fields.push({name:e,label:this.formatLabel(e),type:this.inferFieldType(this.getData()[e],e),format:t}),this}addFormatPipe(e,t){const s=this.getField(e);return s&&(s.format?s.format+=`|${t}`:s.format=t),this}clearFieldFormat(e){const t=this.getField(e);if(t){const s=this.getData();t.format=this.inferFormatter(s[e],e,t.type)}return this}getFormattedValue(t,s=null){const i=this.getField(t);if(!i)return null;const a=null!==s?s:this.getData()[t];return i.format&&null!=a?e.dataFormatter.pipe(a,i.format):a}setFieldFormats(e){return Object.entries(e).forEach(([e,t])=>{this.setFieldFormat(e,t)}),this}getFieldFormats(){const e={};return this.fields.forEach(t=>{t.format&&(e[t.name]=t.format)}),e}onInit(){super.onInit(),this.model&&"function"==typeof this.model.on&&this.model.on("change",()=>{this.render()})}static create(e={}){return new DataView(e)}}exports.default=DataView;
|
|
2
|
+
//# sourceMappingURL=DataView-CHoPxbYT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataView-CHoPxbYT.js","sources":["../../src/core/views/data/DataView.js"],"sourcesContent":["/**\n * DataView - Key/Value data display component for MOJO framework\n * Extends View to display object data in a responsive grid layout with intelligent formatting support\n *\n * Features:\n * - Automatic field generation from data with intelligent type inference\n * - Smart DataFormatter pipe chains (e.g., \"date('MMM D, YYYY')|capitalize\")\n * - Contextual formatting based on field names and values\n * - Support for complex pipe chains like \"truncate(100)|capitalize|badge\"\n * - Nested DataView support with type=\"dataview\" for complex objects\n * - Custom format overrides with fluent API\n * - No format collision: custom field.format completely overrides DataView formatting\n *\n * Format Behavior:\n * - No field.format: DataView applies type-based HTML formatting (badges, links, etc.)\n * - With field.format: DataFormatter handles ALL formatting, DataView only escapes HTML\n *\n * Example Usage:\n * ```javascript\n * const dataView = new DataView({\n * data: {\n * name: 'john doe',\n * email: 'john@example.com',\n * createdAt: '2024-01-15T10:30:00Z',\n * price: 99.99,\n * description: 'A very long description that should be truncated...',\n * isActive: true\n * }\n * });\n *\n * // Auto-inferred formats (DataView adds HTML styling):\n * // name: \"truncate(50)|capitalize\"\n * // email: no format → DataView creates mailto: link\n * // createdAt: \"date('MMM D, YYYY')\"\n * // price: \"currency\"\n * // description: \"truncate(200)\"\n * // isActive: no format → DataView creates badge styling\n *\n * // Custom format overrides (DataFormatter handles ALL formatting):\n * dataView\n * .setFieldFormat('name', 'uppercase|truncate(20)') // No DataView HTML styling\n * .setFieldFormat('email', 'email|uppercase') // No mailto: link\n * .setFieldFormat('isActive', 'boolean|uppercase') // No badge styling\n * .setFieldFormats({\n * description: 'truncate(50)|capitalize',\n * createdAt: 'relative'\n * });\n *\n * // Nested DataView for complex objects:\n * dataView.setFieldFormat('permissions', null); // Clear auto-format\n * // Then configure field: { name: 'permissions', type: 'dataview', label: 'User Permissions' }\n * ```\n */\n\nimport View from '@core/View.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass DataView extends View {\n constructor(options = {}) {\n // Extract DataView-specific options\n const {\n data,\n model,\n fields,\n columns,\n responsive,\n showEmptyValues,\n emptyValueText,\n ...viewOptions\n } = options;\n\n // Set default view options\n super({\n tagName: 'div',\n className: 'data-view',\n ...viewOptions\n });\n\n // Core properties\n this.data = data || {};\n this.fields = fields || [];\n this.model = model || null;\n\n // If a model is provided, use it as data source\n if (this.model) {\n this.data = this.model;\n }\n\n // DataView-specific configuration\n this.dataViewOptions = {\n columns: columns || 2,\n responsive: responsive !== false, // Default to true\n showEmptyValues: showEmptyValues || false,\n emptyValueText: emptyValueText || '—',\n rowClass: 'row g-3',\n itemClass: 'data-view-item',\n labelClass: 'data-view-label fw-semibold text-muted small text-uppercase',\n valueClass: 'data-view-value'\n };\n }\n\n /**\n * Lifecycle hook - prepare data and fields before rendering\n */\n async onBeforeRender() {\n\n // Auto-generate fields from data if none provided\n if (this.fields.length === 0 && this.getData()) {\n this.generateFieldsFromData();\n }\n }\n\n /**\n * Override renderTemplate to generate HTML directly\n * @returns {string} Complete HTML string\n */\n async renderTemplate() {\n const items = this.buildItemsHTML();\n\n return `\n <div class=\"${this.dataViewOptions.rowClass}\">\n ${items}\n </div>\n `;\n }\n\n /**\n * Auto-generate field definitions from data object with intelligent type inference\n */\n generateFieldsFromData() {\n const dataObj = this.getData();\n\n if (dataObj && typeof dataObj === 'object') {\n this.fields = Object.keys(dataObj).map(key => {\n const value = dataObj[key];\n const fieldType = this.inferFieldType(value, key);\n const formatter = this.inferFormatter(value, key, fieldType);\n\n return {\n name: key,\n label: this.formatLabel(key),\n type: fieldType,\n format: formatter\n };\n });\n }\n }\n\n /**\n * Format field name into a readable label\n * @param {string} name - Field name\n * @returns {string} Formatted label\n */\n formatLabel(name) {\n return name\n .replace(/([A-Z])/g, ' $1') // Add space before capital letters\n .replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces\n .replace(/\\b\\w/g, l => l.toUpperCase()) // Title case\n .trim();\n }\n\n /**\n * Infer field type from value and key with improved intelligence\n * @param {*} value - Field value\n * @param {string} key - Field key\n * @returns {string} Field type\n */\n inferFieldType(value, key = '') {\n if (value === null || value === undefined) return 'text';\n\n const keyLower = key.toLowerCase();\n const type = typeof value;\n\n // Date/time patterns\n if (keyLower.includes('date') || keyLower.includes('time') ||\n keyLower.includes('created') || keyLower.includes('updated') ||\n keyLower.includes('modified') || keyLower.includes('last_login') ||\n keyLower.includes('expires') || keyLower.includes('last_activity')) {\n return 'datetime';\n }\n\n // Email patterns\n if (keyLower.includes('email') || keyLower.includes('mail')) {\n return 'email';\n }\n\n // URL patterns\n if (keyLower.includes('url') || keyLower.includes('link') ||\n keyLower.includes('website') || keyLower.includes('homepage')) {\n return 'url';\n }\n\n // Phone patterns\n if (keyLower.includes('phone') || keyLower.includes('tel') ||\n keyLower.includes('mobile') || keyLower.includes('cell')) {\n return 'phone';\n }\n\n // Currency/price patterns\n if (keyLower.includes('price') || keyLower.includes('cost') ||\n keyLower.includes('amount') || keyLower.includes('fee') ||\n keyLower.includes('salary') || keyLower.includes('revenue')) {\n return 'currency';\n }\n\n // File size patterns\n if (keyLower.includes('size') || keyLower.includes('bytes')) {\n return 'filesize';\n }\n\n // Percentage patterns\n if (keyLower.includes('percent') || keyLower.includes('rate') ||\n keyLower.includes('ratio') && type === 'number') {\n return 'percent';\n }\n\n // Type-based inference\n if (type === 'boolean') return 'boolean';\n if (type === 'number') return 'number';\n\n if (type === 'object') {\n if (Array.isArray(value)) return 'array';\n if (value && value.renditions) return 'file';\n\n // Check if object should be displayed as nested DataView\n if (this.shouldUseDataView(value, keyLower)) {\n return 'dataview';\n }\n\n return 'object';\n }\n\n if (type === 'string') {\n // Pattern matching for strings\n if (value.includes('@') && value.includes('.')) return 'email';\n if (value.match(/^\\d{4}-\\d{2}-\\d{2}/)) return 'date';\n if (value.match(/^https?:\\/\\//)) return 'url';\n if (value.match(/^\\+?[\\d\\s\\-\\(\\)]+$/)) return 'phone';\n }\n\n return 'text';\n }\n\n /**\n * Infer appropriate formatter based on type and context\n * @param {*} value - Field value\n * @param {string} key - Field key\n * @param {string} fieldType - Inferred field type\n * @returns {string|null} Formatter pipe string\n */\n inferFormatter(value, key, fieldType) {\n const keyLower = key.toLowerCase();\n const formatters = [];\n\n switch (fieldType) {\n case 'datetime':\n if (keyLower.includes('time') && !keyLower.includes('date')) {\n formatters.push('time');\n } else if (keyLower.includes('relative') || keyLower.includes('ago') || keyLower.includes('last_')) {\n formatters.push('relative');\n } else if (keyLower.includes('created') || keyLower.includes('updated') || keyLower.includes('modified')) {\n formatters.push('date(\"MMM D, YYYY\")');\n } else {\n formatters.push('date(\"MMMM D, YYYY\")');\n }\n break;\n\n case 'date':\n if (keyLower.includes('birth') || keyLower.includes('dob')) {\n formatters.push('date(\"MMMM D, YYYY\")');\n } else {\n formatters.push('date(\"MMM D, YYYY\")');\n }\n break;\n\n case 'email':\n // Don't apply email formatter - DataView handles mailto: links automatically\n break;\n\n case 'url':\n // Don't apply url formatter - DataView handles clickable links automatically\n break;\n\n case 'phone':\n formatters.push('phone');\n break;\n\n case 'currency':\n formatters.push('currency');\n // Add currency symbol detection if needed\n if (keyLower.includes('eur') || keyLower.includes('euro')) {\n formatters[formatters.length - 1] = 'currency(\"EUR\")';\n } else if (keyLower.includes('gbp') || keyLower.includes('pound')) {\n formatters[formatters.length - 1] = 'currency(\"GBP\")';\n }\n break;\n\n case 'filesize':\n formatters.push('filesize');\n break;\n\n case 'percent':\n formatters.push('percent');\n break;\n\n case 'number':\n // Smart number formatting with pipe chains\n if (typeof value === 'number') {\n if (keyLower.includes('count') || keyLower.includes('total') || keyLower.includes('followers') || keyLower.includes('views')) {\n if (value >= 1000) {\n formatters.push('compact');\n } else {\n formatters.push('number');\n }\n } else if (keyLower.includes('score') || keyLower.includes('rating')) {\n formatters.push('number');\n // Add decimal places for scores\n if (value % 1 !== 0) {\n formatters[formatters.length - 1] = 'number(1)';\n }\n } else if (keyLower.includes('version') || keyLower.includes('id')) {\n // Don't format IDs and versions\n return null;\n } else {\n formatters.push('number');\n }\n }\n break;\n\n case 'boolean':\n // Don't apply boolean formatter - DataView handles badge styling automatically\n break;\n\n case 'text':\n // Smart text formatting with contextual pipe chains\n if (typeof value === 'string') {\n // Handle different text contexts\n if (keyLower.includes('description') || keyLower.includes('content') || keyLower.includes('body')) {\n if (value.length > 200) {\n formatters.push('truncate(200)');\n } else if (value.length > 100) {\n formatters.push('truncate(100)');\n }\n } else if (keyLower.includes('summary') || keyLower.includes('excerpt')) {\n if (value.length > 150) {\n formatters.push('truncate(150)');\n }\n } else if (keyLower.includes('name') || keyLower.includes('title') || keyLower.includes('label')) {\n formatters.push('capitalize');\n if (value.length > 50) {\n formatters.unshift('truncate(50)'); // Truncate first, then capitalize\n }\n } else if (keyLower.includes('slug') || keyLower.includes('handle') || keyLower.includes('username')) {\n formatters.push('slug');\n } else if (keyLower.includes('code') || keyLower.includes('token') || keyLower.includes('key')) {\n // Show codes/tokens with masking if long\n if (value.length > 20) {\n formatters.push('mask');\n }\n } else {\n // Generic text handling\n if (value.length > 100) {\n formatters.push('truncate(100)');\n }\n }\n }\n break;\n\n case 'array':\n case 'object':\n // Don't apply json formatter - DataView handles JSON display automatically\n break;\n\n case 'dataview':\n // Don't apply any formatter - nested DataView handles its own formatting\n break;\n\n default:\n // Handle any missed cases with basic text formatting\n if (typeof value === 'string' && value.length > 100) {\n formatters.push('truncate(100)');\n }\n break;\n }\n\n return formatters.length > 0 ? formatters.join('|') : null;\n }\n\n /**\n * Determine if an object should be displayed as nested DataView vs JSON\n * @param {object} value - Object value to check\n * @param {string} keyLower - Lowercase field key\n * @returns {boolean} True if should use DataView\n */\n shouldUseDataView(value, keyLower) {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n\n // Check for common patterns that benefit from DataView display\n const dataViewPatterns = [\n 'permissions', 'perms', 'access', 'rights',\n 'settings', 'config', 'configuration', 'options',\n 'profile', 'info', 'details', 'data',\n 'metadata', 'meta', 'attributes', 'props',\n 'preferences', 'prefs', 'user_data',\n 'contact', 'address', 'location',\n 'stats', 'statistics', 'metrics', 'counts'\n ];\n\n if (window.utils && window.utils.isObject(value) && value.id) {\n return true;\n }\n\n // Check if key matches common patterns\n const matchesPattern = dataViewPatterns.some(pattern => keyLower.includes(pattern));\n\n if (matchesPattern) {\n // Additional checks to ensure it's suitable for DataView\n const keys = Object.keys(value);\n\n // Good candidates: objects with multiple simple key-value pairs\n if (keys.length >= 2 && keys.length <= 20) {\n const hasComplexNesting = keys.some(k =>\n typeof value[k] === 'object' &&\n value[k] !== null &&\n !Array.isArray(value[k]) &&\n Object.keys(value[k]).length > 3\n );\n\n // Use DataView if not too deeply nested\n if (!hasComplexNesting) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n /**\n * Get data object (handles both raw objects and Models)\n * @returns {object} Data object\n */\n getData() {\n if (this.model && this.model.attributes) {\n return { ...this.model.attributes };\n }\n return this.data || {};\n }\n\n /**\n * Get field value with formatting support\n * @param {object} field - Field definition\n * @returns {*} Field value (formatted if specified)\n */\n getFieldValue(field) {\n let value;\n\n // Get raw value from data source\n if (this.model && typeof this.model.get === 'function') {\n // For models, get raw value first, then apply formatting separately\n // This ensures we don't break pipe chains by concatenating strings\n value = this.model.get(field.name);\n } else {\n // Plain object access\n value = this.getData()[field.name];\n }\n\n // Apply formatting using DataFormatter pipe system if specified\n if (field.format) {\n value = dataFormatter.pipe(value, field.format);\n }\n\n // Handle empty values\n if (value === null || value === undefined || value === '') {\n return this.dataViewOptions.showEmptyValues ? this.dataViewOptions.emptyValueText : null;\n }\n\n // Apply template if provided\n if (field.template) {\n const modelData = this.model ? this.model : this.data;\n return this.renderTemplateString(field.template, modelData);\n }\n\n return value;\n }\n\n /**\n * Render a template string with data from a model or object.\n * Replaces {{key}} and {{nested.key}} placeholders.\n * @param {string} templateString - The template string.\n * @param {object} data - The data object or model.\n * @returns {string} The rendered string.\n */\n renderTemplateString(templateString, data) {\n if (!templateString || !data) {\n return '';\n }\n\n // Regex to find all {{...}} placeholders\n return templateString.replace(/\\{\\{([^}]+)\\}\\}/g, (match, key) => {\n const trimmedKey = key.trim();\n let value;\n\n // Handle potential formatters in the template key\n const parts = trimmedKey.split('|');\n const dataKey = parts[0];\n const formatters = parts.slice(1).join('|');\n\n // Get value from model or plain object\n if (this.model && typeof this.model.get === 'function') {\n value = this.model.get(dataKey);\n } else {\n // Handle nested keys for plain objects\n value = dataKey.split('.').reduce((o, i) => (o ? o[i] : undefined), data);\n }\n\n // Apply formatters if any\n if (formatters) {\n value = dataFormatter.pipe(value, formatters);\n }\n\n return value !== undefined && value !== null ? value : '';\n });\n }\n\n /**\n * Generate column classes based on configuration\n * @param {object} field - Field definition\n * @returns {string} CSS classes\n */\n getColumnClasses(field) {\n let classes = this.getColumnSizeClasses(field);\n if (field.justify == \"right\") {\n classes += ` d-flex justify-content-end`;\n } else if (field.justify == \"center\") {\n classes += ` d-flex justify-content-center`;\n }\n return classes;\n }\n\n /**\n * Generate column size classes based on configuration\n * @param {object} field - Field definition\n * @returns {string} CSS classes\n */\n getColumnSizeClasses(field) {\n // JSON objects and nested DataViews always use full width for better display\n if (field.type === 'array' || field.type === 'object' || field.type === 'dataview') {\n return 'col-12';\n }\n\n const colSize = field.columns || field.colSize || field.cols || Math.floor(12 / this.dataViewOptions.columns);\n\n if (this.dataViewOptions.responsive) {\n // Responsive breakpoints: 1 column on small, configured on larger screens\n return `col-12 col-md-${colSize}`;\n }\n\n return `col-${colSize}`;\n }\n\n /**\n * Build HTML for all data items\n * @returns {string} Items HTML\n */\n buildItemsHTML() {\n return this.fields\n .map(field => this.buildItemHTML(field))\n .filter(Boolean) // Remove empty items\n .join('');\n }\n\n /**\n * Build HTML for a single data item\n * @param {object} field - Field definition\n * @returns {string} Item HTML\n */\n buildItemHTML(field) {\n const value = this.getFieldValue(field);\n\n // Skip fields with no value if showEmptyValues is false\n if (value === null && !this.dataViewOptions.showEmptyValues) {\n return '';\n }\n\n const label = field.label || this.formatLabel(field.name);\n const colClasses = this.getColumnClasses(field);\n\n return `\n <div class=\"${colClasses}\">\n <div class=\"${this.dataViewOptions.itemClass} ${field.className}\" data-field=\"${field.name}\">\n ${this.buildLabelHTML(label, field)}\n ${this.buildValueHTML(value, field)}\n </div>\n </div>\n `;\n }\n\n /**\n * Build label HTML\n * @param {string} label - Label text\n * @param {object} field - Field definition\n * @returns {string} Label HTML\n */\n buildLabelHTML(label, field) {\n const labelClass = field.labelClass || this.dataViewOptions.labelClass;\n return `<div class=\"${labelClass}\">${this.escapeHtml(label)}:</div>`;\n }\n\n /**\n * Build value HTML with type-specific formatting\n * @param {*} value - Field value\n * @param {object} field - Field definition\n * @returns {string} Value HTML\n */\n buildValueHTML(value, field) {\n const valueClass = field.valueClass || this.dataViewOptions.valueClass;\n const displayValue = this.formatDisplayValue(value, field);\n return `<div class=\"${valueClass}\">${displayValue}</div>`;\n }\n\n /**\n * Format value for display with enhanced type handling\n * @param {*} value - Formatted value from DataFormatter (or raw if no format)\n * @param {object} field - Field definition\n * @returns {string} Formatted display value with HTML markup\n */\n formatDisplayValue(value, field) {\n if (value === null || value === undefined) {\n return this.dataViewOptions.emptyValueText;\n }\n\n // If a template was used, the value is already the final HTML. Return it directly.\n if (field.template) {\n return String(value);\n }\n\n // If a custom format is specified, we trust the DataFormatter.\n // However, we must determine if the output is intended to be HTML or plain text.\n if (field.format) {\n // A list of formatters known to produce safe HTML output.\n // A list of formatters known to produce safe HTML output.\n // A list of formatters known to produce safe HTML output.\n const htmlSafeFormatters = [\n 'badge', 'email', 'url', 'icon', 'status',\n 'image', 'avatar', 'phone', 'highlight', 'pre'\n ];\n\n // Parse the pipe string to find the last formatter applied.\n const pipes = dataFormatter.parsePipeString(field.format);\n const lastFormatter = pipes.length > 0 ? pipes[pipes.length - 1].name.toLowerCase() : null;\n\n // If the last formatter is in our safe list, render the HTML directly.\n if (lastFormatter && htmlSafeFormatters.includes(lastFormatter)) {\n return String(value);\n }\n\n // Otherwise, escape the output for security.\n return this.escapeHtml(String(value));\n }\n\n // No custom format - apply DataView's default type-specific HTML formatting\n // Get original raw value for special cases\n const rawValue = this.getData()[field.name];\n\n // Handle types that need special HTML presentation (only when no custom format)\n switch (field.type) {\n case 'boolean':\n // Use standard boolean badges (no custom format was applied)\n return rawValue ?\n '<span class=\"badge bg-success\">Yes</span>' :\n '<span class=\"badge bg-secondary\">No</span>';\n\n case 'email':\n // Create clickable email links (no custom format was applied)\n const emailStr = String(value);\n return `<a href=\"mailto:${this.escapeHtml(emailStr)}\" class=\"text-decoration-none\">${this.escapeHtml(emailStr)}</a>`;\n\n case 'url':\n // Create clickable URL links (no custom format was applied)\n const urlStr = String(value);\n return `<a href=\"${this.escapeHtml(urlStr)}\" target=\"_blank\" rel=\"noopener\" class=\"text-decoration-none\">${this.escapeHtml(urlStr)} <i class=\"bi bi-box-arrow-up-right\"></i></a>`;\n\n case 'array':\n case 'object':\n // Display as JSON with special HTML formatting (no custom format was applied)\n return this.formatAsJson(rawValue);\n\n case 'dataview':\n // Create nested DataView for complex objects\n return this.formatAsDataView(rawValue, field);\n\n case 'phone':\n // Create tel: links for phone numbers (no custom format was applied)\n const phoneStr = String(value);\n const telHref = phoneStr.replace(/[^\\d\\+]/g, ''); // Clean for tel: link\n return `<a href=\"tel:${telHref}\" class=\"text-decoration-none\">${this.escapeHtml(phoneStr)}</a>`;\n\n default:\n // For all other types with no custom format, just escape and return\n return this.escapeHtml(String(value));\n }\n }\n\n /**\n * Format object/array values as styled JSON\n * @param {*} value - Object or array value\n * @returns {string} Formatted JSON HTML\n */\n formatAsJson(value) {\n try {\n const jsonString = JSON.stringify(value, null, 2);\n const escapedJson = this.escapeHtml(jsonString);\n const lines = jsonString.split('\\n').length;\n const isLarge = lines > 10 || jsonString.length > 500;\n const uniqueId = `json-${Math.random().toString(36).substr(2, 9)}`;\n\n // Create collapsible JSON display for large objects\n if (isLarge) {\n const preview = JSON.stringify(value).substring(0, 100) + (JSON.stringify(value).length > 100 ? '...' : '');\n const escapedPreview = this.escapeHtml(preview);\n\n return `\n <div class=\"json-container\">\n <div class=\"d-flex align-items-center justify-content-between mb-1\">\n <small class=\"text-muted\">${Array.isArray(value) ? 'Array' : 'Object'} (${lines} lines)</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm json-toggle\" data-bs-toggle=\"collapse\" data-bs-target=\"#${uniqueId}\" aria-expanded=\"false\">\n <i class=\"bi bi-eye\"></i> Show\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm json-copy\" data-json='${this.escapeHtml(jsonString)}' title=\"Copy JSON\">\n <i class=\"bi bi-clipboard\"></i>\n </button>\n </div>\n </div>\n <div class=\"json-preview bg-light p-2 rounded small border\" style=\"font-family: 'Courier New', monospace;\">\n <code class=\"text-muted\">${escapedPreview}</code>\n </div>\n <div class=\"collapse mt-2\" id=\"${uniqueId}\">\n <pre class=\"json-display p-3 rounded small mb-0\" style=\"max-height: 400px; overflow-y: auto; white-space: pre-wrap; font-family: 'Courier New', monospace;\"><code>${this.syntaxHighlightJson(escapedJson)}</code></pre>\n </div>\n </div>\n `;\n } else {\n // Small objects - show directly with copy button\n return `\n <div class=\"json-container\">\n <div class=\"d-flex align-items-center justify-content-between mb-1\">\n <small class=\"text-muted\">${Array.isArray(value) ? 'Array' : 'Object'}</small>\n <button type=\"button\" class=\"btn btn-outline-secondary btn-sm json-copy\" data-json='${this.escapeHtml(jsonString)}' title=\"Copy JSON\">\n <i class=\"bi bi-clipboard\"></i>\n </button>\n </div>\n <pre class=\"json-display bg-light p-2 rounded small mb-0 border\" style=\"white-space: pre-wrap; font-family: 'Courier New', monospace;\"><code>${this.syntaxHighlightJson(escapedJson)}</code></pre>\n </div>\n `;\n }\n } catch (error) {\n // Fallback for objects that can't be stringified\n return `<span class=\"text-muted fst-italic\">[Object: ${typeof value}] - Cannot display as JSON</span>`;\n }\n }\n\n /**\n * Apply basic syntax highlighting to JSON\n * @param {string} json - Escaped JSON string\n * @returns {string} JSON with basic syntax highlighting\n */\n syntaxHighlightJson(json) {\n return json\n .replace(/(\"([^\"\\\\]|\\\\.)*\")\\s*:/g, '<span style=\"color: #0969da;\">$1</span>:') // Keys (blue)\n .replace(/:\\s*(\"([^\"\\\\]|\\\\.)*\")/g, ': <span style=\"color: #0a3069;\">$1</span>') // String values (dark blue)\n .replace(/:\\s*(true|false)/g, ': <span style=\"color: #8250df;\">$1</span>') // Booleans (purple)\n .replace(/:\\s*(null)/g, ': <span style=\"color: #656d76;\">$1</span>') // Null (gray)\n .replace(/:\\s*(-?\\d+\\.?\\d*)/g, ': <span style=\"color: #0550ae;\">$1</span>'); // Numbers (blue)\n }\n\n /**\n * Bind events including JSON interaction handlers\n */\n bindEvents() {\n super.bindEvents();\n\n if (!this.element) return;\n\n // Add click handler for field interactions\n this.element.addEventListener('click', (e) => {\n const fieldElement = e.target.closest('[data-field]');\n if (fieldElement) {\n const fieldName = fieldElement.dataset.field;\n const field = this.fields.find(f => f.name === fieldName);\n this.emit('field:click', { field, fieldName, element: fieldElement, event: e });\n }\n\n // Handle JSON copy button clicks\n if (e.target.closest('.json-copy')) {\n e.preventDefault();\n e.stopPropagation();\n this.handleJsonCopy(e.target.closest('.json-copy'));\n }\n\n // Handle JSON toggle button clicks\n if (e.target.closest('.json-toggle')) {\n this.handleJsonToggle(e.target.closest('.json-toggle'));\n }\n });\n }\n\n /**\n * Handle copying JSON to clipboard\n * @param {HTMLElement} button - Copy button element\n */\n handleJsonCopy(button) {\n const jsonData = button.getAttribute('data-json');\n if (!jsonData) return;\n\n try {\n // Use modern clipboard API if available\n if (navigator.clipboard && window.isSecureContext) {\n navigator.clipboard.writeText(jsonData).then(() => {\n this.showCopyFeedback(button);\n });\n } else {\n // Fallback for older browsers\n const textarea = document.createElement('textarea');\n textarea.value = jsonData;\n document.body.appendChild(textarea);\n textarea.select();\n document.execCommand('copy');\n document.body.removeChild(textarea);\n this.showCopyFeedback(button);\n }\n } catch (error) {\n console.warn('Failed to copy JSON:', error);\n }\n }\n\n /**\n * Handle JSON toggle button state\n * @param {HTMLElement} button - Toggle button element\n */\n handleJsonToggle(button) {\n const icon = button.querySelector('i');\n const isExpanded = button.getAttribute('aria-expanded') === 'true';\n\n // Update button text and icon\n setTimeout(() => {\n if (isExpanded) {\n icon.className = 'bi bi-eye-slash';\n button.innerHTML = '<i class=\"bi bi-eye-slash\"></i> Hide';\n } else {\n icon.className = 'bi bi-eye';\n button.innerHTML = '<i class=\"bi bi-eye\"></i> Show';\n }\n }, 10);\n }\n\n /**\n * Show visual feedback for successful copy\n * @param {HTMLElement} button - Copy button element\n */\n showCopyFeedback(button) {\n const originalIcon = button.querySelector('i').className;\n const icon = button.querySelector('i');\n\n // Show success state\n icon.className = 'bi bi-check text-success';\n button.classList.add('btn-success');\n button.classList.remove('btn-outline-secondary');\n\n // Reset after 1 second\n setTimeout(() => {\n icon.className = originalIcon;\n button.classList.remove('btn-success');\n button.classList.add('btn-outline-secondary');\n }, 1000);\n }\n\n /**\n * Format complex objects as nested DataView\n * @param {object} value - Object value to display as DataView\n * @param {object} field - Field definition\n * @returns {string} Formatted DataView HTML\n */\n formatAsDataView(value, field) {\n if (!value || typeof value !== 'object') {\n return `<span class=\"text-muted fst-italic\">No data available</span>`;\n }\n\n try {\n // Create nested DataView instance\n const nestedView = new (this.constructor)({\n data: value,\n columns: field.dataViewColumns || 2,\n showEmptyValues: field.showEmptyValues ?? true,\n emptyValueText: field.emptyValueText || 'Not set',\n // Pass any other dataView-specific options from field config\n ...(field.dataViewOptions || {})\n });\n\n nestedView.onInit();\n nestedView.generateFieldsFromData();\n // Generate the nested DataView HTML\n const nestedHtml = nestedView.buildItemsHTML();\n\n // Wrap in a styled container with optional label\n // const labelHtml = field.label ?\n // `<h6 class=\"fw-semibold text-muted mb-3 border-bottom pb-2\">${this.escapeHtml(field.label)}</h6>` :\n // '';\n\n return `\n <div class=\"nested-dataview border rounded p-3 bg-light\">\n <div class=\"${nestedView.dataViewOptions.rowClass}\">\n ${nestedHtml}\n </div>\n </div>\n `;\n } catch (error) {\n console.error('Error creating nested DataView:', error);\n return `<span class=\"text-danger\">Error displaying nested data</span>`;\n }\n }\n\n /**\n * Update the data and re-render\n * @param {object} newData - New data object\n * @returns {Promise<DataView>} Promise resolving to this instance\n */\n async updateData(newData) {\n this.data = newData;\n\n // If model is provided, update it instead\n if (this.model && typeof this.model.set === 'function') {\n this.model.set(newData);\n }\n\n // Clear fields to trigger regeneration on next render\n if (this.fields.length > 0 && !this.options.fields) {\n this.fields = [];\n }\n\n await this.render();\n this.emit('data:updated', { data: newData });\n return this;\n }\n\n /**\n * Update field configuration and re-render\n * @param {array} newFields - New field configuration\n * @returns {Promise<DataView>} Promise resolving to this instance\n */\n async updateFields(newFields) {\n this.fields = newFields;\n await this.render();\n this.emit('fields:updated', { fields: newFields });\n return this;\n }\n\n /**\n * Update configuration and re-render\n * @param {object} newOptions - New configuration options\n * @returns {Promise<DataView>} Promise resolving to this instance\n */\n async updateConfig(newOptions) {\n this.dataViewOptions = { ...this.dataViewOptions, ...newOptions };\n await this.render();\n this.emit('config:updated', { options: this.dataViewOptions });\n return this;\n }\n\n /**\n * Refresh data from model if available\n * @returns {Promise<DataView>} Promise resolving to this instance\n */\n async refresh() {\n if (this.model && typeof this.model.fetch === 'function') {\n try {\n await this.model.fetch();\n this.emit('data:refreshed', { model: this.model });\n } catch (error) {\n this.emit('error', { error, message: 'Failed to refresh data' });\n throw error;\n }\n }\n return this;\n }\n\n /**\n * Get current data\n * @returns {object} Current data object\n */\n getCurrentData() {\n return this.getData();\n }\n\n /**\n * Get field definition by name\n * @param {string} name - Field name\n * @returns {object|null} Field definition\n */\n getField(name) {\n return this.fields.find(field => field.name === name) || null;\n }\n\n /**\n * Set custom format for a specific field\n * @param {string} fieldName - Name of the field\n * @param {string} format - Pipe format string (e.g., \"currency|uppercase\")\n * @returns {DataView} This instance for chaining\n */\n setFieldFormat(fieldName, format) {\n const field = this.getField(fieldName);\n if (field) {\n field.format = format;\n } else {\n // Create new field if it doesn't exist\n this.fields.push({\n name: fieldName,\n label: this.formatLabel(fieldName),\n type: this.inferFieldType(this.getData()[fieldName], fieldName),\n format: format\n });\n }\n return this;\n }\n\n /**\n * Add additional formatter to existing field format pipe chain\n * @param {string} fieldName - Name of the field\n * @param {string} formatter - Formatter to add (e.g., \"uppercase\", \"truncate(50)\")\n * @returns {DataView} This instance for chaining\n */\n addFormatPipe(fieldName, formatter) {\n const field = this.getField(fieldName);\n if (field) {\n if (field.format) {\n field.format += `|${formatter}`;\n } else {\n field.format = formatter;\n }\n }\n return this;\n }\n\n /**\n * Clear custom format for a field (revert to auto-inferred format)\n * @param {string} fieldName - Name of the field\n * @returns {DataView} This instance for chaining\n */\n clearFieldFormat(fieldName) {\n const field = this.getField(fieldName);\n if (field) {\n const data = this.getData();\n field.format = this.inferFormatter(data[fieldName], fieldName, field.type);\n }\n return this;\n }\n\n /**\n * Get formatted value for a specific field without rendering\n * @param {string} fieldName - Name of the field\n * @param {*} value - Optional value to format (uses current data if not provided)\n * @returns {*} Formatted value\n */\n getFormattedValue(fieldName, value = null) {\n const field = this.getField(fieldName);\n if (!field) return null;\n\n const targetValue = value !== null ? value : this.getData()[fieldName];\n\n if (field.format && targetValue != null) {\n return dataFormatter.pipe(targetValue, field.format);\n }\n\n return targetValue;\n }\n\n /**\n * Set multiple field formats at once\n * @param {object} formats - Object mapping field names to format strings\n * @returns {DataView} This instance for chaining\n */\n setFieldFormats(formats) {\n Object.entries(formats).forEach(([fieldName, format]) => {\n this.setFieldFormat(fieldName, format);\n });\n return this;\n }\n\n /**\n * Get all current field formats as an object\n * @returns {object} Object mapping field names to their current formats\n */\n getFieldFormats() {\n const formats = {};\n this.fields.forEach(field => {\n if (field.format) {\n formats[field.name] = field.format;\n }\n });\n return formats;\n }\n\n /**\n * Set up model event listeners if model is provided\n */\n onInit() {\n super.onInit();\n\n // Listen for model changes\n if (this.model && typeof this.model.on === 'function') {\n this.model.on('change', () => {\n this.render();\n });\n }\n }\n\n /**\n * Static factory method\n * @param {object} options - DataView options\n * @returns {DataView} New DataView instance\n */\n static create(options = {}) {\n return new DataView(options);\n }\n}\n\nexport default DataView;\n"],"names":["DataView","View","constructor","options","data","model","fields","columns","responsive","showEmptyValues","emptyValueText","viewOptions","super","tagName","className","this","dataViewOptions","rowClass","itemClass","labelClass","valueClass","onBeforeRender","length","getData","generateFieldsFromData","renderTemplate","items","buildItemsHTML","dataObj","Object","keys","map","key","value","fieldType","inferFieldType","formatter","inferFormatter","name","label","formatLabel","type","format","replace","l","toUpperCase","trim","keyLower","toLowerCase","includes","Array","isArray","renditions","shouldUseDataView","match","formatters","push","unshift","join","window","utils","isObject","id","some","pattern","k","attributes","getFieldValue","field","get","dataFormatter","pipe","template","modelData","renderTemplateString","templateString","parts","split","dataKey","slice","reduce","o","i","getColumnClasses","classes","getColumnSizeClasses","justify","colSize","cols","Math","floor","buildItemHTML","filter","Boolean","buildLabelHTML","buildValueHTML","escapeHtml","formatDisplayValue","String","htmlSafeFormatters","pipes","parsePipeString","lastFormatter","rawValue","emailStr","urlStr","formatAsJson","formatAsDataView","phoneStr","jsonString","JSON","stringify","escapedJson","lines","isLarge","uniqueId","random","toString","substr","preview","substring","escapedPreview","syntaxHighlightJson","error","json","bindEvents","element","addEventListener","e","fieldElement","target","closest","fieldName","dataset","find","f","emit","event","preventDefault","stopPropagation","handleJsonCopy","handleJsonToggle","button","jsonData","getAttribute","navigator","clipboard","isSecureContext","writeText","then","showCopyFeedback","textarea","document","createElement","body","appendChild","select","execCommand","removeChild","console","warn","icon","querySelector","isExpanded","setTimeout","innerHTML","originalIcon","classList","add","remove","nestedView","dataViewColumns","onInit","nestedHtml","updateData","newData","set","render","updateFields","newFields","updateConfig","newOptions","refresh","fetch","message","getCurrentData","getField","setFieldFormat","addFormatPipe","clearFieldFormat","getFormattedValue","targetValue","setFieldFormats","formats","entries","forEach","getFieldFormats","on","create"],"mappings":"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"}
|