web-mojo 2.2.65 → 2.2.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/ChatView-Cfe0ZGvr.js +2 -0
- package/dist/chunks/ChatView-Cfe0ZGvr.js.map +1 -0
- package/dist/chunks/ChatView-DuQVFrCY.js +2 -0
- package/dist/chunks/ChatView-DuQVFrCY.js.map +1 -0
- package/dist/chunks/{Collection-1sPoIFvQ.js → Collection-BWKmydl5.js} +2 -2
- package/dist/chunks/{Collection-1sPoIFvQ.js.map → Collection-BWKmydl5.js.map} +1 -1
- package/dist/chunks/{Collection-DSBRXpwK.js → Collection-CmjTsmrP.js} +2 -2
- package/dist/chunks/{Collection-DSBRXpwK.js.map → Collection-CmjTsmrP.js.map} +1 -1
- package/dist/chunks/ContextMenu-8vTiZZQV.js +2 -0
- package/dist/chunks/ContextMenu-8vTiZZQV.js.map +1 -0
- package/dist/chunks/ContextMenu-DBw0WMTO.js +2 -0
- package/dist/chunks/ContextMenu-DBw0WMTO.js.map +1 -0
- package/dist/chunks/{DataView--nUWtq6r.js → DataView-BEovBggn.js} +2 -2
- package/dist/chunks/{DataView--nUWtq6r.js.map → DataView-BEovBggn.js.map} +1 -1
- package/dist/chunks/{DataView-CK3Z0TJH.js → DataView-DyJKgOn3.js} +2 -2
- package/dist/chunks/{DataView-CK3Z0TJH.js.map → DataView-DyJKgOn3.js.map} +1 -1
- package/dist/chunks/{Dialog-VoLlToMl.js → Dialog-DW7PHzUc.js} +2 -2
- package/dist/chunks/{Dialog-wBhTkeWg.js.map → Dialog-DW7PHzUc.js.map} +1 -1
- package/dist/chunks/{Dialog-wBhTkeWg.js → Dialog-jfBsXy5X.js} +2 -2
- package/dist/chunks/{Dialog-VoLlToMl.js.map → Dialog-jfBsXy5X.js.map} +1 -1
- package/dist/chunks/Files-C-ChBvr5.js +2 -0
- package/dist/chunks/Files-C-ChBvr5.js.map +1 -0
- package/dist/chunks/Files-DNbHDy43.js +2 -0
- package/dist/chunks/Files-DNbHDy43.js.map +1 -0
- package/dist/chunks/FormView-EoB_ZdIB.js +3 -0
- package/dist/chunks/FormView-EoB_ZdIB.js.map +1 -0
- package/dist/chunks/FormView-Q_lFA0nr.js +3 -0
- package/dist/chunks/FormView-Q_lFA0nr.js.map +1 -0
- package/dist/chunks/{ListView-6JQ6tRXs.js → ListView-BLFFK_Ir.js} +2 -2
- package/dist/chunks/{ListView-6JQ6tRXs.js.map → ListView-BLFFK_Ir.js.map} +1 -1
- package/dist/chunks/{ListView-DVStKiMi.js → ListView-CMZpwyyC.js} +2 -2
- package/dist/chunks/{ListView-DVStKiMi.js.map → ListView-CMZpwyyC.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-CnAEbUw_.js → MetricsCountryMapView-B0kWK-Js.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-CnAEbUw_.js.map → MetricsCountryMapView-B0kWK-Js.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-J067qrrt.js → MetricsCountryMapView-DuBKO7gz.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-J067qrrt.js.map → MetricsCountryMapView-DuBKO7gz.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-DtWq2_YJ.js → MetricsMiniChartWidget-BkMjI-gz.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-DtWq2_YJ.js.map → MetricsMiniChartWidget-BkMjI-gz.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-CtCN1rtt.js → MetricsMiniChartWidget-ChC5GGm6.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-CtCN1rtt.js.map → MetricsMiniChartWidget-ChC5GGm6.js.map} +1 -1
- package/dist/chunks/{PDFViewer-BNCyDElg.js → PDFViewer-iOqYpg-6.js} +2 -2
- package/dist/chunks/{PDFViewer-BNCyDElg.js.map → PDFViewer-iOqYpg-6.js.map} +1 -1
- package/dist/chunks/{PDFViewer-Di4OHAkv.js → PDFViewer-sFoyopz3.js} +2 -2
- package/dist/chunks/{PDFViewer-Di4OHAkv.js.map → PDFViewer-sFoyopz3.js.map} +1 -1
- package/dist/chunks/{Rest-Ds9e8tN8.js → Rest-B1eUyLX5.js} +2 -2
- package/dist/chunks/{Rest-Ds9e8tN8.js.map → Rest-B1eUyLX5.js.map} +1 -1
- package/dist/chunks/{Rest-DHbszkuP.js → Rest-BJ3Mvx1L.js} +2 -2
- package/dist/chunks/{Rest-DHbszkuP.js.map → Rest-BJ3Mvx1L.js.map} +1 -1
- package/dist/chunks/TokenManager-BYMKH_aW.js +2 -0
- package/dist/chunks/{TokenManager-D-9tqubS.js.map → TokenManager-BYMKH_aW.js.map} +1 -1
- package/dist/chunks/TokenManager-DhDUKmaw.js +2 -0
- package/dist/chunks/{TokenManager-8JM2qj_1.js.map → TokenManager-DhDUKmaw.js.map} +1 -1
- package/dist/chunks/User-BnlvMG5J.js +3 -0
- package/dist/chunks/User-BnlvMG5J.js.map +1 -0
- package/dist/chunks/User-DSqcOwPL.js +3 -0
- package/dist/chunks/User-DSqcOwPL.js.map +1 -0
- package/dist/chunks/{WebApp-JJAK0eNM.js → WebApp-B0m6JCjO.js} +2 -2
- package/dist/chunks/{WebApp-JJAK0eNM.js.map → WebApp-B0m6JCjO.js.map} +1 -1
- package/dist/chunks/{WebApp-BdJA4Uup.js → WebApp-Bsic6FPo.js} +2 -2
- package/dist/chunks/{WebApp-BdJA4Uup.js.map → WebApp-Bsic6FPo.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-BoF7TV7i.js → WebSocketClient-Bh0Mmtje.js} +2 -2
- package/dist/chunks/{WebSocketClient-BoF7TV7i.js.map → WebSocketClient-Bh0Mmtje.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-BxL2M7p0.js → WebSocketClient-CLgYPxWX.js} +2 -2
- package/dist/chunks/{WebSocketClient-BxL2M7p0.js.map → WebSocketClient-CLgYPxWX.js.map} +1 -1
- package/dist/chunks/{version-kTtMlfsq.js → version-BY7AsEkb.js} +2 -2
- package/dist/chunks/{version-kTtMlfsq.js.map → version-BY7AsEkb.js.map} +1 -1
- package/dist/chunks/{version-Cn26llLs.js → version-BdfRyQDm.js} +2 -2
- package/dist/chunks/{version-Cn26llLs.js.map → version-BdfRyQDm.js.map} +1 -1
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.cjs.js.map +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/docit.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.cjs.js.map +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/lightbox.es.js.map +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/web-mojo.lite.iife.js +1230 -2
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +95 -74
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +8 -7
- package/dist/chunks/ChatView-D1-ev2qA.js +0 -2
- package/dist/chunks/ChatView-D1-ev2qA.js.map +0 -1
- package/dist/chunks/ChatView-DesGp57e.js +0 -2
- package/dist/chunks/ChatView-DesGp57e.js.map +0 -1
- package/dist/chunks/ContextMenu-CsQGpSrv.js +0 -3
- package/dist/chunks/ContextMenu-CsQGpSrv.js.map +0 -1
- package/dist/chunks/ContextMenu-dqFxitXY.js +0 -3
- package/dist/chunks/ContextMenu-dqFxitXY.js.map +0 -1
- package/dist/chunks/FormView-BQnvMheR.js +0 -3
- package/dist/chunks/FormView-BQnvMheR.js.map +0 -1
- package/dist/chunks/FormView-C7FnbmEI.js +0 -3
- package/dist/chunks/FormView-C7FnbmEI.js.map +0 -1
- package/dist/chunks/TokenManager-8JM2qj_1.js +0 -2
- package/dist/chunks/TokenManager-D-9tqubS.js +0 -2
|
@@ -983,6 +983,10 @@ var MOJO = (function(exports) {
|
|
|
983
983
|
try {
|
|
984
984
|
const response = await fetch(request.url, fetchOptions);
|
|
985
985
|
const responseData = await this.processResponseInterceptors(response, request);
|
|
986
|
+
if (options.dataOnly && responseData.data && typeof responseData.data === "object" && "data" in responseData.data) {
|
|
987
|
+
responseData.message = responseData.message || responseData.data.message;
|
|
988
|
+
responseData.data = responseData.data.data;
|
|
989
|
+
}
|
|
986
990
|
return responseData;
|
|
987
991
|
} catch (error) {
|
|
988
992
|
if (error.name === "AbortError") {
|
|
@@ -7480,7 +7484,7 @@ var MOJO = (function(exports) {
|
|
|
7480
7484
|
</div>
|
|
7481
7485
|
`,
|
|
7482
7486
|
image: `
|
|
7483
|
-
<div class="mojo-form-control">
|
|
7487
|
+
<div class="mojo-form-control" style="display: flex; flex-direction: column; align-items: center;">
|
|
7484
7488
|
{{#label}}
|
|
7485
7489
|
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
7486
7490
|
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
@@ -13864,7 +13868,7 @@ var MOJO = (function(exports) {
|
|
|
13864
13868
|
console.log("FormView: initializeChangeHandlers - found", inputs.length, "inputs");
|
|
13865
13869
|
inputs.forEach((input) => {
|
|
13866
13870
|
console.log("FormView: Processing input:", input.type, input.name, input.getAttribute("data-change-action"));
|
|
13867
|
-
if (input.hasAttribute("data-component") || input.classList.contains("form-check-input")) {
|
|
13871
|
+
if (input.hasAttribute("data-component") || input.hasAttribute("data-change-action") || input.classList.contains("form-check-input")) {
|
|
13868
13872
|
return;
|
|
13869
13873
|
}
|
|
13870
13874
|
input.addEventListener("change", (event) => {
|
|
@@ -17312,6 +17316,56 @@ var MOJO = (function(exports) {
|
|
|
17312
17316
|
});
|
|
17313
17317
|
});
|
|
17314
17318
|
}
|
|
17319
|
+
/**
|
|
17320
|
+
* Show form in a dialog for simple data collection (no model saving)
|
|
17321
|
+
* @param {object} options - Configuration options
|
|
17322
|
+
* @returns {Promise} Promise that resolves with form data or null if cancelled
|
|
17323
|
+
*/
|
|
17324
|
+
static async updateModelImage(options = {}, fieldOptions = {}) {
|
|
17325
|
+
const upload = options.upload || false;
|
|
17326
|
+
const fieldName = fieldOptions.name || options.field || "image";
|
|
17327
|
+
const formOptions = {
|
|
17328
|
+
title: "Upload Your Avatar",
|
|
17329
|
+
model: null,
|
|
17330
|
+
autoSave: !upload,
|
|
17331
|
+
size: "sm",
|
|
17332
|
+
fields: [
|
|
17333
|
+
{
|
|
17334
|
+
type: "image",
|
|
17335
|
+
name: fieldName,
|
|
17336
|
+
size: "lg",
|
|
17337
|
+
imageSize: { width: 200, height: 200 },
|
|
17338
|
+
placeholder: "Upload your image",
|
|
17339
|
+
...fieldOptions
|
|
17340
|
+
}
|
|
17341
|
+
],
|
|
17342
|
+
...options
|
|
17343
|
+
};
|
|
17344
|
+
const result = await Dialog2.showForm(formOptions);
|
|
17345
|
+
if (!upload || !result || !options.model) return result;
|
|
17346
|
+
const base64Data = result[fieldName];
|
|
17347
|
+
if (!base64Data || !base64Data.startsWith("data:")) return result;
|
|
17348
|
+
const arr = base64Data.split(",");
|
|
17349
|
+
const mime = arr[0].match(/:(.*?);/)[1];
|
|
17350
|
+
const bstr = atob(arr[1]);
|
|
17351
|
+
let n = bstr.length;
|
|
17352
|
+
const u8arr = new Uint8Array(n);
|
|
17353
|
+
while (n--) {
|
|
17354
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
17355
|
+
}
|
|
17356
|
+
const ext = mime.split("/")[1] || "png";
|
|
17357
|
+
const file = new window.File([u8arr], `${fieldName}.${ext}`, { type: mime });
|
|
17358
|
+
const { File: FileModel } = await Promise.resolve().then(() => Files);
|
|
17359
|
+
const fileModel = new FileModel();
|
|
17360
|
+
await fileModel.upload({
|
|
17361
|
+
file,
|
|
17362
|
+
name: `${fieldName}.${ext}`,
|
|
17363
|
+
description: options.uploadDescription || `${fieldName} upload`,
|
|
17364
|
+
showToast: true
|
|
17365
|
+
});
|
|
17366
|
+
const resp = await options.model.save({ [fieldName]: fileModel.id });
|
|
17367
|
+
return resp;
|
|
17368
|
+
}
|
|
17315
17369
|
static async showModelView(model, options) {
|
|
17316
17370
|
const modelClass = model.constructor;
|
|
17317
17371
|
const modelView = modelClass.VIEW_CLASS;
|
|
@@ -20967,6 +21021,1180 @@ var MOJO = (function(exports) {
|
|
|
20967
21021
|
if (MOJO2) {
|
|
20968
21022
|
attachLite(MOJO2);
|
|
20969
21023
|
}
|
|
21024
|
+
class ToastService {
|
|
21025
|
+
constructor(options = {}) {
|
|
21026
|
+
this.options = {
|
|
21027
|
+
containerId: "toast-container",
|
|
21028
|
+
position: "top-end",
|
|
21029
|
+
// top-start, top-center, top-end, middle-start, etc.
|
|
21030
|
+
autohide: true,
|
|
21031
|
+
defaultDelay: 5e3,
|
|
21032
|
+
// 5 seconds
|
|
21033
|
+
maxToasts: 5,
|
|
21034
|
+
// Maximum number of toasts to show at once
|
|
21035
|
+
...options
|
|
21036
|
+
};
|
|
21037
|
+
this.toasts = /* @__PURE__ */ new Map();
|
|
21038
|
+
this.toastCounter = 0;
|
|
21039
|
+
this.init();
|
|
21040
|
+
}
|
|
21041
|
+
/**
|
|
21042
|
+
* Initialize the toast service
|
|
21043
|
+
*/
|
|
21044
|
+
init() {
|
|
21045
|
+
this.createContainer();
|
|
21046
|
+
}
|
|
21047
|
+
/**
|
|
21048
|
+
* Create the toast container if it doesn't exist
|
|
21049
|
+
*/
|
|
21050
|
+
createContainer() {
|
|
21051
|
+
let container = document.getElementById(this.options.containerId);
|
|
21052
|
+
if (!container) {
|
|
21053
|
+
container = document.createElement("div");
|
|
21054
|
+
container.id = this.options.containerId;
|
|
21055
|
+
container.className = `toast-container position-fixed ${this.getPositionClasses()}`;
|
|
21056
|
+
container.style.zIndex = "1070";
|
|
21057
|
+
container.setAttribute("aria-live", "polite");
|
|
21058
|
+
container.setAttribute("aria-atomic", "true");
|
|
21059
|
+
document.body.appendChild(container);
|
|
21060
|
+
}
|
|
21061
|
+
this.container = container;
|
|
21062
|
+
}
|
|
21063
|
+
/**
|
|
21064
|
+
* Get CSS classes for toast positioning
|
|
21065
|
+
*/
|
|
21066
|
+
getPositionClasses() {
|
|
21067
|
+
const positionMap = {
|
|
21068
|
+
"top-start": "top-0 start-0 p-3",
|
|
21069
|
+
"top-center": "top-0 start-50 translate-middle-x p-3",
|
|
21070
|
+
"top-end": "top-0 end-0 p-3",
|
|
21071
|
+
"middle-start": "top-50 start-0 translate-middle-y p-3",
|
|
21072
|
+
"middle-center": "top-50 start-50 translate-middle p-3",
|
|
21073
|
+
"middle-end": "top-50 end-0 translate-middle-y p-3",
|
|
21074
|
+
"bottom-start": "bottom-0 start-0 p-3",
|
|
21075
|
+
"bottom-center": "bottom-0 start-50 translate-middle-x p-3",
|
|
21076
|
+
"bottom-end": "bottom-0 end-0 p-3"
|
|
21077
|
+
};
|
|
21078
|
+
return positionMap[this.options.position] || positionMap["top-end"];
|
|
21079
|
+
}
|
|
21080
|
+
/**
|
|
21081
|
+
* Show a success toast
|
|
21082
|
+
* @param {string} message - The message to display
|
|
21083
|
+
* @param {object} options - Additional options
|
|
21084
|
+
*/
|
|
21085
|
+
success(message, options = {}) {
|
|
21086
|
+
return this.show(message, "success", {
|
|
21087
|
+
icon: "bi-check-circle-fill",
|
|
21088
|
+
...options
|
|
21089
|
+
});
|
|
21090
|
+
}
|
|
21091
|
+
/**
|
|
21092
|
+
* Show an error toast
|
|
21093
|
+
* @param {string} message - The message to display
|
|
21094
|
+
* @param {object} options - Additional options
|
|
21095
|
+
*/
|
|
21096
|
+
error(message, options = {}) {
|
|
21097
|
+
return this.show(message, "error", {
|
|
21098
|
+
icon: "bi-exclamation-triangle-fill",
|
|
21099
|
+
autohide: true,
|
|
21100
|
+
// Keep error toasts visible until manually dismissed
|
|
21101
|
+
...options
|
|
21102
|
+
});
|
|
21103
|
+
}
|
|
21104
|
+
/**
|
|
21105
|
+
* Show an info toast
|
|
21106
|
+
* @param {string} message - The message to display
|
|
21107
|
+
* @param {object} options - Additional options
|
|
21108
|
+
*/
|
|
21109
|
+
info(message, options = {}) {
|
|
21110
|
+
return this.show(message, "info", {
|
|
21111
|
+
icon: "bi-info-circle-fill",
|
|
21112
|
+
...options
|
|
21113
|
+
});
|
|
21114
|
+
}
|
|
21115
|
+
/**
|
|
21116
|
+
* Show a warning toast
|
|
21117
|
+
* @param {string} message - The message to display
|
|
21118
|
+
* @param {object} options - Additional options
|
|
21119
|
+
*/
|
|
21120
|
+
warning(message, options = {}) {
|
|
21121
|
+
return this.show(message, "warning", {
|
|
21122
|
+
icon: "bi-exclamation-triangle-fill",
|
|
21123
|
+
...options
|
|
21124
|
+
});
|
|
21125
|
+
}
|
|
21126
|
+
/**
|
|
21127
|
+
* Show a plain toast without specific styling
|
|
21128
|
+
* @param {string} message - The message to display
|
|
21129
|
+
* @param {object} options - Additional options
|
|
21130
|
+
*/
|
|
21131
|
+
plain(message, options = {}) {
|
|
21132
|
+
return this.show(message, "plain", {
|
|
21133
|
+
...options
|
|
21134
|
+
});
|
|
21135
|
+
}
|
|
21136
|
+
/**
|
|
21137
|
+
* Show a toast with specified type and options
|
|
21138
|
+
* @param {string} message - The message to display
|
|
21139
|
+
* @param {string} type - Toast type (success, error, info, warning)
|
|
21140
|
+
* @param {object} options - Additional options
|
|
21141
|
+
*/
|
|
21142
|
+
show(message, type = "info", options = {}) {
|
|
21143
|
+
this.enforceMaxToasts();
|
|
21144
|
+
const toastId = `toast-${++this.toastCounter}`;
|
|
21145
|
+
const config = {
|
|
21146
|
+
title: this.getDefaultTitle(type),
|
|
21147
|
+
icon: this.getDefaultIcon(type),
|
|
21148
|
+
autohide: this.options.autohide,
|
|
21149
|
+
delay: this.options.defaultDelay,
|
|
21150
|
+
dismissible: true,
|
|
21151
|
+
...options
|
|
21152
|
+
};
|
|
21153
|
+
const toastElement = this.createToastElement(toastId, message, type, config);
|
|
21154
|
+
this.container.appendChild(toastElement);
|
|
21155
|
+
if (typeof bootstrap === "undefined") {
|
|
21156
|
+
throw new Error("Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.");
|
|
21157
|
+
}
|
|
21158
|
+
const bsToast = new bootstrap.Toast(toastElement, {
|
|
21159
|
+
autohide: config.autohide,
|
|
21160
|
+
delay: config.delay
|
|
21161
|
+
});
|
|
21162
|
+
this.toasts.set(toastId, {
|
|
21163
|
+
element: toastElement,
|
|
21164
|
+
bootstrap: bsToast,
|
|
21165
|
+
type,
|
|
21166
|
+
message
|
|
21167
|
+
});
|
|
21168
|
+
toastElement.addEventListener("hidden.bs.toast", () => {
|
|
21169
|
+
this.cleanup(toastId);
|
|
21170
|
+
});
|
|
21171
|
+
bsToast.show();
|
|
21172
|
+
return {
|
|
21173
|
+
id: toastId,
|
|
21174
|
+
hide: () => {
|
|
21175
|
+
try {
|
|
21176
|
+
bsToast.hide();
|
|
21177
|
+
} catch (error) {
|
|
21178
|
+
console.warn("Error hiding toast:", error);
|
|
21179
|
+
}
|
|
21180
|
+
},
|
|
21181
|
+
dispose: () => this.cleanup(toastId),
|
|
21182
|
+
updateProgress: options.updateProgress || null
|
|
21183
|
+
};
|
|
21184
|
+
}
|
|
21185
|
+
/**
|
|
21186
|
+
* Show a toast with a View component in the body
|
|
21187
|
+
* @param {View} view - The View component to display
|
|
21188
|
+
* @param {string} type - Toast type (success, error, info, warning, plain)
|
|
21189
|
+
* @param {object} options - Additional options
|
|
21190
|
+
*/
|
|
21191
|
+
showView(view, type = "info", options = {}) {
|
|
21192
|
+
this.enforceMaxToasts();
|
|
21193
|
+
const toastId = `toast-${++this.toastCounter}`;
|
|
21194
|
+
const config = {
|
|
21195
|
+
title: options.title || this.getDefaultTitle(type),
|
|
21196
|
+
icon: options.icon || this.getDefaultIcon(type),
|
|
21197
|
+
autohide: this.options.autohide,
|
|
21198
|
+
delay: this.options.defaultDelay,
|
|
21199
|
+
dismissible: true,
|
|
21200
|
+
...options
|
|
21201
|
+
};
|
|
21202
|
+
const toastElement = this.createViewToastElement(toastId, view, type, config);
|
|
21203
|
+
this.container.appendChild(toastElement);
|
|
21204
|
+
if (typeof bootstrap === "undefined") {
|
|
21205
|
+
throw new Error("Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.");
|
|
21206
|
+
}
|
|
21207
|
+
const bsToast = new bootstrap.Toast(toastElement, {
|
|
21208
|
+
autohide: config.autohide,
|
|
21209
|
+
delay: config.delay
|
|
21210
|
+
});
|
|
21211
|
+
this.toasts.set(toastId, {
|
|
21212
|
+
element: toastElement,
|
|
21213
|
+
bootstrap: bsToast,
|
|
21214
|
+
type,
|
|
21215
|
+
view,
|
|
21216
|
+
message: "View toast"
|
|
21217
|
+
});
|
|
21218
|
+
toastElement.addEventListener("hidden.bs.toast", () => {
|
|
21219
|
+
this.cleanupView(toastId);
|
|
21220
|
+
});
|
|
21221
|
+
const bodyContainer = toastElement.querySelector(".toast-view-body");
|
|
21222
|
+
if (bodyContainer && view) {
|
|
21223
|
+
view.render(true, bodyContainer);
|
|
21224
|
+
}
|
|
21225
|
+
bsToast.show();
|
|
21226
|
+
return {
|
|
21227
|
+
id: toastId,
|
|
21228
|
+
view,
|
|
21229
|
+
hide: () => {
|
|
21230
|
+
try {
|
|
21231
|
+
bsToast.hide();
|
|
21232
|
+
} catch (error) {
|
|
21233
|
+
console.warn("Error hiding view toast:", error);
|
|
21234
|
+
}
|
|
21235
|
+
},
|
|
21236
|
+
dispose: () => this.cleanupView(toastId),
|
|
21237
|
+
updateProgress: (progressInfo) => {
|
|
21238
|
+
if (view && typeof view.updateProgress === "function") {
|
|
21239
|
+
view.updateProgress(progressInfo);
|
|
21240
|
+
}
|
|
21241
|
+
}
|
|
21242
|
+
};
|
|
21243
|
+
}
|
|
21244
|
+
/**
|
|
21245
|
+
* Create toast DOM element
|
|
21246
|
+
*/
|
|
21247
|
+
createToastElement(id, message, type, config) {
|
|
21248
|
+
const toast = document.createElement("div");
|
|
21249
|
+
toast.id = id;
|
|
21250
|
+
toast.className = `toast toast-service-${type}`;
|
|
21251
|
+
toast.setAttribute("role", "alert");
|
|
21252
|
+
toast.setAttribute("aria-live", "assertive");
|
|
21253
|
+
toast.setAttribute("aria-atomic", "true");
|
|
21254
|
+
const header = config.title || config.icon ? this.createToastHeader(config, type) : "";
|
|
21255
|
+
const body = this.createToastBody(message, config.icon && !config.title);
|
|
21256
|
+
toast.innerHTML = `
|
|
21257
|
+
${header}
|
|
21258
|
+
${body}
|
|
21259
|
+
`;
|
|
21260
|
+
return toast;
|
|
21261
|
+
}
|
|
21262
|
+
/**
|
|
21263
|
+
* Create toast DOM element for View component
|
|
21264
|
+
*/
|
|
21265
|
+
createViewToastElement(id, view, type, config) {
|
|
21266
|
+
const toast = document.createElement("div");
|
|
21267
|
+
toast.id = id;
|
|
21268
|
+
toast.className = `toast toast-service-${type}`;
|
|
21269
|
+
toast.setAttribute("role", "alert");
|
|
21270
|
+
toast.setAttribute("aria-live", "assertive");
|
|
21271
|
+
toast.setAttribute("aria-atomic", "true");
|
|
21272
|
+
const header = config.title || config.icon ? this.createToastHeader(config, type) : "";
|
|
21273
|
+
const body = this.createViewToastBody();
|
|
21274
|
+
toast.innerHTML = `
|
|
21275
|
+
${header}
|
|
21276
|
+
${body}
|
|
21277
|
+
`;
|
|
21278
|
+
return toast;
|
|
21279
|
+
}
|
|
21280
|
+
/**
|
|
21281
|
+
* Create toast body for View component
|
|
21282
|
+
*/
|
|
21283
|
+
createViewToastBody() {
|
|
21284
|
+
return `
|
|
21285
|
+
<div class="toast-body p-0">
|
|
21286
|
+
<div class="toast-view-body p-3"></div>
|
|
21287
|
+
</div>
|
|
21288
|
+
`;
|
|
21289
|
+
}
|
|
21290
|
+
/**
|
|
21291
|
+
* Create toast header with title and icon
|
|
21292
|
+
*/
|
|
21293
|
+
createToastHeader(config, _type) {
|
|
21294
|
+
const iconHtml = config.icon ? `<i class="${config.icon} toast-service-icon me-2"></i>` : "";
|
|
21295
|
+
const titleHtml = config.title ? `<strong class="me-auto">${iconHtml}${this.escapeHtml(config.title)}</strong>` : "";
|
|
21296
|
+
const timeHtml = config.showTime ? `<small class="text-muted">${this.getTimeString()}</small>` : "";
|
|
21297
|
+
const closeButton = config.dismissible ? `<button type="button" class="btn-close toast-service-close" data-bs-dismiss="toast" aria-label="Close"></button>` : "";
|
|
21298
|
+
if (!titleHtml && !timeHtml && !closeButton) {
|
|
21299
|
+
return "";
|
|
21300
|
+
}
|
|
21301
|
+
return `
|
|
21302
|
+
<div class="toast-header">
|
|
21303
|
+
${titleHtml}
|
|
21304
|
+
${timeHtml}
|
|
21305
|
+
${closeButton}
|
|
21306
|
+
</div>
|
|
21307
|
+
`;
|
|
21308
|
+
}
|
|
21309
|
+
/**
|
|
21310
|
+
* Create toast body with message
|
|
21311
|
+
*/
|
|
21312
|
+
createToastBody(message, showIcon = false) {
|
|
21313
|
+
const iconHtml = showIcon ? `<i class="${this.getDefaultIcon("info")} toast-service-icon me-2"></i>` : "";
|
|
21314
|
+
return `
|
|
21315
|
+
<div class="toast-body d-flex align-items-center">
|
|
21316
|
+
${iconHtml}
|
|
21317
|
+
<span>${this.escapeHtml(message)}</span>
|
|
21318
|
+
</div>
|
|
21319
|
+
`;
|
|
21320
|
+
}
|
|
21321
|
+
/**
|
|
21322
|
+
* Get default title for toast type
|
|
21323
|
+
*/
|
|
21324
|
+
getDefaultTitle(type) {
|
|
21325
|
+
const titles = {
|
|
21326
|
+
success: "Success",
|
|
21327
|
+
error: "Error",
|
|
21328
|
+
warning: "Warning",
|
|
21329
|
+
info: "Information",
|
|
21330
|
+
plain: ""
|
|
21331
|
+
};
|
|
21332
|
+
return titles[type] || "Notification";
|
|
21333
|
+
}
|
|
21334
|
+
/**
|
|
21335
|
+
* Get default icon for toast type
|
|
21336
|
+
*/
|
|
21337
|
+
getDefaultIcon(type) {
|
|
21338
|
+
const icons = {
|
|
21339
|
+
success: "bi-check-circle-fill",
|
|
21340
|
+
error: "bi-exclamation-triangle-fill",
|
|
21341
|
+
warning: "bi-exclamation-triangle-fill",
|
|
21342
|
+
info: "bi-info-circle-fill",
|
|
21343
|
+
plain: ""
|
|
21344
|
+
};
|
|
21345
|
+
return icons[type] || "bi-info-circle-fill";
|
|
21346
|
+
}
|
|
21347
|
+
/**
|
|
21348
|
+
* Enforce maximum number of toasts
|
|
21349
|
+
*/
|
|
21350
|
+
enforceMaxToasts() {
|
|
21351
|
+
const activeToasts = Array.from(this.toasts.values());
|
|
21352
|
+
if (activeToasts.length >= this.options.maxToasts) {
|
|
21353
|
+
const oldestId = this.toasts.keys().next().value;
|
|
21354
|
+
const oldest = this.toasts.get(oldestId);
|
|
21355
|
+
if (oldest) {
|
|
21356
|
+
oldest.bootstrap.hide();
|
|
21357
|
+
}
|
|
21358
|
+
}
|
|
21359
|
+
}
|
|
21360
|
+
/**
|
|
21361
|
+
* Clean up toast resources
|
|
21362
|
+
*/
|
|
21363
|
+
cleanup(toastId) {
|
|
21364
|
+
const toast = this.toasts.get(toastId);
|
|
21365
|
+
if (toast) {
|
|
21366
|
+
try {
|
|
21367
|
+
toast.bootstrap.dispose();
|
|
21368
|
+
} catch (e) {
|
|
21369
|
+
console.warn("Error disposing toast:", e);
|
|
21370
|
+
}
|
|
21371
|
+
if (toast.element && toast.element.parentNode) {
|
|
21372
|
+
toast.element.parentNode.removeChild(toast.element);
|
|
21373
|
+
}
|
|
21374
|
+
this.toasts.delete(toastId);
|
|
21375
|
+
}
|
|
21376
|
+
}
|
|
21377
|
+
/**
|
|
21378
|
+
* Clean up view toast resources with proper view disposal
|
|
21379
|
+
*/
|
|
21380
|
+
cleanupView(toastId) {
|
|
21381
|
+
const toast = this.toasts.get(toastId);
|
|
21382
|
+
if (toast) {
|
|
21383
|
+
if (toast.view && typeof toast.view.dispose === "function") {
|
|
21384
|
+
try {
|
|
21385
|
+
toast.view.dispose();
|
|
21386
|
+
} catch (e) {
|
|
21387
|
+
console.warn("Error disposing view in toast:", e);
|
|
21388
|
+
}
|
|
21389
|
+
}
|
|
21390
|
+
try {
|
|
21391
|
+
toast.bootstrap.dispose();
|
|
21392
|
+
} catch (e) {
|
|
21393
|
+
console.warn("Error disposing toast:", e);
|
|
21394
|
+
}
|
|
21395
|
+
if (toast.element && toast.element.parentNode) {
|
|
21396
|
+
toast.element.parentNode.removeChild(toast.element);
|
|
21397
|
+
}
|
|
21398
|
+
this.toasts.delete(toastId);
|
|
21399
|
+
}
|
|
21400
|
+
}
|
|
21401
|
+
/**
|
|
21402
|
+
* Hide all active toasts
|
|
21403
|
+
*/
|
|
21404
|
+
hideAll() {
|
|
21405
|
+
this.toasts.forEach((toast, _id) => {
|
|
21406
|
+
toast.bootstrap.hide();
|
|
21407
|
+
});
|
|
21408
|
+
}
|
|
21409
|
+
/**
|
|
21410
|
+
* Clear all toasts immediately
|
|
21411
|
+
*/
|
|
21412
|
+
clearAll() {
|
|
21413
|
+
this.toasts.forEach((toast, id) => {
|
|
21414
|
+
this.cleanup(id);
|
|
21415
|
+
});
|
|
21416
|
+
}
|
|
21417
|
+
/**
|
|
21418
|
+
* Get current time string
|
|
21419
|
+
*/
|
|
21420
|
+
getTimeString() {
|
|
21421
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString([], {
|
|
21422
|
+
hour: "2-digit",
|
|
21423
|
+
minute: "2-digit"
|
|
21424
|
+
});
|
|
21425
|
+
}
|
|
21426
|
+
/**
|
|
21427
|
+
* Escape HTML to prevent XSS
|
|
21428
|
+
*/
|
|
21429
|
+
escapeHtml(str) {
|
|
21430
|
+
const div = document.createElement("div");
|
|
21431
|
+
div.textContent = str;
|
|
21432
|
+
return div.innerHTML;
|
|
21433
|
+
}
|
|
21434
|
+
/**
|
|
21435
|
+
* Dispose of the entire toast service
|
|
21436
|
+
*/
|
|
21437
|
+
dispose() {
|
|
21438
|
+
this.clearAll();
|
|
21439
|
+
if (this.container && this.container.parentNode) {
|
|
21440
|
+
this.container.parentNode.removeChild(this.container);
|
|
21441
|
+
}
|
|
21442
|
+
}
|
|
21443
|
+
/**
|
|
21444
|
+
* Get statistics about active toasts
|
|
21445
|
+
*/
|
|
21446
|
+
getStats() {
|
|
21447
|
+
const stats = {
|
|
21448
|
+
total: this.toasts.size,
|
|
21449
|
+
byType: {}
|
|
21450
|
+
};
|
|
21451
|
+
this.toasts.forEach((toast) => {
|
|
21452
|
+
stats.byType[toast.type] = (stats.byType[toast.type] || 0) + 1;
|
|
21453
|
+
});
|
|
21454
|
+
return stats;
|
|
21455
|
+
}
|
|
21456
|
+
/**
|
|
21457
|
+
* Set global options
|
|
21458
|
+
*/
|
|
21459
|
+
setOptions(newOptions) {
|
|
21460
|
+
this.options = { ...this.options, ...newOptions };
|
|
21461
|
+
if (newOptions.position) {
|
|
21462
|
+
if (this.container) {
|
|
21463
|
+
this.container.className = `toast-container position-fixed ${this.getPositionClasses()}`;
|
|
21464
|
+
}
|
|
21465
|
+
}
|
|
21466
|
+
}
|
|
21467
|
+
}
|
|
21468
|
+
class FileUpload {
|
|
21469
|
+
constructor(fileModel, options = {}) {
|
|
21470
|
+
this.fileModel = fileModel;
|
|
21471
|
+
this.options = {
|
|
21472
|
+
file: null,
|
|
21473
|
+
name: null,
|
|
21474
|
+
group: null,
|
|
21475
|
+
description: null,
|
|
21476
|
+
onProgress: null,
|
|
21477
|
+
onComplete: null,
|
|
21478
|
+
onError: null,
|
|
21479
|
+
showToast: true,
|
|
21480
|
+
...options
|
|
21481
|
+
};
|
|
21482
|
+
if (!this.options.file || !(this.options.file instanceof File)) {
|
|
21483
|
+
throw new Error("FileUpload requires a valid File object");
|
|
21484
|
+
}
|
|
21485
|
+
this.cancelled = false;
|
|
21486
|
+
this.uploadRequest = null;
|
|
21487
|
+
this.progressToast = null;
|
|
21488
|
+
this.progressView = null;
|
|
21489
|
+
this.toastService = null;
|
|
21490
|
+
if (this.options.showToast) {
|
|
21491
|
+
this.toastService = new ToastService();
|
|
21492
|
+
}
|
|
21493
|
+
this.promise = this._startUpload();
|
|
21494
|
+
}
|
|
21495
|
+
/**
|
|
21496
|
+
* Main upload orchestration
|
|
21497
|
+
* @returns {Promise} Upload promise
|
|
21498
|
+
* @private
|
|
21499
|
+
*/
|
|
21500
|
+
async _startUpload() {
|
|
21501
|
+
try {
|
|
21502
|
+
if (this.options.showToast) {
|
|
21503
|
+
this._showProgressToast();
|
|
21504
|
+
}
|
|
21505
|
+
let uploadData;
|
|
21506
|
+
try {
|
|
21507
|
+
uploadData = await this._initiateUpload();
|
|
21508
|
+
} catch (error) {
|
|
21509
|
+
throw new Error(`Failed to initiate upload: ${error.message}`);
|
|
21510
|
+
}
|
|
21511
|
+
if (this.cancelled) {
|
|
21512
|
+
throw new Error("Upload cancelled");
|
|
21513
|
+
}
|
|
21514
|
+
if (!uploadData || !uploadData.upload_url) {
|
|
21515
|
+
throw new Error("Invalid upload response: missing upload URL");
|
|
21516
|
+
}
|
|
21517
|
+
let uploadConfig;
|
|
21518
|
+
if (typeof uploadData.upload_url === "string") {
|
|
21519
|
+
uploadConfig = {
|
|
21520
|
+
url: uploadData.upload_url,
|
|
21521
|
+
method: "PUT",
|
|
21522
|
+
fields: null,
|
|
21523
|
+
headers: {}
|
|
21524
|
+
};
|
|
21525
|
+
} else if (uploadData.upload_url && typeof uploadData.upload_url === "object" && uploadData.upload_url.upload_url) {
|
|
21526
|
+
uploadConfig = {
|
|
21527
|
+
url: uploadData.upload_url.upload_url,
|
|
21528
|
+
method: uploadData.upload_url.method || "POST",
|
|
21529
|
+
fields: uploadData.upload_url.fields || null,
|
|
21530
|
+
headers: uploadData.upload_url.headers || {}
|
|
21531
|
+
};
|
|
21532
|
+
} else {
|
|
21533
|
+
throw new Error(
|
|
21534
|
+
`Invalid upload response: unrecognised upload_url format. Server returned: ${JSON.stringify(uploadData.upload_url)}`
|
|
21535
|
+
);
|
|
21536
|
+
}
|
|
21537
|
+
let result;
|
|
21538
|
+
try {
|
|
21539
|
+
result = await this._performUpload(uploadConfig);
|
|
21540
|
+
} catch (error) {
|
|
21541
|
+
throw new Error(`File upload failed: ${error.message}`);
|
|
21542
|
+
}
|
|
21543
|
+
if (this.cancelled) {
|
|
21544
|
+
throw new Error("Upload cancelled");
|
|
21545
|
+
}
|
|
21546
|
+
try {
|
|
21547
|
+
await this._completeUpload();
|
|
21548
|
+
} catch (error) {
|
|
21549
|
+
console.warn("Failed to mark upload as completed:", error);
|
|
21550
|
+
}
|
|
21551
|
+
this._onComplete(this.fileModel);
|
|
21552
|
+
return this.fileModel;
|
|
21553
|
+
} catch (error) {
|
|
21554
|
+
if (error.message !== "Upload cancelled") {
|
|
21555
|
+
this._onError(error);
|
|
21556
|
+
}
|
|
21557
|
+
throw error;
|
|
21558
|
+
}
|
|
21559
|
+
}
|
|
21560
|
+
/**
|
|
21561
|
+
* Initiate upload by calling the API to get signed URL
|
|
21562
|
+
* @returns {Promise<Object>} Upload initiation data
|
|
21563
|
+
* @private
|
|
21564
|
+
*/
|
|
21565
|
+
async _initiateUpload() {
|
|
21566
|
+
try {
|
|
21567
|
+
const payload = {
|
|
21568
|
+
filename: this.options.name || this.options.file.name,
|
|
21569
|
+
file_size: this.options.file.size,
|
|
21570
|
+
content_type: this.options.file.type
|
|
21571
|
+
};
|
|
21572
|
+
if (this.options.group) payload.group = this.options.group;
|
|
21573
|
+
if (this.options.description) payload.description = this.options.description;
|
|
21574
|
+
const response = await this.fileModel.rest.POST("/api/fileman/upload/initiate", payload);
|
|
21575
|
+
if (!response) {
|
|
21576
|
+
throw new Error("No response from upload initiation API");
|
|
21577
|
+
}
|
|
21578
|
+
if (!response.data) {
|
|
21579
|
+
throw new Error("Upload initiation response missing data");
|
|
21580
|
+
}
|
|
21581
|
+
if (!response.data.status) {
|
|
21582
|
+
const errorMessage = response.data.error || "Upload initiation failed";
|
|
21583
|
+
throw new Error(errorMessage);
|
|
21584
|
+
}
|
|
21585
|
+
if (!response.data.data) {
|
|
21586
|
+
throw new Error("Upload initiation response missing data payload");
|
|
21587
|
+
}
|
|
21588
|
+
if (response.data.data.id) {
|
|
21589
|
+
this.fileModel.set("id", response.data.data.id);
|
|
21590
|
+
}
|
|
21591
|
+
return response.data.data;
|
|
21592
|
+
} catch (error) {
|
|
21593
|
+
if (error.message === "Network Error" || error.name === "TypeError") {
|
|
21594
|
+
throw new Error("Network error during upload initiation. Please check your connection.");
|
|
21595
|
+
}
|
|
21596
|
+
throw error;
|
|
21597
|
+
}
|
|
21598
|
+
}
|
|
21599
|
+
/**
|
|
21600
|
+
* Upload file using the normalised upload config from _startUpload.
|
|
21601
|
+
*
|
|
21602
|
+
* Two dispatch paths:
|
|
21603
|
+
* - PUT + raw bytes → legacy plain-string upload_url, or any config with method PUT.
|
|
21604
|
+
* Used by backends that issue a direct signed PUT URL (e.g. S3 signed URL).
|
|
21605
|
+
* - POST + FormData → config object with method POST and optional fields dict.
|
|
21606
|
+
* Used by the local filesystem backend and S3 presigned POST.
|
|
21607
|
+
* fields are appended first, then the file as the "file" key,
|
|
21608
|
+
* matching Django's request.FILES['file'] convention.
|
|
21609
|
+
* Content-Type is intentionally NOT set manually so the browser
|
|
21610
|
+
* can write the correct multipart boundary.
|
|
21611
|
+
*
|
|
21612
|
+
* @param {{ url: string, method: string, fields: object|null, headers: object }} uploadConfig
|
|
21613
|
+
* @returns {Promise} Upload result
|
|
21614
|
+
* @private
|
|
21615
|
+
*/
|
|
21616
|
+
async _performUpload(uploadConfig) {
|
|
21617
|
+
return new Promise((resolve, reject) => {
|
|
21618
|
+
if (!(this.options.file instanceof File)) {
|
|
21619
|
+
reject(new Error("Only single File objects are supported"));
|
|
21620
|
+
return;
|
|
21621
|
+
}
|
|
21622
|
+
const { url, method, fields, headers } = uploadConfig;
|
|
21623
|
+
const useFormData = method === "POST" && fields !== null;
|
|
21624
|
+
const xhr = new XMLHttpRequest();
|
|
21625
|
+
this.uploadRequest = xhr;
|
|
21626
|
+
xhr.upload.onprogress = (event) => {
|
|
21627
|
+
if (this.cancelled) return;
|
|
21628
|
+
this._onProgress({
|
|
21629
|
+
progress: event.loaded / event.total,
|
|
21630
|
+
loaded: event.loaded,
|
|
21631
|
+
total: event.total,
|
|
21632
|
+
percentage: Math.round(event.loaded / event.total * 100)
|
|
21633
|
+
});
|
|
21634
|
+
};
|
|
21635
|
+
xhr.onload = () => {
|
|
21636
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
21637
|
+
resolve({ data: xhr.response, status: xhr.status, statusText: xhr.statusText, xhr });
|
|
21638
|
+
} else {
|
|
21639
|
+
reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
|
|
21640
|
+
}
|
|
21641
|
+
};
|
|
21642
|
+
xhr.onerror = () => reject(new Error("Upload failed: Network error"));
|
|
21643
|
+
xhr.ontimeout = () => reject(new Error("Upload timed out — file may be too large or connection too slow"));
|
|
21644
|
+
xhr.onabort = () => reject(new Error("Upload cancelled"));
|
|
21645
|
+
let apiUrl = url;
|
|
21646
|
+
if (url.startsWith("/") && !url.startsWith("/api/")) {
|
|
21647
|
+
apiUrl = "/api" + url;
|
|
21648
|
+
}
|
|
21649
|
+
const resolvedUrl = this.fileModel.rest.buildUrl(apiUrl);
|
|
21650
|
+
xhr.open(method, resolvedUrl);
|
|
21651
|
+
xhr.timeout = 3e4;
|
|
21652
|
+
if (useFormData) {
|
|
21653
|
+
for (const [key, value] of Object.entries(headers || {})) {
|
|
21654
|
+
if (key.toLowerCase() !== "content-type") {
|
|
21655
|
+
xhr.setRequestHeader(key, value);
|
|
21656
|
+
}
|
|
21657
|
+
}
|
|
21658
|
+
const formData = new FormData();
|
|
21659
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
21660
|
+
formData.append(key, value);
|
|
21661
|
+
}
|
|
21662
|
+
formData.append("file", this.options.file);
|
|
21663
|
+
xhr.send(formData);
|
|
21664
|
+
} else {
|
|
21665
|
+
xhr.setRequestHeader("Content-Type", this.options.file.type);
|
|
21666
|
+
for (const [key, value] of Object.entries(headers || {})) {
|
|
21667
|
+
if (key.toLowerCase() !== "content-type") {
|
|
21668
|
+
xhr.setRequestHeader(key, value);
|
|
21669
|
+
}
|
|
21670
|
+
}
|
|
21671
|
+
xhr.send(this.options.file);
|
|
21672
|
+
}
|
|
21673
|
+
});
|
|
21674
|
+
}
|
|
21675
|
+
/**
|
|
21676
|
+
* Mark upload as completed in the API
|
|
21677
|
+
* @returns {Promise} Completion result
|
|
21678
|
+
* @private
|
|
21679
|
+
*/
|
|
21680
|
+
async _completeUpload() {
|
|
21681
|
+
try {
|
|
21682
|
+
const response = await this.fileModel.save({ action: "mark_as_completed" });
|
|
21683
|
+
if (!response) {
|
|
21684
|
+
throw new Error("No response from upload completion API");
|
|
21685
|
+
}
|
|
21686
|
+
if (response.data && !response.data.status) {
|
|
21687
|
+
const errorMessage = response.data.error || "Failed to mark upload as completed";
|
|
21688
|
+
throw new Error(errorMessage);
|
|
21689
|
+
}
|
|
21690
|
+
return response;
|
|
21691
|
+
} catch (error) {
|
|
21692
|
+
if (error.message === "Network Error" || error.name === "TypeError") {
|
|
21693
|
+
throw new Error("Network error during upload completion. The file may have uploaded successfully.");
|
|
21694
|
+
}
|
|
21695
|
+
throw error;
|
|
21696
|
+
}
|
|
21697
|
+
}
|
|
21698
|
+
/**
|
|
21699
|
+
* Handle progress updates
|
|
21700
|
+
* @param {Object} progressInfo - Progress information
|
|
21701
|
+
* @private
|
|
21702
|
+
*/
|
|
21703
|
+
_onProgress(progressInfo) {
|
|
21704
|
+
if (this.progressToast && this.progressToast.updateProgress) {
|
|
21705
|
+
this.progressToast.updateProgress(progressInfo);
|
|
21706
|
+
}
|
|
21707
|
+
if (typeof this.options.onProgress === "function") {
|
|
21708
|
+
this.options.onProgress(progressInfo);
|
|
21709
|
+
}
|
|
21710
|
+
}
|
|
21711
|
+
/**
|
|
21712
|
+
* Handle successful upload completion
|
|
21713
|
+
* @param {Object} result - Upload result
|
|
21714
|
+
* @private
|
|
21715
|
+
*/
|
|
21716
|
+
_onComplete(result) {
|
|
21717
|
+
if (this.progressView) {
|
|
21718
|
+
this.progressView.markCompleted("Upload completed successfully!");
|
|
21719
|
+
}
|
|
21720
|
+
if (this.progressToast) {
|
|
21721
|
+
setTimeout(() => {
|
|
21722
|
+
try {
|
|
21723
|
+
if (this.progressToast && typeof this.progressToast.hide === "function") {
|
|
21724
|
+
this.progressToast.hide();
|
|
21725
|
+
}
|
|
21726
|
+
} catch (error) {
|
|
21727
|
+
console.warn("Error hiding progress toast:", error);
|
|
21728
|
+
}
|
|
21729
|
+
}, 2e3);
|
|
21730
|
+
}
|
|
21731
|
+
if (typeof this.options.onComplete === "function") {
|
|
21732
|
+
this.options.onComplete(result);
|
|
21733
|
+
}
|
|
21734
|
+
}
|
|
21735
|
+
/**
|
|
21736
|
+
* Handle upload errors
|
|
21737
|
+
* @param {Error} error - Error object
|
|
21738
|
+
* @private
|
|
21739
|
+
*/
|
|
21740
|
+
_onError(error) {
|
|
21741
|
+
if (this.progressToast) {
|
|
21742
|
+
try {
|
|
21743
|
+
this.progressToast.hide();
|
|
21744
|
+
} catch (error2) {
|
|
21745
|
+
console.warn("Error hiding progress toast on error:", error2);
|
|
21746
|
+
}
|
|
21747
|
+
}
|
|
21748
|
+
if (this.toastService) {
|
|
21749
|
+
this.toastService.error(`Upload failed: ${error.message}`);
|
|
21750
|
+
}
|
|
21751
|
+
if (typeof this.options.onError === "function") {
|
|
21752
|
+
this.options.onError(error);
|
|
21753
|
+
}
|
|
21754
|
+
}
|
|
21755
|
+
/**
|
|
21756
|
+
* Show progress toast with ProgressView component
|
|
21757
|
+
* @private
|
|
21758
|
+
*/
|
|
21759
|
+
_showProgressToast() {
|
|
21760
|
+
this.progressView = new ProgressView({
|
|
21761
|
+
filename: this.options.name || this.options.file.name,
|
|
21762
|
+
filesize: this.options.file.size,
|
|
21763
|
+
showCancel: true,
|
|
21764
|
+
onCancel: () => this.cancel()
|
|
21765
|
+
});
|
|
21766
|
+
this.progressToast = this.toastService.showView(this.progressView, "info", {
|
|
21767
|
+
title: "File Upload",
|
|
21768
|
+
autohide: false,
|
|
21769
|
+
dismissible: false
|
|
21770
|
+
});
|
|
21771
|
+
}
|
|
21772
|
+
/**
|
|
21773
|
+
* Cancel the upload
|
|
21774
|
+
* @returns {boolean} True if cancelled, false if already completed
|
|
21775
|
+
*/
|
|
21776
|
+
cancel() {
|
|
21777
|
+
if (this.cancelled) {
|
|
21778
|
+
return false;
|
|
21779
|
+
}
|
|
21780
|
+
this.cancelled = true;
|
|
21781
|
+
if (this.uploadRequest && typeof this.uploadRequest.abort === "function") {
|
|
21782
|
+
this.uploadRequest.abort();
|
|
21783
|
+
}
|
|
21784
|
+
if (this.progressView) {
|
|
21785
|
+
this.progressView.markCancelled();
|
|
21786
|
+
}
|
|
21787
|
+
if (this.progressToast) {
|
|
21788
|
+
setTimeout(() => {
|
|
21789
|
+
try {
|
|
21790
|
+
if (this.progressToast && typeof this.progressToast.hide === "function") {
|
|
21791
|
+
this.progressToast.hide();
|
|
21792
|
+
}
|
|
21793
|
+
} catch (error) {
|
|
21794
|
+
console.warn("Error hiding progress toast on cancel:", error);
|
|
21795
|
+
}
|
|
21796
|
+
}, 1500);
|
|
21797
|
+
}
|
|
21798
|
+
return true;
|
|
21799
|
+
}
|
|
21800
|
+
/**
|
|
21801
|
+
* Check if upload is cancelled
|
|
21802
|
+
* @returns {boolean} True if cancelled
|
|
21803
|
+
*/
|
|
21804
|
+
isCancelled() {
|
|
21805
|
+
return this.cancelled;
|
|
21806
|
+
}
|
|
21807
|
+
/**
|
|
21808
|
+
* Promise interface - then
|
|
21809
|
+
* @param {function} onSuccess - Success handler
|
|
21810
|
+
* @param {function} onError - Error handler
|
|
21811
|
+
* @returns {Promise} Promise chain
|
|
21812
|
+
*/
|
|
21813
|
+
then(onSuccess, onError) {
|
|
21814
|
+
return this.promise.then(onSuccess, onError);
|
|
21815
|
+
}
|
|
21816
|
+
/**
|
|
21817
|
+
* Promise interface - catch
|
|
21818
|
+
* @param {function} onError - Error handler
|
|
21819
|
+
* @returns {Promise} Promise chain
|
|
21820
|
+
*/
|
|
21821
|
+
catch(onError) {
|
|
21822
|
+
return this.promise.catch(onError);
|
|
21823
|
+
}
|
|
21824
|
+
/**
|
|
21825
|
+
* Promise interface - finally
|
|
21826
|
+
* @param {function} onFinally - Finally handler
|
|
21827
|
+
* @returns {Promise} Promise chain
|
|
21828
|
+
*/
|
|
21829
|
+
finally(onFinally) {
|
|
21830
|
+
return this.promise.finally(onFinally);
|
|
21831
|
+
}
|
|
21832
|
+
/**
|
|
21833
|
+
* Get upload statistics
|
|
21834
|
+
* @returns {Object} Upload stats
|
|
21835
|
+
*/
|
|
21836
|
+
getStats() {
|
|
21837
|
+
return {
|
|
21838
|
+
filename: this.options.file.name,
|
|
21839
|
+
size: this.options.file.size,
|
|
21840
|
+
type: this.options.file.type,
|
|
21841
|
+
cancelled: this.cancelled,
|
|
21842
|
+
group: this.options.group,
|
|
21843
|
+
description: this.options.description
|
|
21844
|
+
};
|
|
21845
|
+
}
|
|
21846
|
+
}
|
|
21847
|
+
class Group extends Model {
|
|
21848
|
+
constructor(data = {}) {
|
|
21849
|
+
super(data, {
|
|
21850
|
+
endpoint: "/api/group"
|
|
21851
|
+
});
|
|
21852
|
+
}
|
|
21853
|
+
}
|
|
21854
|
+
class GroupList extends Collection {
|
|
21855
|
+
constructor(options = {}) {
|
|
21856
|
+
super({
|
|
21857
|
+
ModelClass: Group,
|
|
21858
|
+
endpoint: "/api/group",
|
|
21859
|
+
size: 10,
|
|
21860
|
+
...options
|
|
21861
|
+
});
|
|
21862
|
+
}
|
|
21863
|
+
}
|
|
21864
|
+
const GroupKinds = {
|
|
21865
|
+
"org": "Organization",
|
|
21866
|
+
"division": "Division",
|
|
21867
|
+
"department": "Department",
|
|
21868
|
+
"team": "Team",
|
|
21869
|
+
"merchant": "Merchant",
|
|
21870
|
+
"partner": "Partner",
|
|
21871
|
+
"client": "Client",
|
|
21872
|
+
"iso": "ISO",
|
|
21873
|
+
"sales": "Sales",
|
|
21874
|
+
"reseller": "Reseller",
|
|
21875
|
+
"location": "Location",
|
|
21876
|
+
"region": "Region",
|
|
21877
|
+
"route": "Route",
|
|
21878
|
+
"project": "Project",
|
|
21879
|
+
"inventory": "Inventory",
|
|
21880
|
+
"test": "Testing",
|
|
21881
|
+
"misc": "Miscellaneous",
|
|
21882
|
+
"qa": "Quality Assurance"
|
|
21883
|
+
};
|
|
21884
|
+
const GroupKindOptions = Object.entries(GroupKinds).map(([key, label]) => ({
|
|
21885
|
+
value: key,
|
|
21886
|
+
label
|
|
21887
|
+
}));
|
|
21888
|
+
const GroupForms = {
|
|
21889
|
+
create: {
|
|
21890
|
+
title: "Create Group",
|
|
21891
|
+
fields: [
|
|
21892
|
+
{
|
|
21893
|
+
name: "name",
|
|
21894
|
+
type: "text",
|
|
21895
|
+
label: "Group Name",
|
|
21896
|
+
required: true,
|
|
21897
|
+
placeholder: "Enter group name"
|
|
21898
|
+
},
|
|
21899
|
+
{
|
|
21900
|
+
name: "kind",
|
|
21901
|
+
type: "select",
|
|
21902
|
+
label: "Group Kind",
|
|
21903
|
+
required: true,
|
|
21904
|
+
options: GroupKindOptions
|
|
21905
|
+
},
|
|
21906
|
+
{
|
|
21907
|
+
type: "collection",
|
|
21908
|
+
name: "parent",
|
|
21909
|
+
label: "Parent Group",
|
|
21910
|
+
Collection: GroupList,
|
|
21911
|
+
// Collection class
|
|
21912
|
+
labelField: "name",
|
|
21913
|
+
// Field to display in dropdown
|
|
21914
|
+
valueField: "id",
|
|
21915
|
+
// Field to use as value
|
|
21916
|
+
maxItems: 10,
|
|
21917
|
+
// Max items to show in dropdown
|
|
21918
|
+
placeholder: "Search groups...",
|
|
21919
|
+
emptyFetch: false,
|
|
21920
|
+
debounceMs: 300
|
|
21921
|
+
// Search debounce delay
|
|
21922
|
+
}
|
|
21923
|
+
]
|
|
21924
|
+
},
|
|
21925
|
+
edit: {
|
|
21926
|
+
title: "Edit Group",
|
|
21927
|
+
fields: [
|
|
21928
|
+
{
|
|
21929
|
+
name: "name",
|
|
21930
|
+
type: "text",
|
|
21931
|
+
label: "Group Name",
|
|
21932
|
+
required: true,
|
|
21933
|
+
placeholder: "Enter group name"
|
|
21934
|
+
},
|
|
21935
|
+
{
|
|
21936
|
+
name: "kind",
|
|
21937
|
+
type: "select",
|
|
21938
|
+
label: "Group Kind",
|
|
21939
|
+
required: true,
|
|
21940
|
+
options: GroupKindOptions
|
|
21941
|
+
},
|
|
21942
|
+
{
|
|
21943
|
+
type: "collection",
|
|
21944
|
+
name: "parent",
|
|
21945
|
+
label: "Parent Group",
|
|
21946
|
+
Collection: GroupList,
|
|
21947
|
+
// Collection class
|
|
21948
|
+
labelField: "name",
|
|
21949
|
+
// Field to display in dropdown
|
|
21950
|
+
valueField: "id",
|
|
21951
|
+
// Field to use as value
|
|
21952
|
+
maxItems: 10,
|
|
21953
|
+
// Max items to show in dropdown
|
|
21954
|
+
placeholder: "Search groups...",
|
|
21955
|
+
emptyFetch: false,
|
|
21956
|
+
debounceMs: 300
|
|
21957
|
+
// Search debounce delay
|
|
21958
|
+
},
|
|
21959
|
+
{
|
|
21960
|
+
name: "metadata.domain",
|
|
21961
|
+
type: "text",
|
|
21962
|
+
label: "Default Domain",
|
|
21963
|
+
placeholder: "Enter Domain"
|
|
21964
|
+
},
|
|
21965
|
+
{
|
|
21966
|
+
name: "metadata.portal",
|
|
21967
|
+
type: "text",
|
|
21968
|
+
label: "Default Portal",
|
|
21969
|
+
placeholder: "Enter Portal URL"
|
|
21970
|
+
},
|
|
21971
|
+
{
|
|
21972
|
+
name: "is_active",
|
|
21973
|
+
type: "switch",
|
|
21974
|
+
label: "Is Active",
|
|
21975
|
+
cols: 4
|
|
21976
|
+
}
|
|
21977
|
+
]
|
|
21978
|
+
}
|
|
21979
|
+
};
|
|
21980
|
+
Group.EDIT_FORM = GroupForms.edit;
|
|
21981
|
+
Group.ADD_FORM = GroupForms.create;
|
|
21982
|
+
Group.CREATE_FORM = GroupForms.create;
|
|
21983
|
+
Group.GroupKindOptions = GroupKindOptions;
|
|
21984
|
+
Group.GroupKinds = GroupKinds;
|
|
21985
|
+
class User extends Model {
|
|
21986
|
+
constructor(data = {}) {
|
|
21987
|
+
super(data, {
|
|
21988
|
+
endpoint: "/api/user"
|
|
21989
|
+
});
|
|
21990
|
+
}
|
|
21991
|
+
hasPermission(permission) {
|
|
21992
|
+
if (this.get("is_superuser")) return true;
|
|
21993
|
+
if (Array.isArray(permission)) {
|
|
21994
|
+
return permission.some((p) => this.hasPermission(p));
|
|
21995
|
+
}
|
|
21996
|
+
const isSysPermission = permission.startsWith("sys.");
|
|
21997
|
+
const permissionToCheck = isSysPermission ? permission.substring(4) : permission;
|
|
21998
|
+
if (this._hasPermission(permissionToCheck)) {
|
|
21999
|
+
return true;
|
|
22000
|
+
}
|
|
22001
|
+
if (!isSysPermission && this.member && this.member.hasPermission(permission)) {
|
|
22002
|
+
return true;
|
|
22003
|
+
}
|
|
22004
|
+
return false;
|
|
22005
|
+
}
|
|
22006
|
+
_hasPermission(permission) {
|
|
22007
|
+
const permissions = this.get("permissions");
|
|
22008
|
+
if (!permissions) {
|
|
22009
|
+
return false;
|
|
22010
|
+
}
|
|
22011
|
+
return permissions[permission] == true;
|
|
22012
|
+
}
|
|
22013
|
+
hasPerm(p) {
|
|
22014
|
+
return this.hasPermission(p);
|
|
22015
|
+
}
|
|
22016
|
+
}
|
|
22017
|
+
User.PERMISSIONS = [
|
|
22018
|
+
{ name: "manage_users", label: "Manage Users" },
|
|
22019
|
+
{ name: "view_users", label: "View Users" },
|
|
22020
|
+
{ name: "view_groups", label: "View Groups" },
|
|
22021
|
+
{ name: "manage_groups", label: "Manage Groups" },
|
|
22022
|
+
{ name: "view_metrics", label: "View System Metrics" },
|
|
22023
|
+
{ name: "manage_metrics", label: "Manage System Metrics" },
|
|
22024
|
+
{ name: "view_logs", label: "View Logs" },
|
|
22025
|
+
{ name: "view_incidents", label: "View Incidents" },
|
|
22026
|
+
{ name: "manage_incidents", label: "Manage Incidents" },
|
|
22027
|
+
{ name: "view_tickets", label: "View Tickets" },
|
|
22028
|
+
{ name: "manage_tickets", label: "Manage Tickets" },
|
|
22029
|
+
{ name: "view_admin", label: "View Admin" },
|
|
22030
|
+
{ name: "view_jobs", label: "View Jobs" },
|
|
22031
|
+
{ name: "manage_jobs", label: "Manage Jobs" },
|
|
22032
|
+
{ name: "view_global", label: "View Global" },
|
|
22033
|
+
{ name: "manage_notifications", label: "Manage Notifications" },
|
|
22034
|
+
{ name: "manage_files", label: "Manage Files" },
|
|
22035
|
+
{ name: "force_single_session", label: "Force Single Session" },
|
|
22036
|
+
{ name: "file_vault", label: "Access File Vault" },
|
|
22037
|
+
{ name: "manage_aws", label: "Manage AWS" },
|
|
22038
|
+
{ name: "manage_docit", label: "Manage DocIt" }
|
|
22039
|
+
];
|
|
22040
|
+
User.PERMISSION_FIELDS = [
|
|
22041
|
+
...User.PERMISSIONS.map((permission) => ({
|
|
22042
|
+
name: `permissions.${permission.name}`,
|
|
22043
|
+
type: "switch",
|
|
22044
|
+
label: permission.label,
|
|
22045
|
+
columns: 4
|
|
22046
|
+
}))
|
|
22047
|
+
];
|
|
22048
|
+
const UserForms = {
|
|
22049
|
+
create: {
|
|
22050
|
+
title: "Create User",
|
|
22051
|
+
fields: [
|
|
22052
|
+
{ name: "email", type: "text", label: "Email", required: true },
|
|
22053
|
+
{ name: "phone_number", type: "text", label: "Phone number", columns: 12 },
|
|
22054
|
+
{ name: "display_name", type: "text", label: "Display Name" }
|
|
22055
|
+
]
|
|
22056
|
+
},
|
|
22057
|
+
edit: {
|
|
22058
|
+
title: "Edit User",
|
|
22059
|
+
fields: [
|
|
22060
|
+
{ name: "email", type: "email", label: "Email", columns: 12 },
|
|
22061
|
+
{ name: "display_name", type: "text", label: "Display Name", columns: 12 },
|
|
22062
|
+
{ name: "phone_number", type: "text", label: "Phone number", columns: 12 },
|
|
22063
|
+
{ type: "collection", name: "org", label: "Organization", Collection: GroupList, labelField: "name", valueField: "id", columns: 12 }
|
|
22064
|
+
]
|
|
22065
|
+
},
|
|
22066
|
+
permissions: {
|
|
22067
|
+
fields: User.PERMISSION_FIELDS
|
|
22068
|
+
}
|
|
22069
|
+
};
|
|
22070
|
+
const UserDataView = {
|
|
22071
|
+
// Comprehensive view with all data
|
|
22072
|
+
detailed: {
|
|
22073
|
+
title: "Detailed User Information",
|
|
22074
|
+
columns: 2,
|
|
22075
|
+
showEmptyValues: true,
|
|
22076
|
+
emptyValueText: "Not set",
|
|
22077
|
+
fields: [
|
|
22078
|
+
// Basic Info Section
|
|
22079
|
+
{
|
|
22080
|
+
name: "id",
|
|
22081
|
+
label: "User ID",
|
|
22082
|
+
type: "number",
|
|
22083
|
+
colSize: 3
|
|
22084
|
+
},
|
|
22085
|
+
{
|
|
22086
|
+
name: "display_name",
|
|
22087
|
+
label: "Display Name",
|
|
22088
|
+
type: "text",
|
|
22089
|
+
format: 'capitalize|default("Unnamed User")',
|
|
22090
|
+
colSize: 9
|
|
22091
|
+
},
|
|
22092
|
+
{
|
|
22093
|
+
name: "username",
|
|
22094
|
+
label: "Username",
|
|
22095
|
+
type: "text",
|
|
22096
|
+
format: "lowercase",
|
|
22097
|
+
colSize: 6
|
|
22098
|
+
},
|
|
22099
|
+
{
|
|
22100
|
+
name: "email",
|
|
22101
|
+
label: "Email Address",
|
|
22102
|
+
type: "email",
|
|
22103
|
+
colSize: 6
|
|
22104
|
+
},
|
|
22105
|
+
{
|
|
22106
|
+
name: "phone_number",
|
|
22107
|
+
label: "Phone Number",
|
|
22108
|
+
type: "phone",
|
|
22109
|
+
format: 'phone|default("Not provided")',
|
|
22110
|
+
colSize: 6
|
|
22111
|
+
},
|
|
22112
|
+
{
|
|
22113
|
+
name: "is_active",
|
|
22114
|
+
label: "Account Status",
|
|
22115
|
+
type: "boolean",
|
|
22116
|
+
colSize: 6
|
|
22117
|
+
},
|
|
22118
|
+
// Activity Info
|
|
22119
|
+
{
|
|
22120
|
+
name: "last_login",
|
|
22121
|
+
label: "Last Login",
|
|
22122
|
+
type: "datetime",
|
|
22123
|
+
format: "relative",
|
|
22124
|
+
colSize: 6
|
|
22125
|
+
},
|
|
22126
|
+
{
|
|
22127
|
+
name: "last_activity",
|
|
22128
|
+
label: "Last Activity",
|
|
22129
|
+
type: "datetime",
|
|
22130
|
+
format: "relative",
|
|
22131
|
+
colSize: 6
|
|
22132
|
+
},
|
|
22133
|
+
// Avatar Info
|
|
22134
|
+
{
|
|
22135
|
+
name: "avatar.url",
|
|
22136
|
+
label: "Avatar",
|
|
22137
|
+
type: "url",
|
|
22138
|
+
colSize: 12
|
|
22139
|
+
},
|
|
22140
|
+
// Complex Data (will use full width automatically)
|
|
22141
|
+
{
|
|
22142
|
+
name: "permissions",
|
|
22143
|
+
label: "User Permissions",
|
|
22144
|
+
type: "dataview",
|
|
22145
|
+
dataViewColumns: 2,
|
|
22146
|
+
showEmptyValues: false
|
|
22147
|
+
},
|
|
22148
|
+
{
|
|
22149
|
+
name: "metadata",
|
|
22150
|
+
label: "User Metadata",
|
|
22151
|
+
type: "dataview",
|
|
22152
|
+
dataViewColumns: 1
|
|
22153
|
+
},
|
|
22154
|
+
{
|
|
22155
|
+
name: "avatar",
|
|
22156
|
+
label: "Avatar Details",
|
|
22157
|
+
type: "dataview",
|
|
22158
|
+
dataViewColumns: 1
|
|
22159
|
+
}
|
|
22160
|
+
]
|
|
22161
|
+
}
|
|
22162
|
+
};
|
|
22163
|
+
User.DATA_VIEW = UserDataView.detailed;
|
|
22164
|
+
User.EDIT_FORM = UserForms.edit;
|
|
22165
|
+
User.ADD_FORM = UserForms.create;
|
|
22166
|
+
let File$1 = class File extends Model {
|
|
22167
|
+
constructor(data = {}) {
|
|
22168
|
+
super(data, {
|
|
22169
|
+
endpoint: "/api/fileman/file"
|
|
22170
|
+
});
|
|
22171
|
+
}
|
|
22172
|
+
isImage() {
|
|
22173
|
+
return this.get("category") === "image";
|
|
22174
|
+
}
|
|
22175
|
+
/**
|
|
22176
|
+
* Upload file with progress tracking and UI integration
|
|
22177
|
+
* Returns a FileUpload instance with promise interface and cancellation support
|
|
22178
|
+
*
|
|
22179
|
+
* @param {object} options - Upload configuration
|
|
22180
|
+
* @param {File} options.file - File object to upload
|
|
22181
|
+
* @param {string} options.name - Custom filename (optional)
|
|
22182
|
+
* @param {string} options.group - File group/category (optional)
|
|
22183
|
+
* @param {string} options.description - File description (optional)
|
|
22184
|
+
* @param {function} options.onProgress - Progress callback ({ progress, loaded, total, percentage })
|
|
22185
|
+
* @param {function} options.onComplete - Success callback
|
|
22186
|
+
* @param {function} options.onError - Error callback
|
|
22187
|
+
* @param {boolean} options.showToast - Show progress toast (default: true)
|
|
22188
|
+
* @returns {FileUpload} Upload instance with promise interface
|
|
22189
|
+
*/
|
|
22190
|
+
upload(options = {}) {
|
|
22191
|
+
return new FileUpload(this, options);
|
|
22192
|
+
}
|
|
22193
|
+
};
|
|
22194
|
+
const Files = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
22195
|
+
__proto__: null,
|
|
22196
|
+
File: File$1
|
|
22197
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
20970
22198
|
class DataView extends View {
|
|
20971
22199
|
constructor(options = {}) {
|
|
20972
22200
|
const {
|