web-mojo 2.2.57 → 2.2.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +1 -10105
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.es.js +1 -588
  7. package/dist/auth.es.js.map +1 -1
  8. package/dist/charts.cjs.js +1 -1
  9. package/dist/charts.es.js +1 -571
  10. package/dist/charts.es.js.map +1 -1
  11. package/dist/chunks/ChatView-D4A9rIX3.js +2 -0
  12. package/dist/chunks/ChatView-D4A9rIX3.js.map +1 -0
  13. package/dist/chunks/ChatView-nxaq8aIo.js +2 -0
  14. package/dist/chunks/ChatView-nxaq8aIo.js.map +1 -0
  15. package/dist/chunks/Collection-1sPoIFvQ.js +2 -0
  16. package/dist/chunks/{Collection-DaiL0uGl.js.map → Collection-1sPoIFvQ.js.map} +1 -1
  17. package/dist/chunks/{Collection-CxbNKOas.js → Collection-DSBRXpwK.js} +2 -2
  18. package/dist/chunks/{Collection-CxbNKOas.js.map → Collection-DSBRXpwK.js.map} +1 -1
  19. package/dist/chunks/{ContextMenu-ClwHEbbD.js → ContextMenu-BWy7WqF4.js} +2 -2
  20. package/dist/chunks/{ContextMenu-ClwHEbbD.js.map → ContextMenu-BWy7WqF4.js.map} +1 -1
  21. package/dist/chunks/ContextMenu-BvniQz-N.js +3 -0
  22. package/dist/chunks/{ContextMenu-sgvgSACY.js.map → ContextMenu-BvniQz-N.js.map} +1 -1
  23. package/dist/chunks/DataView--nUWtq6r.js +2 -0
  24. package/dist/chunks/{DataView-Dzo0jbs2.js.map → DataView--nUWtq6r.js.map} +1 -1
  25. package/dist/chunks/{DataView-1xh3GFeC.js → DataView-CK3Z0TJH.js} +2 -2
  26. package/dist/chunks/{DataView-1xh3GFeC.js.map → DataView-CK3Z0TJH.js.map} +1 -1
  27. package/dist/chunks/Dialog-BcgSR01Z.js +2 -0
  28. package/dist/chunks/{Dialog-DOGDalUq.js.map → Dialog-BcgSR01Z.js.map} +1 -1
  29. package/dist/chunks/{Dialog-CQlTDhZS.js → Dialog-DwCTFV6O.js} +2 -2
  30. package/dist/chunks/{Dialog-CQlTDhZS.js.map → Dialog-DwCTFV6O.js.map} +1 -1
  31. package/dist/chunks/FormPlugins-DvQ-G5J5.js +2 -0
  32. package/dist/chunks/{FormPlugins-DY6e88YT.js.map → FormPlugins-DvQ-G5J5.js.map} +1 -1
  33. package/dist/chunks/{FormView-DaKA4Sys.js → FormView-CRmEReTC.js} +3 -3
  34. package/dist/chunks/{FormView-DaKA4Sys.js.map → FormView-CRmEReTC.js.map} +1 -1
  35. package/dist/chunks/FormView-OLA7t-yv.js +3 -0
  36. package/dist/chunks/{FormView-Dz3mYasQ.js.map → FormView-OLA7t-yv.js.map} +1 -1
  37. package/dist/chunks/ListView-6JQ6tRXs.js +2 -0
  38. package/dist/chunks/{ListView-X5w5jf51.js.map → ListView-6JQ6tRXs.js.map} +1 -1
  39. package/dist/chunks/{ListView-CDzKIpd8.js → ListView-DVStKiMi.js} +2 -2
  40. package/dist/chunks/{ListView-CDzKIpd8.js.map → ListView-DVStKiMi.js.map} +1 -1
  41. package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js → MetricsCountryMapView-CnAEbUw_.js} +2 -2
  42. package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js.map → MetricsCountryMapView-CnAEbUw_.js.map} +1 -1
  43. package/dist/chunks/MetricsCountryMapView-J067qrrt.js +2 -0
  44. package/dist/chunks/{MetricsCountryMapView-B2xz6zUw.js.map → MetricsCountryMapView-J067qrrt.js.map} +1 -1
  45. package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js → MetricsMiniChartWidget-BeD1slGs.js} +2 -2
  46. package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js.map → MetricsMiniChartWidget-BeD1slGs.js.map} +1 -1
  47. package/dist/chunks/MetricsMiniChartWidget-x2gFjHOU.js +2 -0
  48. package/dist/chunks/{MetricsMiniChartWidget-DvKd7Qrk.js.map → MetricsMiniChartWidget-x2gFjHOU.js.map} +1 -1
  49. package/dist/chunks/PDFViewer-CsyKn-gh.js +2 -0
  50. package/dist/chunks/{PDFViewer-EJ9cOfPF.js.map → PDFViewer-CsyKn-gh.js.map} +1 -1
  51. package/dist/chunks/{PDFViewer-ofMGdSaj.js → PDFViewer-DSa4BZCm.js} +2 -2
  52. package/dist/chunks/{PDFViewer-ofMGdSaj.js.map → PDFViewer-DSa4BZCm.js.map} +1 -1
  53. package/dist/chunks/Rest-DHbszkuP.js +2 -0
  54. package/dist/chunks/Rest-DHbszkuP.js.map +1 -0
  55. package/dist/chunks/Rest-Ds9e8tN8.js +2 -0
  56. package/dist/chunks/Rest-Ds9e8tN8.js.map +1 -0
  57. package/dist/chunks/TokenManager-D6SjKgPZ.js +2 -0
  58. package/dist/chunks/{TokenManager-DoN9e6q6.js.map → TokenManager-D6SjKgPZ.js.map} +1 -1
  59. package/dist/chunks/{TokenManager-Gqvj7SDX.js → TokenManager-REbha1Le.js} +2 -2
  60. package/dist/chunks/{TokenManager-Gqvj7SDX.js.map → TokenManager-REbha1Le.js.map} +1 -1
  61. package/dist/chunks/WebApp-CULZpO_0.js +2 -0
  62. package/dist/chunks/{WebApp-6qvqmOts.js.map → WebApp-CULZpO_0.js.map} +1 -1
  63. package/dist/chunks/{WebApp-_dgpwtFw.js → WebApp-DovLtA60.js} +2 -2
  64. package/dist/chunks/{WebApp-_dgpwtFw.js.map → WebApp-DovLtA60.js.map} +1 -1
  65. package/dist/chunks/WebSocketClient-B-wc3mez.js +2 -0
  66. package/dist/chunks/{WebSocketClient-DG2olXpH.js.map → WebSocketClient-B-wc3mez.js.map} +1 -1
  67. package/dist/chunks/{WebSocketClient-MFkFlSue.js → WebSocketClient-BdZ9QYll.js} +2 -2
  68. package/dist/chunks/{WebSocketClient-MFkFlSue.js.map → WebSocketClient-BdZ9QYll.js.map} +1 -1
  69. package/dist/chunks/version-C3dnl1bg.js +2 -0
  70. package/dist/chunks/version-C3dnl1bg.js.map +1 -0
  71. package/dist/chunks/{version-BVADfTA5.js → version-ioN546cp.js} +2 -2
  72. package/dist/chunks/{version-BVADfTA5.js.map → version-ioN546cp.js.map} +1 -1
  73. package/dist/css/web-mojo.css +1 -1
  74. package/dist/docit.cjs.js +1 -1
  75. package/dist/docit.es.js +1 -957
  76. package/dist/docit.es.js.map +1 -1
  77. package/dist/index.cjs.js +1 -1
  78. package/dist/index.es.js +1 -3252
  79. package/dist/index.es.js.map +1 -1
  80. package/dist/lightbox.cjs.js +1 -1
  81. package/dist/lightbox.es.js +1 -3737
  82. package/dist/lightbox.es.js.map +1 -1
  83. package/dist/loader.umd.js +2 -2
  84. package/dist/map.cjs.js +1 -1
  85. package/dist/map.es.js +1 -1032
  86. package/dist/map.es.js.map +1 -1
  87. package/dist/mojo-auth.es.js +338 -0
  88. package/dist/mojo-auth.umd.js +1 -0
  89. package/dist/timeline.cjs.js +1 -1
  90. package/dist/timeline.es.js +1 -224
  91. package/dist/timeline.es.js.map +1 -1
  92. package/dist/web-mojo.lite.iife.js +14 -3
  93. package/dist/web-mojo.lite.iife.js.map +1 -1
  94. package/dist/web-mojo.lite.iife.min.js +6 -6
  95. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  96. package/package.json +2 -2
  97. package/dist/chunks/ChatView-9k6xBWXk.js +0 -7632
  98. package/dist/chunks/ChatView-9k6xBWXk.js.map +0 -1
  99. package/dist/chunks/ChatView-CdtuCDYm.js +0 -2
  100. package/dist/chunks/ChatView-CdtuCDYm.js.map +0 -1
  101. package/dist/chunks/Collection-DaiL0uGl.js +0 -1014
  102. package/dist/chunks/ContextMenu-sgvgSACY.js +0 -1535
  103. package/dist/chunks/DataView-Dzo0jbs2.js +0 -862
  104. package/dist/chunks/Dialog-DOGDalUq.js +0 -1579
  105. package/dist/chunks/FormPlugins-DY6e88YT.js +0 -124
  106. package/dist/chunks/FormView-Dz3mYasQ.js +0 -8636
  107. package/dist/chunks/ListView-X5w5jf51.js +0 -495
  108. package/dist/chunks/MetricsCountryMapView-B2xz6zUw.js +0 -1054
  109. package/dist/chunks/MetricsMiniChartWidget-DvKd7Qrk.js +0 -3283
  110. package/dist/chunks/PDFViewer-EJ9cOfPF.js +0 -946
  111. package/dist/chunks/Rest-CgSjfMaU.js +0 -2
  112. package/dist/chunks/Rest-CgSjfMaU.js.map +0 -1
  113. package/dist/chunks/Rest-W-sPfGh9.js +0 -4375
  114. package/dist/chunks/Rest-W-sPfGh9.js.map +0 -1
  115. package/dist/chunks/TokenManager-DoN9e6q6.js +0 -1423
  116. package/dist/chunks/WebApp-6qvqmOts.js +0 -1386
  117. package/dist/chunks/WebSocketClient-DG2olXpH.js +0 -209
  118. package/dist/chunks/version-OyPGnx30.js +0 -38
  119. package/dist/chunks/version-OyPGnx30.js.map +0 -1
@@ -1,1014 +0,0 @@
1
- import { b as EventEmitter, r as rest, a as MOJOUtils } from "./Rest-W-sPfGh9.js";
2
- class Model {
3
- constructor(data = {}, options = {}) {
4
- this.endpoint = options.endpoint || this.constructor.endpoint || "";
5
- this.id = data.id || null;
6
- this.attributes = { ...data };
7
- this._ = this.attributes;
8
- this.originalAttributes = { ...data };
9
- this.errors = {};
10
- this.loading = false;
11
- this.rest = rest;
12
- this.options = {
13
- idAttribute: "id",
14
- timestamps: true,
15
- ...options
16
- };
17
- }
18
- getContextValue(key) {
19
- return this.get(key);
20
- }
21
- /**
22
- * Get attribute value with support for dot notation and pipe formatting
23
- * @param {string} key - Attribute key with optional pipes (e.g., "name|uppercase")
24
- * @returns {*} Attribute value, possibly formatted
25
- */
26
- get(key) {
27
- if (!key.includes(".") && !key.includes("|") && this[key] !== void 0) {
28
- if (typeof this[key] === "function") {
29
- return this[key]();
30
- }
31
- return this[key];
32
- }
33
- return MOJOUtils.getContextData(this.attributes, key);
34
- }
35
- /**
36
- * Set attribute value(s)
37
- * @param {string|object} key - Attribute key or object of key-value pairs
38
- * @param {*} value - Attribute value (if key is string)
39
- * @param {object} options - Options (silent: true to not trigger change event)
40
- */
41
- set(key, value, options = {}) {
42
- const previousAttributes = JSON.parse(JSON.stringify(this.attributes));
43
- let hasChanged = false;
44
- if (key === void 0 || key === null) return;
45
- if (typeof key === "object") {
46
- for (const [attrKey, attrValue] of Object.entries(key)) {
47
- hasChanged = this._setNestedAttribute(attrKey, attrValue) || hasChanged;
48
- }
49
- if (key.id !== void 0) {
50
- this.id = key.id;
51
- }
52
- } else {
53
- if (key === "id") {
54
- this.id = value;
55
- hasChanged = true;
56
- } else {
57
- hasChanged = this._setNestedAttribute(key, value);
58
- }
59
- }
60
- if (hasChanged && !options.silent) {
61
- this.emit("change", this);
62
- if (typeof key === "string") {
63
- this.emit(`change:${key}`, value, this);
64
- } else {
65
- for (const [attr, val] of Object.entries(key)) {
66
- const finalValue = this._getNestedValue(attr);
67
- if (JSON.stringify(this._getNestedValue(attr, previousAttributes)) !== JSON.stringify(finalValue)) {
68
- this.emit(`change:${attr}`, finalValue, this);
69
- }
70
- }
71
- }
72
- }
73
- }
74
- /**
75
- * Set a nested attribute using dot notation
76
- * @param {string} key - Attribute key (may contain dots)
77
- * @param {*} value - Value to set
78
- * @returns {boolean} - Whether the value changed
79
- */
80
- _setNestedAttribute(key, value) {
81
- if (!key.includes(".")) {
82
- const oldValue2 = this.attributes[key];
83
- this.attributes[key] = value;
84
- this[key] = value;
85
- return oldValue2 !== value;
86
- }
87
- const keys = key.split(".");
88
- const topLevelKey = keys[0];
89
- if (!this.attributes[topLevelKey] || typeof this.attributes[topLevelKey] !== "object") {
90
- this.attributes[topLevelKey] = {};
91
- }
92
- if (!this[topLevelKey] || typeof this[topLevelKey] !== "object") {
93
- this[topLevelKey] = {};
94
- }
95
- const oldValue = this._getNestedValue(key);
96
- let attrTarget = this.attributes[topLevelKey];
97
- let instanceTarget = this[topLevelKey];
98
- for (let i = 1; i < keys.length - 1; i++) {
99
- const currentKey = keys[i];
100
- if (!attrTarget[currentKey] || typeof attrTarget[currentKey] !== "object") {
101
- attrTarget[currentKey] = {};
102
- }
103
- if (!instanceTarget[currentKey] || typeof instanceTarget[currentKey] !== "object") {
104
- instanceTarget[currentKey] = {};
105
- }
106
- attrTarget = attrTarget[currentKey];
107
- instanceTarget = instanceTarget[currentKey];
108
- }
109
- const finalKey = keys[keys.length - 1];
110
- attrTarget[finalKey] = value;
111
- instanceTarget[finalKey] = value;
112
- return JSON.stringify(oldValue) !== JSON.stringify(value);
113
- }
114
- /**
115
- * Get a nested value using dot notation
116
- * @param {string} key - Attribute key (may contain dots)
117
- * @param {object} source - Source object (defaults to this.attributes)
118
- * @returns {*} - The nested value
119
- */
120
- _getNestedValue(key, source = this.attributes) {
121
- if (!key.includes(".")) {
122
- return source[key];
123
- }
124
- const keys = key.split(".");
125
- let current = source;
126
- for (const k of keys) {
127
- if (current == null || typeof current !== "object") {
128
- return void 0;
129
- }
130
- current = current[k];
131
- }
132
- return current;
133
- }
134
- getData() {
135
- return this.attributes;
136
- }
137
- getId() {
138
- return this.id;
139
- }
140
- /**
141
- * Fetch model data from API with request deduplication and cancellation
142
- * @param {object} options - Request options
143
- * @param {number} options.debounceMs - Optional debounce delay in milliseconds
144
- * @returns {Promise} Promise that resolves with REST response
145
- */
146
- async fetch(options = {}) {
147
- let url = options.url;
148
- if (!url) {
149
- const id = options.id || this.getId();
150
- if (!id && this.options.requiresId !== false) {
151
- throw new Error("Model: ID is required for fetching");
152
- }
153
- url = this.buildUrl(id);
154
- }
155
- const requestKey = JSON.stringify({ url, params: options.params });
156
- if (options.debounceMs && options.debounceMs > 0) {
157
- return this._debouncedFetch(requestKey, options);
158
- }
159
- if (this.currentRequest && this.currentRequestKey !== requestKey) {
160
- console.info("Model: Cancelling previous request for new parameters");
161
- this.abortController?.abort();
162
- this.currentRequest = null;
163
- }
164
- if (this.currentRequest && this.currentRequestKey === requestKey) {
165
- console.info("Model: Duplicate request in progress, returning existing promise");
166
- return this.currentRequest;
167
- }
168
- const now = Date.now();
169
- const minInterval = 100;
170
- if (this.lastFetchTime && now - this.lastFetchTime < minInterval) {
171
- console.info("Model: Rate limited, skipping fetch");
172
- return this;
173
- }
174
- this.loading = true;
175
- this.errors = {};
176
- this.lastFetchTime = now;
177
- this.currentRequestKey = requestKey;
178
- this.abortController = new AbortController();
179
- this.currentRequest = this._performFetch(url, options, this.abortController);
180
- try {
181
- const result = await this.currentRequest;
182
- return result;
183
- } catch (error) {
184
- if (error.name === "AbortError") {
185
- console.info("Model: Request was cancelled");
186
- return this;
187
- }
188
- throw error;
189
- } finally {
190
- this.currentRequest = null;
191
- this.currentRequestKey = null;
192
- this.abortController = null;
193
- }
194
- }
195
- /**
196
- * Handle debounced fetch requests
197
- * @param {string} requestKey - Unique key for this request
198
- * @param {object} options - Fetch options
199
- * @returns {Promise} Promise that resolves with REST response
200
- */
201
- async _debouncedFetch(requestKey, options) {
202
- if (this.debouncedFetchTimeout) {
203
- clearTimeout(this.debouncedFetchTimeout);
204
- }
205
- this.cancel();
206
- return new Promise((resolve, reject) => {
207
- this.debouncedFetchTimeout = setTimeout(async () => {
208
- try {
209
- const result = await this.fetch({ ...options, debounceMs: 0 });
210
- resolve(result);
211
- } catch (error) {
212
- reject(error);
213
- }
214
- }, options.debounceMs);
215
- });
216
- }
217
- /**
218
- * Internal method to perform the actual fetch
219
- * @param {string} url - API endpoint URL
220
- * @param {object} options - Request options
221
- * @param {AbortController} abortController - Controller for request cancellation
222
- * @returns {Promise} Promise that resolves with REST response
223
- */
224
- async _performFetch(url, options, abortController) {
225
- try {
226
- if (options.graph && (!options.params || !options.params.graph)) {
227
- if (!options.params) options.params = {};
228
- options.params.graph = options.graph;
229
- }
230
- const response = await this.rest.GET(url, options.params, {
231
- signal: abortController.signal
232
- });
233
- if (response.success) {
234
- if (response.data.status) {
235
- this.originalAttributes = { ...this.attributes };
236
- if (response.data.data) this.set(response.data.data);
237
- this.errors = {};
238
- } else {
239
- this.errors = response.data;
240
- }
241
- } else {
242
- this.errors = response.errors || {};
243
- }
244
- return response;
245
- } catch (error) {
246
- if (error.name === "AbortError") {
247
- console.info("Model: Fetch was cancelled");
248
- throw error;
249
- }
250
- this.errors = { fetch: error.message };
251
- return {
252
- success: false,
253
- error: error.message,
254
- status: error.status || 500
255
- };
256
- } finally {
257
- this.loading = false;
258
- }
259
- }
260
- /**
261
- * Save model to API (create or update)
262
- * @param {object} data - Data to save to the model
263
- * @param {object} options - Request options
264
- * @returns {Promise} Promise that resolves with REST response
265
- */
266
- async save(data, options = {}) {
267
- const isNew = !this.id;
268
- const method = isNew ? "POST" : "PUT";
269
- const url = isNew ? this.buildUrl() : this.buildUrl(this.id);
270
- this.loading = true;
271
- this.errors = {};
272
- try {
273
- const response = await this.rest[method](url, data, options.params);
274
- if (response.success) {
275
- if (response.data.status) {
276
- this.originalAttributes = { ...this.attributes };
277
- this.set(response.data.data);
278
- this.errors = {};
279
- } else {
280
- this.errors = response.data;
281
- }
282
- } else {
283
- this.errors = response.errors || {};
284
- }
285
- return response;
286
- } catch (error) {
287
- return {
288
- success: false,
289
- error: error.message,
290
- status: error.status || 500
291
- };
292
- } finally {
293
- this.loading = false;
294
- }
295
- }
296
- /**
297
- * Delete model from API
298
- * @param {object} options - Request options
299
- * @returns {Promise} Promise that resolves with REST response
300
- */
301
- async destroy(options = {}) {
302
- if (!this.id) {
303
- this.errors = { destroy: "Cannot destroy model without ID" };
304
- return {
305
- success: false,
306
- error: "Cannot destroy model without ID",
307
- status: 400
308
- };
309
- }
310
- const url = this.buildUrl(this.id);
311
- this.loading = true;
312
- this.errors = {};
313
- try {
314
- const response = await this.rest.DELETE(url, options.params);
315
- if (response.success) {
316
- this.attributes = {};
317
- this.originalAttributes = {};
318
- this.id = null;
319
- this.errors = {};
320
- } else {
321
- this.errors = response.errors || {};
322
- }
323
- return response;
324
- } catch (error) {
325
- this.errors = { destroy: error.message };
326
- return {
327
- success: false,
328
- error: error.message,
329
- status: error.status || 500
330
- };
331
- } finally {
332
- this.loading = false;
333
- }
334
- }
335
- /**
336
- * Check if model has been modified
337
- * @returns {boolean} True if model has unsaved changes
338
- */
339
- isDirty() {
340
- return JSON.stringify(this.attributes) !== JSON.stringify(this.originalAttributes);
341
- }
342
- /**
343
- * Get attributes that have changed since last save
344
- * @returns {object} Object containing only changed attributes
345
- */
346
- getChangedAttributes() {
347
- const changed = {};
348
- for (const [key, value] of Object.entries(this.attributes)) {
349
- if (this.originalAttributes[key] !== value) {
350
- changed[key] = value;
351
- }
352
- }
353
- return changed;
354
- }
355
- /**
356
- * Reset model to original state
357
- */
358
- reset() {
359
- this.attributes = { ...this.originalAttributes };
360
- this._ = this.attributes;
361
- this.errors = {};
362
- }
363
- /**
364
- * Build URL for API requests
365
- * @param {string|number} id - Optional ID to append to URL
366
- * @returns {string} Complete API URL
367
- */
368
- buildUrl(id = null) {
369
- let url = this.endpoint;
370
- if (id) {
371
- url = url.endsWith("/") ? `${url}${id}` : `${url}/${id}`;
372
- }
373
- return url;
374
- }
375
- /**
376
- * Convert model to JSON
377
- * @returns {object} Model attributes as plain object
378
- */
379
- toJSON() {
380
- return {
381
- id: this.id,
382
- ...this.attributes
383
- };
384
- }
385
- /**
386
- * Validate model attributes
387
- * @returns {boolean} True if valid, false if validation errors exist
388
- */
389
- validate() {
390
- this.errors = {};
391
- if (this.constructor.validations) {
392
- for (const [field, rules] of Object.entries(this.constructor.validations)) {
393
- this.validateField(field, rules);
394
- }
395
- }
396
- return Object.keys(this.errors).length === 0;
397
- }
398
- /**
399
- * Validate a single field
400
- * @param {string} field - Field name
401
- * @param {object|array} rules - Validation rules
402
- */
403
- validateField(field, rules) {
404
- const value = this.get(field);
405
- const rulesArray = Array.isArray(rules) ? rules : [rules];
406
- for (const rule of rulesArray) {
407
- if (typeof rule === "function") {
408
- const result = rule(value, this);
409
- if (result !== true) {
410
- this.errors[field] = result || `${field} is invalid`;
411
- break;
412
- }
413
- } else if (typeof rule === "object") {
414
- if (rule.required && (value === void 0 || value === null || value === "")) {
415
- this.errors[field] = rule.message || `${field} is required`;
416
- break;
417
- }
418
- if (rule.minLength && value && value.length < rule.minLength) {
419
- this.errors[field] = rule.message || `${field} must be at least ${rule.minLength} characters`;
420
- break;
421
- }
422
- if (rule.maxLength && value && value.length > rule.maxLength) {
423
- this.errors[field] = rule.message || `${field} must be no more than ${rule.maxLength} characters`;
424
- break;
425
- }
426
- if (rule.pattern && value && !rule.pattern.test(value)) {
427
- this.errors[field] = rule.message || `${field} format is invalid`;
428
- break;
429
- }
430
- }
431
- }
432
- }
433
- // EventEmitter API: on, off, once, emit (from mixin).
434
- /**
435
- * Static method to create and fetch a model by ID
436
- * @param {string|number} id - Model ID
437
- * @param {object} options - Options
438
- * @returns {Promise<RestModel>} Promise that resolves with fetched model
439
- */
440
- static async find(id, options = {}) {
441
- const model = new this({}, options);
442
- await model.fetch({ id, ...options });
443
- return model;
444
- }
445
- /**
446
- * Static method to create a new model with data
447
- * @param {object} data - Model data
448
- * @param {object} options - Options
449
- * @returns {RestModel} New model instance
450
- */
451
- static create(data = {}, options = {}) {
452
- return new this(data, options);
453
- }
454
- /**
455
- * Cancel any active fetch request
456
- * @returns {boolean} True if a request was cancelled, false if no active request
457
- */
458
- cancel() {
459
- if (this.currentRequest && this.abortController) {
460
- console.info("Model: Manually cancelling active request");
461
- this.abortController.abort();
462
- return true;
463
- }
464
- if (this.debouncedFetchTimeout) {
465
- clearTimeout(this.debouncedFetchTimeout);
466
- this.debouncedFetchTimeout = null;
467
- return true;
468
- }
469
- return false;
470
- }
471
- /**
472
- * Check if model has an active fetch request
473
- * @returns {boolean} True if fetch is in progress
474
- */
475
- isFetching() {
476
- return !!this.currentRequest;
477
- }
478
- async showError(message) {
479
- await Dialog.alert(message, "Error", {
480
- size: "md",
481
- class: "text-danger"
482
- });
483
- }
484
- }
485
- Object.assign(Model.prototype, EventEmitter);
486
- class Collection {
487
- constructor(options = {}, data = null) {
488
- if (Array.isArray(options)) {
489
- data = options;
490
- options = data || {};
491
- } else {
492
- data = data || options.data || [];
493
- }
494
- this.ModelClass = options.ModelClass || Model;
495
- this.models = [];
496
- this.loading = false;
497
- this.errors = {};
498
- this.meta = {};
499
- this.rest = rest;
500
- if (data) {
501
- this.add(data);
502
- }
503
- this.params = {
504
- start: 0,
505
- size: options.size || 10,
506
- ...options.params
507
- };
508
- this.endpoint = options.endpoint || this.ModelClass.endpoint || "";
509
- if (!this.endpoint) {
510
- let tmp = new this.ModelClass();
511
- this.endpoint = tmp.endpoint;
512
- }
513
- this.restEnabled = this.endpoint ? true : false;
514
- if (options.restEnabled !== void 0) {
515
- this.restEnabled = options.restEnabled;
516
- }
517
- this.options = {
518
- parse: true,
519
- reset: true,
520
- preloaded: false,
521
- ...options
522
- };
523
- }
524
- getModelName() {
525
- return this.ModelClass.name;
526
- }
527
- /**
528
- * Fetch collection data from API
529
- * @param {object} additionalParams - Additional parameters to merge for this fetch only
530
- * @returns {Promise} Promise that resolves with REST response
531
- */
532
- async fetch(additionalParams = {}) {
533
- const requestKey = JSON.stringify({ ...this.params, ...additionalParams });
534
- if (this.currentRequest && this.currentRequestKey !== requestKey) {
535
- console.info("Collection: Cancelling previous request for new parameters");
536
- this.abortController?.abort();
537
- this.currentRequest = null;
538
- }
539
- if (this.currentRequest && this.currentRequestKey === requestKey) {
540
- console.info("Collection: Duplicate request in progress, returning existing promise");
541
- return this.currentRequest;
542
- }
543
- const now = Date.now();
544
- const minInterval = 100;
545
- if (this.options.rateLimiting && this.lastFetchTime && now - this.lastFetchTime < minInterval) {
546
- console.info("Collection: Rate limited, skipping fetch");
547
- return { success: true, message: "Rate limited, skipping fetch", data: { data: this.toJSON() } };
548
- }
549
- if (!this.restEnabled) {
550
- console.info("Collection: REST disabled, skipping fetch");
551
- return { success: true, message: "REST disabled, skipping fetch", data: { data: this.toJSON() } };
552
- }
553
- if (this.options.preloaded && this.models.length > 0) {
554
- console.info("Collection: Using preloaded data, skipping fetch");
555
- return { success: true, message: "Using preloaded data, skipping fetch", data: { data: this.toJSON() } };
556
- }
557
- const url = this.buildUrl();
558
- this.loading = true;
559
- this.errors = {};
560
- this.lastFetchTime = now;
561
- this.currentRequestKey = requestKey;
562
- this.abortController = new AbortController();
563
- this.currentRequest = this._performFetch(url, additionalParams, this.abortController);
564
- try {
565
- const result = await this.currentRequest;
566
- return result;
567
- } catch (error) {
568
- if (error.name === "AbortError") {
569
- console.info("Collection: Request was cancelled");
570
- return { success: false, error: "Request cancelled", status: 0 };
571
- }
572
- return {
573
- success: false,
574
- error: error.message,
575
- status: error.status || 500
576
- };
577
- } finally {
578
- this.currentRequest = null;
579
- this.currentRequestKey = null;
580
- this.abortController = null;
581
- }
582
- }
583
- /**
584
- * Internal method to perform the actual fetch
585
- * @param {string} url - API endpoint URL
586
- * @param {object} additionalParams - Additional parameters
587
- * @param {AbortController} abortController - Controller for request cancellation
588
- * @returns {Promise} Promise that resolves with REST response
589
- */
590
- async _performFetch(url, additionalParams, abortController) {
591
- const fetchParams = { ...this.params, ...additionalParams };
592
- console.log("Fetching collection data from", url, fetchParams);
593
- try {
594
- this.emit("fetch:start");
595
- const response = await this.rest.GET(url, fetchParams, {
596
- signal: abortController.signal
597
- });
598
- if (response.success && response.data.status) {
599
- const data = this.options.parse ? this.parse(response) : response.data;
600
- if (this.options.reset || additionalParams.reset !== false) {
601
- this.reset();
602
- }
603
- this.add(data, { silent: additionalParams.silent });
604
- this.errors = {};
605
- this.emit("fetch:success");
606
- } else {
607
- if (response.data && response.data.error) {
608
- this.errors = response.data;
609
- this.emit("fetch:error", { message: response.data.error, error: response.data });
610
- } else {
611
- this.errors = response.errors || {};
612
- this.emit("fetch:error", { error: response.errors });
613
- }
614
- }
615
- return response;
616
- } catch (error) {
617
- if (error.name === "AbortError") {
618
- console.info("Collection: Fetch was cancelled");
619
- return { success: false, error: "Request cancelled", status: 0 };
620
- }
621
- this.errors = { fetch: error.message };
622
- this.emit("fetch:error", { message: error.message, error });
623
- return {
624
- success: false,
625
- error: error.message,
626
- status: error.status || 500
627
- };
628
- } finally {
629
- this.loading = false;
630
- this.emit("fetch:end");
631
- }
632
- }
633
- /**
634
- * Update collection parameters and optionally fetch new data
635
- * @param {object} newParams - Parameters to update
636
- * @param {boolean} autoFetch - Whether to automatically fetch after updating params
637
- * @param {number} debounceMs - Optional debounce delay in milliseconds
638
- * @returns {Promise} Promise that resolves with REST response if autoFetch=true, or collection if autoFetch=false
639
- */
640
- async updateParams(newParams, autoFetch = false, debounceMs = 0) {
641
- return await this.setParams({ ...this.params, ...newParams }, autoFetch, debounceMs);
642
- }
643
- async setParams(newParams, autoFetch = false, debounceMs = 0) {
644
- this.params = newParams;
645
- if (autoFetch && this.restEnabled) {
646
- if (debounceMs > 0) {
647
- if (this.debouncedFetchTimeout) {
648
- clearTimeout(this.debouncedFetchTimeout);
649
- }
650
- this.cancel();
651
- return new Promise((resolve, reject) => {
652
- this.debouncedFetchTimeout = setTimeout(async () => {
653
- try {
654
- const result = await this.fetch();
655
- resolve(result);
656
- } catch (error) {
657
- reject(error);
658
- }
659
- }, debounceMs);
660
- });
661
- } else {
662
- return this.fetch();
663
- }
664
- }
665
- return Promise.resolve(this);
666
- }
667
- /**
668
- * Fetch a single model by ID
669
- * @param {string|number} id - Model ID to fetch
670
- * @param {object} options - Additional fetch options
671
- * @returns {Promise<Model|null>} Promise that resolves with model instance or null if not found
672
- */
673
- async fetchOne(id, options = {}) {
674
- if (!id) {
675
- console.warn("Collection: fetchOne requires an ID");
676
- return null;
677
- }
678
- if (!this.restEnabled) {
679
- console.info("Collection: REST disabled, cannot fetch single item");
680
- return null;
681
- }
682
- try {
683
- const model = new this.ModelClass({ id }, {
684
- endpoint: this.endpoint,
685
- collection: this
686
- });
687
- const response = await model.fetch(options);
688
- if (response.success) {
689
- if (options.addToCollection === true) {
690
- const existingModel = this.get(model.id);
691
- if (!existingModel) {
692
- this.add(model, { silent: options.silent });
693
- } else if (options.merge !== false) {
694
- existingModel.set(model.attributes);
695
- }
696
- }
697
- return model;
698
- } else {
699
- console.warn("Collection: fetchOne failed -", response.error || "Unknown error");
700
- return null;
701
- }
702
- } catch (error) {
703
- console.error("Collection: fetchOne error -", error.message);
704
- return null;
705
- }
706
- }
707
- /**
708
- * Download collection data in a specified format
709
- * @param {string} format - The format for the download (e.g., 'csv', 'json')
710
- * @param {object} options - Download options
711
- * @returns {Promise} Promise that resolves with the download result
712
- */
713
- async download(format = "json", options = {}) {
714
- if (!this.restEnabled) {
715
- console.warn("Collection: REST is not enabled, cannot download from remote.");
716
- return { success: false, message: "Remote downloads are not enabled for this collection." };
717
- }
718
- const url = this.buildUrl();
719
- const downloadParams = { ...this.params };
720
- delete downloadParams.start;
721
- delete downloadParams.size;
722
- downloadParams.download_format = format;
723
- const baseName = `export-${this.getModelName().toLowerCase()}`;
724
- const rangeSuffix = this._buildDateRangeSuffix(downloadParams);
725
- const filename = `${baseName}${rangeSuffix}.${format}`;
726
- const contentTypes = {
727
- json: "application/json",
728
- csv: "text/csv"
729
- };
730
- const acceptHeader = contentTypes[format] || "*/*";
731
- downloadParams.filename = filename;
732
- return this.rest.download(url, downloadParams, {
733
- ...options,
734
- filename,
735
- headers: { "Accept": acceptHeader }
736
- });
737
- }
738
- _buildDateRangeSuffix(params = {}) {
739
- const hasStart = params.dr_start;
740
- const hasEnd = params.dr_end;
741
- if (!hasStart && !hasEnd) {
742
- return "";
743
- }
744
- const sanitize = (value) => {
745
- if (!value) return "";
746
- return String(value).replace(/[^\dA-Za-z_-]/g, "-");
747
- };
748
- const parts = [];
749
- const field = params.dr_field || "daterange";
750
- parts.push(sanitize(field));
751
- if (hasStart) {
752
- parts.push(`from-${sanitize(params.dr_start)}`);
753
- }
754
- if (hasEnd) {
755
- parts.push(`to-${sanitize(params.dr_end)}`);
756
- }
757
- return `-${parts.filter(Boolean).join("-")}`;
758
- }
759
- /**
760
- * Parse response data - override in subclasses for custom parsing
761
- * @param {object} response - API response
762
- * @returns {array} Array of model data objects
763
- */
764
- parse(response) {
765
- if (response.data && Array.isArray(response.data.data)) {
766
- this.meta = {
767
- size: response.data.size || 10,
768
- start: response.data.start || 0,
769
- count: response.data.count || 0,
770
- status: response.data.status,
771
- graph: response.data.graph,
772
- ...response.meta
773
- };
774
- return response.data.data;
775
- }
776
- if (Array.isArray(response.data)) {
777
- return response.data;
778
- }
779
- return Array.isArray(response) ? response : [response];
780
- }
781
- /**
782
- * Add model(s) to the collection
783
- * @param {object|array} data - Model data or array of model data
784
- * @param {object} options - Options for adding models
785
- */
786
- add(data, options = {}) {
787
- const modelsData = Array.isArray(data) ? data : [data];
788
- const addedModels = [];
789
- for (const modelData of modelsData) {
790
- let model;
791
- if (modelData instanceof this.ModelClass) {
792
- model = modelData;
793
- } else {
794
- model = new this.ModelClass(modelData, {
795
- endpoint: this.endpoint,
796
- collection: this
797
- });
798
- }
799
- const existingIndex = this.models.findIndex((m) => m.id === model.id);
800
- if (existingIndex !== -1) {
801
- if (options.merge !== false) {
802
- this.models[existingIndex].set(model.attributes);
803
- }
804
- } else {
805
- this.models.push(model);
806
- addedModels.push(model);
807
- }
808
- }
809
- if (!options.silent && addedModels.length > 0) {
810
- this.emit("add", { models: addedModels, collection: this });
811
- this.emit("update", { collection: this });
812
- }
813
- return addedModels;
814
- }
815
- /**
816
- * Remove model(s) from the collection
817
- * @param {Model|array|string|number} models - Model(s) to remove or ID(s)
818
- * @param {object} options - Options
819
- */
820
- remove(models, options = {}) {
821
- const modelsToRemove = Array.isArray(models) ? models : [models];
822
- const removedModels = [];
823
- for (const model of modelsToRemove) {
824
- let index = -1;
825
- if (typeof model === "string" || typeof model === "number") {
826
- index = this.models.findIndex((m) => m.id == model);
827
- } else {
828
- index = this.models.indexOf(model);
829
- }
830
- if (index !== -1) {
831
- const removedModel = this.models.splice(index, 1)[0];
832
- removedModels.push(removedModel);
833
- }
834
- }
835
- if (!options.silent && removedModels.length > 0) {
836
- this.emit("remove", { models: removedModels, collection: this });
837
- this.emit("update", { collection: this });
838
- }
839
- return removedModels;
840
- }
841
- /**
842
- * Reset the collection (remove all models)
843
- * @param {array} models - Optional new models to set
844
- * @param {object} options - Options
845
- */
846
- reset(models = null, options = {}) {
847
- const previousModels = [...this.models];
848
- this.models = [];
849
- if (models) {
850
- this.add(models, { silent: true, ...options });
851
- }
852
- if (!options.silent) {
853
- this.emit("reset", {
854
- collection: this,
855
- previousModels
856
- });
857
- }
858
- return this;
859
- }
860
- /**
861
- * Get model by ID
862
- * @param {string|number} id - Model ID
863
- * @returns {Model|undefined} Model instance or undefined
864
- */
865
- get(id) {
866
- return this.models.find((model) => model.id == id);
867
- }
868
- /**
869
- * Get model by index
870
- * @param {number} index - Model index
871
- * @returns {Model|undefined} Model instance or undefined
872
- */
873
- at(index) {
874
- return this.models[index];
875
- }
876
- /**
877
- * Get collection length
878
- * @returns {number} Number of models in collection
879
- */
880
- length() {
881
- return this.models.length;
882
- }
883
- /**
884
- * Check if collection is empty
885
- * @returns {boolean} True if collection has no models
886
- */
887
- isEmpty() {
888
- return this.models.length === 0;
889
- }
890
- /**
891
- * Find models matching criteria
892
- * @param {function|object} criteria - Filter function or object with key-value pairs
893
- * @returns {array} Array of matching models
894
- */
895
- where(criteria) {
896
- if (typeof criteria === "function") {
897
- return this.models.filter(criteria);
898
- }
899
- if (typeof criteria === "object") {
900
- return this.models.filter((model) => {
901
- return Object.entries(criteria).every(([key, value]) => {
902
- return model.get(key) === value;
903
- });
904
- });
905
- }
906
- return [];
907
- }
908
- /**
909
- * Find first model matching criteria
910
- * @param {function|object} criteria - Filter function or object with key-value pairs
911
- * @returns {Model|undefined} First matching model or undefined
912
- */
913
- findWhere(criteria) {
914
- const results = this.where(criteria);
915
- return results.length > 0 ? results[0] : void 0;
916
- }
917
- /**
918
- * Iterate over each model in the collection
919
- * @param {function} callback - Function to execute for each model (model, index, collection)
920
- * @param {object} thisArg - Optional value to use as this when executing callback
921
- * @returns {Collection} Returns the collection for chaining
922
- */
923
- forEach(callback, thisArg) {
924
- if (typeof callback !== "function") {
925
- throw new TypeError("Callback must be a function");
926
- }
927
- this.models.forEach((model, index) => {
928
- callback.call(thisArg, model, index, this);
929
- });
930
- return this;
931
- }
932
- /**
933
- * Sort collection by comparator function
934
- * @param {function|string} comparator - Comparison function or attribute name
935
- * @param {object} options - Sort options
936
- */
937
- sort(comparator, options = {}) {
938
- if (typeof comparator === "string") {
939
- const attr = comparator;
940
- comparator = (a, b) => {
941
- const aVal = a.get(attr);
942
- const bVal = b.get(attr);
943
- if (aVal < bVal) return -1;
944
- if (aVal > bVal) return 1;
945
- return 0;
946
- };
947
- }
948
- this.models.sort(comparator);
949
- if (!options.silent) {
950
- this.trigger("sort", { collection: this });
951
- }
952
- return this;
953
- }
954
- /**
955
- * Convert collection to JSON array
956
- * @returns {array} Array of model JSON representations
957
- */
958
- toJSON() {
959
- return this.models.map((model) => model.toJSON());
960
- }
961
- /**
962
- * Cancel any active fetch request
963
- * @returns {boolean} True if a request was cancelled, false if no active request
964
- */
965
- cancel() {
966
- if (this.currentRequest && this.abortController) {
967
- console.info("Collection: Manually cancelling active request");
968
- this.abortController.abort();
969
- return true;
970
- }
971
- return false;
972
- }
973
- /**
974
- * Check if collection has an active fetch request
975
- * @returns {boolean} True if fetch is in progress
976
- */
977
- isFetching() {
978
- return !!this.currentRequest;
979
- }
980
- /**
981
- * Build URL for collection endpoint
982
- * @returns {string} Collection API URL
983
- */
984
- buildUrl() {
985
- return this.endpoint;
986
- }
987
- // EventEmitter API: on, off, once, emit (from mixin).
988
- /**
989
- * Iterator support for for...of loops
990
- */
991
- *[Symbol.iterator]() {
992
- for (const model of this.models) {
993
- yield model;
994
- }
995
- }
996
- /**
997
- * Static method to create collection from array data
998
- * @param {function} ModelClass - Model class constructor
999
- * @param {array} data - Array of model data
1000
- * @param {object} options - Collection options
1001
- * @returns {Collection} New collection instance
1002
- */
1003
- static fromArray(ModelClass, data = [], options = {}) {
1004
- const collection = new this(ModelClass, options);
1005
- collection.add(data, { silent: true });
1006
- return collection;
1007
- }
1008
- }
1009
- Object.assign(Collection.prototype, EventEmitter);
1010
- export {
1011
- Collection as C,
1012
- Model as M
1013
- };
1014
- //# sourceMappingURL=Collection-DaiL0uGl.js.map