@stackcraftmx/calcita 0.53.0 → 0.54.0
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/README.md +56 -0
- package/dist/filepond/FileDropzone.js +60 -19
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/filepond/pondServer.js +86 -27
- package/dist/filepond/pondServer.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/types/filepond/FileDropzone.d.ts +5 -0
- package/dist/types/filepond/chunkedUpload.d.ts +122 -0
- package/dist/types/filepond/pondServer.d.ts +25 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/locales/en.d.ts +1 -0
- package/dist/types/locales/es.d.ts +1 -0
- package/dist/types/upload/chunkedUpload.d.ts +189 -0
- package/dist/types/upload/uploadWithProgress.d.ts +4 -2
- package/dist/upload/chunkedUpload.js +261 -0
- package/dist/upload/chunkedUpload.js.map +1 -0
- package/dist/upload/uploadWithProgress.js +8 -3
- package/dist/upload/uploadWithProgress.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1259,6 +1259,62 @@ import {FormFileUploadField} from '@stackcraftmx/calcita/forms';
|
|
|
1259
1259
|
omisión `result.id`). Lo del negocio (Aprobado, Falta información) va junto al estado con `actions`, y `onStatusChange`
|
|
1260
1260
|
dice en qué va cada renglón para contar «2 de 3 cargados».
|
|
1261
1261
|
|
|
1262
|
+
### Por trozos, con FilePond
|
|
1263
|
+
|
|
1264
|
+
Para archivos grandes o servidores que solo reciben por trozos: la zona de `/filepond` con `createPondServer` sube cada
|
|
1265
|
+
archivo con el protocolo de trozos de FilePond (un POST da el folio, cada trozo va en su PATCH a `patchUrl` + folio, uno
|
|
1266
|
+
detrás de otro, y un HEAD dice hasta dónde llegó para retomar). Las opciones llevan los nombres de FilePond, así que un
|
|
1267
|
+
backend que ya recibía trozos de FilePond no cambia.
|
|
1268
|
+
|
|
1269
|
+
```jsx
|
|
1270
|
+
import {FileDropzone, createPondServer} from '@stackcraftmx/calcita/filepond';
|
|
1271
|
+
|
|
1272
|
+
const backupServer = createPondServer({
|
|
1273
|
+
url: '/upload',
|
|
1274
|
+
patchUrl: '/upload?patch=',
|
|
1275
|
+
headers: {Authorization: `Bearer ${token}`, 'X-Upload-Context': JSON.stringify({branch: 'Querétaro'})},
|
|
1276
|
+
// un backend que contesta {message, errors} en lugar de RFC 9457: la tarjeta enseña el title
|
|
1277
|
+
toProblem: (body) => ({title: body.message, detail: body.message, errors: body.errors}),
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
<FileDropzone value={file} onChange={setFile} server={backupServer} apiRef={pondApi}
|
|
1281
|
+
chunkUploads chunkForce chunkSize="1 MB" chunkRetryDelays={[500, 1000, 3000]}
|
|
1282
|
+
pondProps={{maxParallelUploads: 1}} />
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
- La barra es honesta: cuenta lo que el servidor ya guardó y no dice 100 % hasta que guardó el último trozo (hay servidores
|
|
1286
|
+
que esperan medio segundo por trozo). Nunca regresa, ni cuando un trozo se reintenta.
|
|
1287
|
+
- Un trozo sin respuesta o con 5xx se reintenta con las esperas de `chunkRetryDelays` (`null` o `[]`, sin reintentos); un 4xx
|
|
1288
|
+
es el servidor diciendo que no y su motivo sale en la tarjeta en seguida. Con el botón de reintentar de la tarjeta, sigue
|
|
1289
|
+
desde el último trozo completo, con el mismo folio.
|
|
1290
|
+
- `chunkSize` va en bytes o con unidades, como `maxSize` (por omisión 5 MB, el de FilePond); `chunkForce` manda por trozos
|
|
1291
|
+
también lo chico.
|
|
1292
|
+
- Los headers van en las tres peticiones y por trozos viajan en UTF-8 (el nombre del archivo también): páselos como texto
|
|
1293
|
+
normal. Un sistema que los convertía a mano para una versión vieja de FilePond (`unescape(encodeURIComponent(...))`) ya no
|
|
1294
|
+
debe hacerlo: convertidos dos veces, los acentos llegan rotos.
|
|
1295
|
+
- `sendRequest` cambia por dónde viaja cada petición (su cliente, o uno de mentira para probar sin servidor); por omisión es
|
|
1296
|
+
`uploadWithProgress`, que ahora también acepta `method`.
|
|
1297
|
+
- `pondProps` pasa cualquier otra opción de FilePond tal cual. Lo que la zona ya maneja gana (los tipos y pesos, los textos,
|
|
1298
|
+
el candado de `disabled`/`busy`), y sus eventos (`onupdatefiles`, `onprocessfile`, `onwarning`, `onerror`, `beforeAddFile`)
|
|
1299
|
+
corren después de los de la zona.
|
|
1300
|
+
|
|
1301
|
+
El cargador mini también sube por trozos: `createChunkedUpload` arma la función `upload` de `FileUploadInput` y
|
|
1302
|
+
`FormFileUploadField` con el mismo protocolo, las mismas opciones (`patchUrl`, `chunkSize`, `chunkRetryDelays`, `headers`,
|
|
1303
|
+
`toProblem`, `sendRequest`) y sus estados de siempre. Con el último byte enviado la barra llega a 100 % y el renglón dice
|
|
1304
|
+
«Procesando» hasta que el servidor guardó el último trozo; al renglón llega el folio como id.
|
|
1305
|
+
|
|
1306
|
+
```jsx
|
|
1307
|
+
import {FileUploadInput, createChunkedUpload} from '@stackcraftmx/calcita';
|
|
1308
|
+
|
|
1309
|
+
const uploadBackup = createChunkedUpload({url: '/upload', patchUrl: '/upload?patch=', chunkSize: '1 MB',
|
|
1310
|
+
toProblem: (body) => ({title: body.message, detail: body.message, errors: body.errors})});
|
|
1311
|
+
|
|
1312
|
+
<FileUploadInput label="Respaldo de la base" value={backupId} onChange={setBackupId} upload={uploadBackup} />
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
Todo va por trozos (uno chico, en un solo trozo). Si solo los grandes deben ir así, la pantalla elige:
|
|
1316
|
+
`upload={(file, options) => (file.size > LIMITE ? uploadBackup : subidaNormal)(file, options)}`.
|
|
1317
|
+
|
|
1262
1318
|
## Documentos, avisos legales y cuenta regresiva
|
|
1263
1319
|
|
|
1264
1320
|
- `PdfViewerDialog`: un PDF en una ventana con el visor del navegador (sin librerías), con «Abrir en otra pestaña» y
|
|
@@ -34,6 +34,30 @@ function detectFileType(file) {
|
|
|
34
34
|
const dotPosition = file.name.lastIndexOf(".");
|
|
35
35
|
return Promise.resolve(dotPosition >= 0 ? file.name.slice(dotPosition).toLowerCase() : mimeType);
|
|
36
36
|
}
|
|
37
|
+
function stablePondPropsOf(pondProps, stableFunctions, latest) {
|
|
38
|
+
const { files: ignoredFiles, ...pondOptions } = pondProps ?? {};
|
|
39
|
+
if (ignoredFiles !== void 0) {
|
|
40
|
+
warnUnknownProps(THEME_NAME, { "pondProps.files": ignoredFiles });
|
|
41
|
+
}
|
|
42
|
+
return Object.fromEntries(Object.entries(pondOptions).map(([option, value]) => {
|
|
43
|
+
if (typeof value !== "function") {
|
|
44
|
+
return [option, value];
|
|
45
|
+
}
|
|
46
|
+
if (!stableFunctions.current.has(option)) {
|
|
47
|
+
stableFunctions.current.set(option, (...args) => latest.current.pondProps?.[option]?.(...args));
|
|
48
|
+
}
|
|
49
|
+
return [option, stableFunctions.current.get(option)];
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
function chunkOptionsOf({ chunkUploads, chunkForce, chunkSize, chunkRetryDelays }) {
|
|
53
|
+
return {
|
|
54
|
+
...chunkUploads !== void 0 && { chunkUploads },
|
|
55
|
+
...chunkForce !== void 0 && { chunkForce },
|
|
56
|
+
...chunkSize !== void 0 && { chunkSize: parseFileSize(chunkSize) },
|
|
57
|
+
// FilePond truena con null: sin reintentos es una lista vacía
|
|
58
|
+
...chunkRetryDelays !== void 0 && { chunkRetryDelays: chunkRetryDelays ?? [] }
|
|
59
|
+
};
|
|
60
|
+
}
|
|
37
61
|
function sameFiles(firstFiles, secondFiles) {
|
|
38
62
|
return firstFiles.length === secondFiles.length && firstFiles.every((file, position) => file === secondFiles[position]);
|
|
39
63
|
}
|
|
@@ -102,6 +126,11 @@ function FileDropzoneContent(props) {
|
|
|
102
126
|
apiRef = null,
|
|
103
127
|
onProcessProgress,
|
|
104
128
|
onReject,
|
|
129
|
+
chunkUploads,
|
|
130
|
+
chunkForce,
|
|
131
|
+
chunkSize,
|
|
132
|
+
chunkRetryDelays,
|
|
133
|
+
pondProps = null,
|
|
105
134
|
actions,
|
|
106
135
|
children,
|
|
107
136
|
sx,
|
|
@@ -123,7 +152,10 @@ function FileDropzoneContent(props) {
|
|
|
123
152
|
const files = useMemo(() => filesOf(value, multiple), [value, multiple]);
|
|
124
153
|
const pondRef = useRef(null);
|
|
125
154
|
const latest = useRef({});
|
|
126
|
-
latest.current = { files, multiple, onChange, onReject, autoReject, validate, onProcessProgress, maxFiles, typeErrorLabel, totalErrorLabel, t };
|
|
155
|
+
latest.current = { files, multiple, onChange, onReject, autoReject, validate, onProcessProgress, maxFiles, typeErrorLabel, totalErrorLabel, t, pondProps };
|
|
156
|
+
const stableFunctions = useRef(/* @__PURE__ */ new Map());
|
|
157
|
+
const pondOptions = stablePondPropsOf(pondProps, stableFunctions, latest);
|
|
158
|
+
const pondAllows = (option) => !isLocked && (pondProps?.[option] ?? true);
|
|
127
159
|
useEffect(() => {
|
|
128
160
|
if (apiRef) {
|
|
129
161
|
apiRef.current = pondRef.current;
|
|
@@ -144,7 +176,7 @@ function FileDropzoneContent(props) {
|
|
|
144
176
|
emitChange(nextFile);
|
|
145
177
|
}
|
|
146
178
|
}, []);
|
|
147
|
-
const handleError = useCallback((error, item) => {
|
|
179
|
+
const handleError = useCallback((error, item, status) => {
|
|
148
180
|
syncFromPond();
|
|
149
181
|
const { onReject: reject, autoReject: shouldAutoReject, typeErrorLabel: typeLabel, totalErrorLabel: totalLabel } = latest.current;
|
|
150
182
|
let reason = FILE_REJECTION.size;
|
|
@@ -163,11 +195,13 @@ function FileDropzoneContent(props) {
|
|
|
163
195
|
if (reject && item?.file) {
|
|
164
196
|
reject(item.file, reason);
|
|
165
197
|
}
|
|
198
|
+
latest.current.pondProps?.onerror?.(error, item, status);
|
|
166
199
|
}, [syncFromPond]);
|
|
167
200
|
const handleBeforeAdd = useCallback(async (item) => {
|
|
168
|
-
const { validate: judge, onReject: reject, typeErrorLabel: fallbackMessage } = latest.current;
|
|
201
|
+
const { validate: judge, onReject: reject, typeErrorLabel: fallbackMessage, pondProps: projectOptions } = latest.current;
|
|
202
|
+
const projectAccepts = async () => projectOptions?.beforeAddFile ? await projectOptions.beforeAddFile(item) !== false : true;
|
|
169
203
|
if (!judge) {
|
|
170
|
-
return
|
|
204
|
+
return projectAccepts();
|
|
171
205
|
}
|
|
172
206
|
let verdict;
|
|
173
207
|
try {
|
|
@@ -176,7 +210,7 @@ function FileDropzoneContent(props) {
|
|
|
176
210
|
verdict = false;
|
|
177
211
|
}
|
|
178
212
|
if (verdict === true) {
|
|
179
|
-
return
|
|
213
|
+
return projectAccepts();
|
|
180
214
|
}
|
|
181
215
|
const message = typeof verdict === "string" ? verdict : fallbackMessage;
|
|
182
216
|
if (reject) {
|
|
@@ -186,16 +220,16 @@ function FileDropzoneContent(props) {
|
|
|
186
220
|
}
|
|
187
221
|
return false;
|
|
188
222
|
}, []);
|
|
189
|
-
const handleProcessed = useCallback(() => {
|
|
190
|
-
const report = latest.current
|
|
191
|
-
if (
|
|
192
|
-
|
|
223
|
+
const handleProcessed = useCallback((error, processedItem) => {
|
|
224
|
+
const { onProcessProgress: report, pondProps: projectOptions } = latest.current;
|
|
225
|
+
if (report) {
|
|
226
|
+
const pondItems = pondRef.current?.getFiles?.() ?? [];
|
|
227
|
+
report({ done: pondItems.filter((item) => item.status === FileStatus.PROCESSING_COMPLETE).length, total: pondItems.length });
|
|
193
228
|
}
|
|
194
|
-
|
|
195
|
-
report({ done: pondItems.filter((item) => item.status === FileStatus.PROCESSING_COMPLETE).length, total: pondItems.length });
|
|
229
|
+
projectOptions?.onprocessfile?.(error, processedItem);
|
|
196
230
|
}, []);
|
|
197
|
-
const handleWarning = useCallback((warning) => {
|
|
198
|
-
const { maxFiles: maxFileCount, onReject: reject, t: translate } = latest.current;
|
|
231
|
+
const handleWarning = useCallback((warning, file, status) => {
|
|
232
|
+
const { maxFiles: maxFileCount, onReject: reject, t: translate, pondProps: projectOptions } = latest.current;
|
|
199
233
|
if (maxFileCount && String(warning?.body ?? "").toLowerCase().includes(MAX_FILES_WARNING)) {
|
|
200
234
|
if (reject) {
|
|
201
235
|
reject(null, FILE_REJECTION.count);
|
|
@@ -203,7 +237,12 @@ function FileDropzoneContent(props) {
|
|
|
203
237
|
toastError(translate("upload.tooManyFiles", { count: maxFileCount }));
|
|
204
238
|
}
|
|
205
239
|
}
|
|
240
|
+
projectOptions?.onwarning?.(warning, file, status);
|
|
206
241
|
}, []);
|
|
242
|
+
const handleUpdateFiles = useCallback((items) => {
|
|
243
|
+
syncFromPond();
|
|
244
|
+
latest.current.pondProps?.onupdatefiles?.(items);
|
|
245
|
+
}, [syncFromPond]);
|
|
207
246
|
useEffect(() => {
|
|
208
247
|
const pondItems = pondRef.current?.getFiles?.() ?? [];
|
|
209
248
|
if (files.length === 0 && pondItems.length > 0) {
|
|
@@ -222,21 +261,23 @@ function FileDropzoneContent(props) {
|
|
|
222
261
|
/* @__PURE__ */ jsx(
|
|
223
262
|
FilePond,
|
|
224
263
|
{
|
|
264
|
+
...pondOptions,
|
|
265
|
+
...chunkOptionsOf({ chunkUploads, chunkForce, chunkSize, chunkRetryDelays }),
|
|
225
266
|
ref: pondRef,
|
|
226
267
|
name: pondName,
|
|
227
268
|
server,
|
|
228
269
|
instantUpload,
|
|
229
270
|
beforeAddFile: handleBeforeAdd,
|
|
230
271
|
onprocessfile: handleProcessed,
|
|
231
|
-
onupdatefiles:
|
|
272
|
+
onupdatefiles: handleUpdateFiles,
|
|
232
273
|
allowMultiple: multiple,
|
|
233
274
|
maxFiles: multiple ? maxFiles ?? null : 1,
|
|
234
275
|
disabled: isLocked,
|
|
235
|
-
allowDrop:
|
|
236
|
-
allowBrowse:
|
|
237
|
-
allowPaste:
|
|
238
|
-
allowRemove:
|
|
239
|
-
allowReplace:
|
|
276
|
+
allowDrop: pondAllows("allowDrop"),
|
|
277
|
+
allowBrowse: pondAllows("allowBrowse"),
|
|
278
|
+
allowPaste: pondAllows("allowPaste"),
|
|
279
|
+
allowRemove: pondAllows("allowRemove"),
|
|
280
|
+
allowReplace: pondAllows("allowReplace"),
|
|
240
281
|
credits: false,
|
|
241
282
|
acceptedFileTypes: acceptedTypes,
|
|
242
283
|
fileValidateTypeDetectType: detectFileType,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileDropzone.js","sources":["../../src/filepond/FileDropzone.jsx"],"sourcesContent":["// La zona para soltar archivos con FilePond por dentro (react-filepond): el MISMO uso que la de MUI (FileDropzone de la\n// puerta principal) más varios archivos, animaciones, el error dentro de cada archivo y, si se quiere, la subida de cada\n// archivo con su propia barra de avance (server). Cambiar de una a otra es cambiar el import.\n//\n// Solo ELIGE archivos (controlado: value / onChange). Enviarlos es de la pantalla: arma su FormData y los sube\n// (uploadWithProgress + uploadMachine). Huecos: actions (botones abajo) y children (lo que va debajo).\n//\n// Configuración, además de la de la zona de MUI (accept, maxSize, hint, buttonLabel, disabled, busy, busyModal, onReject):\n// multiple varios archivos: value / onChange hablan en ARREGLOS de File\n// maxFiles tope de archivos (solo con multiple)\n// maxTotalSize tope de lo que pesan todos juntos (solo con multiple), con unidades como maxSize\n// validate revisión del CONTENIDO al entrar: async (file) => true | 'mensaje de rechazo' (un .xml bien formado\n// puede no ser una factura); lo que no pasa no entra a la lista\n// server subir cada archivo por su cuenta con su barra (createPondServer, o un process propio)\n// instantUpload con server, sube al soltar (por omisión NO: regla de la casa, sube cuando la persona oprime el botón,\n// que llama apiRef.current.processFiles())\n// apiRef la API de FilePond para ese botón (processFiles, getFiles, removeFiles…). OJO: processFiles() regresa\n// una promesa que FALLA si algún archivo no subió; el porqué ya se ve en su tarjeta, pero hay que\n// atraparla (.catch) o queda un error suelto en la consola\n// onProcessProgress({done, total}) el «subidos 2 de 5» del modo server\n// autoReject (por omisión sí) lo que no es de un tipo permitido enseña su error un momento y se quita solo\n//\n// OJO, trampa pagada (react-filepond#198): react-filepond no siempre refresca sus funciones después de montar, y puede\n// quedarse para siempre con la del primer render: quitar el archivo no apagaba el botón porque se comparaba contra un\n// value viejo. Blindaje: las funciones que recibe FilePond no cambian nunca (useCallback sin dependencias) y todo lo que\n// necesitan de afuera lo leen de UN ref que cada render vuelve a apuntar (latest).\n//\n// OJO: FilePond va NO controlado. Pasarle los archivos como prop revive archivos recién quitados; la verdad es FilePond y\n// value es su espejo. Lo único que se manda de afuera hacia adentro es LIMPIAR (value vacío vacía la zona).\n//\n// Los colores salen del tema (claro y oscuro) con selectores que le ganan a la hoja de FilePond sin importar el orden.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaPondDropzone con defaultProps y\n// styleOverrides de sus partes: root, summary, limits, actions, extra.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useCallback, useEffect, useId, useMemo, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport {FileStatus} from 'filepond';\nimport FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size';\nimport FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';\nimport {FilePond, registerPlugin} from 'react-filepond';\n// la hoja de FilePond la carga el empaquetador del proyecto junto con la zona (en los tipos se quita: ver\n// scripts/stripStyleImportsFromTypes.js)\nimport 'filepond/dist/filepond.min.css';\n\nimport ModalWaiting from '@/feedback/ModalWaiting.jsx';\nimport {toastError} from '@/feedback/toast.jsx';\nimport {accentTextStylesOf} from '@/theme/glassSurface.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {FILE_REJECTION, parseFileSize, resolveAccept} from '@/upload/fileRules.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaPondDropzone)\nconst THEME_NAME = 'CalcitaPondDropzone';\n// la raíz: el estilo de FilePond de Calcita abajo, el tema del proyecto encima y el sx de la pantalla al final\nconst PondRoot = piecePart(THEME_NAME, 'Root', Box);\nconst PondSummary = piecePart(THEME_NAME, 'Summary', Typography);\nconst PondLimits = piecePart(THEME_NAME, 'Limits', Typography);\nconst PondActions = piecePart(THEME_NAME, 'Actions', Stack);\nconst PondExtra = piecePart(THEME_NAME, 'Extra', Box);\n\nregisterPlugin(FilePondPluginFileValidateType, FilePondPluginFileValidateSize);\n\n// cuánto se ve el error de un archivo de tipo no permitido antes de quitarse solo: lo suficiente para leer por qué\nconst AUTO_REJECT_DELAY_MILLISECONDS = 1800;\n// el aviso de FilePond cuando se sueltan más archivos de los permitidos\nconst MAX_FILES_WARNING = 'max files';\n\n/**\n * El tipo de un archivo para el complemento de tipos: el paquete habla de EXTENSIONES (.xlsx) y FilePond, por omisión,\n * del tipo MIME. Imagen, audio y video van por su tipo (para que 'image/*' funcione); lo demás, por su extensión.\n *\n * @param {File} file\n * @returns {Promise<string>}\n */\nfunction detectFileType(file) {\n const mimeType = (file.type ?? '').toLowerCase();\n if (/^(image|audio|video)\\//.test(mimeType)) {\n return Promise.resolve(mimeType);\n }\n const dotPosition = file.name.lastIndexOf('.');\n return Promise.resolve(dotPosition >= 0 ? file.name.slice(dotPosition).toLowerCase() : mimeType);\n}\n\n/**\n * ¿Los mismos archivos en el mismo orden?\n *\n * @param {File[]} firstFiles\n * @param {File[]} secondFiles\n * @returns {boolean}\n */\nfunction sameFiles(firstFiles, secondFiles) {\n return firstFiles.length === secondFiles.length && firstFiles.every((file, position) => file === secondFiles[position]);\n}\n\n/**\n * value (File | File[] | null) como arreglo.\n *\n * @param {File|File[]|null} value\n * @param {boolean} multiple\n * @returns {File[]}\n */\nfunction filesOf(value, multiple) {\n if (multiple) {\n return Array.isArray(value) ? value : (value ? [value] : []);\n }\n return value ? [value] : [];\n}\n\n/**\n * Los textos que FilePond pinta dentro de cada tarjeta (vienen en inglés), desde los textos del paquete.\n *\n * El de «no se pudo subir» enseña el mensaje del servidor cuando lo hay (createPondServer manda el título del problema\n * RFC 9457): «No se pudo subir» a secas no le dice a la persona qué corregir.\n *\n * @param {(key: string) => string} t traductor de common\n * @returns {object} props de FilePond\n */\nfunction pondLabelsOf(t) {\n return {\n labelFileLoading: t('upload.pond.loading'),\n labelFileLoadError: t('upload.pond.loadError'),\n labelFileWaitingForSize: t('upload.pond.waitingForSize'),\n labelFileSizeNotAvailable: t('upload.pond.sizeNotAvailable'),\n labelInvalidField: t('upload.pond.invalidField'),\n labelFileProcessing: t('upload.pond.processing'),\n labelFileProcessingComplete: t('upload.pond.processingComplete'),\n labelFileProcessingAborted: t('upload.pond.processingAborted'),\n labelFileProcessingError: (error) => error?.body || t('upload.pond.processingError'),\n labelFileProcessingRevertError: t('upload.pond.revertError'),\n labelFileRemoveError: t('upload.pond.removeError'),\n labelTapToCancel: t('upload.pond.tapToCancel'),\n labelTapToRetry: t('upload.pond.tapToRetry'),\n labelTapToUndo: t('upload.pond.tapToUndo'),\n labelButtonRemoveItem: t('upload.pond.remove'),\n labelButtonAbortItemLoad: t('upload.pond.abortLoad'),\n labelButtonRetryItemLoad: t('upload.pond.retryLoad'),\n labelButtonAbortItemProcessing: t('upload.pond.abortProcessing'),\n labelButtonUndoItemProcessing: t('upload.pond.undoProcessing'),\n labelButtonRetryItemProcessing: t('upload.pond.retryProcessing'),\n labelButtonProcessItem: t('upload.pond.processItem'),\n };\n}\n\n/**\n * Los colores de FilePond desde el tema. El «& » los anida dentro de la caja: pesan más que los de la hoja de FilePond\n * aunque esa hoja cargue después.\n *\n * @param {object} theme tema de MUI (con o sin variables CSS)\n * @returns {object}\n */\nfunction pondThemeSxOf(theme) {\n const {palette} = theme.vars ?? theme;\n return {\n '& .filepond--root': {fontFamily: theme.typography.fontFamily, mb: 0},\n '& .filepond--panel-root': {bgcolor: palette.background.paper, border: `2px dashed ${palette.divider}`, borderRadius: 2},\n '& .filepond--drop-label': {color: palette.text.primary},\n // la liga «Elegir archivos»: del color del tema pero legible (accentTextStylesOf); el naranja tal cual se perdía en oscuro\n '& .filepond--label-action': {...accentTextStylesOf(theme), textDecorationColor: 'currentColor'},\n '& .filepond--item-panel': {bgcolor: palette.primary.main},\n '& .filepond--file, & .filepond--file-action-button': {color: palette.primary.contrastText},\n };\n}\n\n/**\n * @param {object} props\n * @param {File|File[]|null} [props.value]\n * @param {(value: File|File[]|null) => void} props.onChange\n * @param {string|string[]} [props.accept]\n * @param {number|string} [props.maxSize] por archivo\n * @param {boolean} [props.multiple]\n * @param {number} [props.maxFiles]\n * @param {number|string} [props.maxTotalSize]\n * @param {string} [props.name] nombre del campo; por omisión uno propio por zona (dos zonas no se pisan)\n * @param {string} [props.hint]\n * @param {string} [props.buttonLabel]\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.busy]\n * @param {boolean|{title?: string, text?: string}} [props.busyModal]\n * @param {boolean} [props.autoReject]\n * @param {((file: File) => Promise<true|string>)|null} [props.validate]\n * @param {object|null} [props.server] createPondServer(...) o {process} propio\n * @param {boolean} [props.instantUpload]\n * @param {{current: object|null}|null} [props.apiRef]\n * @param {(progress: {done: number, total: number}) => void} [props.onProcessProgress]\n * @param {(file: File|null, reason: string, message?: string) => void} [props.onReject] reason de FILE_REJECTION\n * @param {import('react').ReactNode} [props.actions]\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction FileDropzoneContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaPondDropzone.defaultProps) y encima lo que llega aquí\n const {\n value = null, onChange, accept, maxSize, multiple = false, maxFiles, maxTotalSize, name, hint, buttonLabel, disabled = false,\n busy = false, busyModal = false, autoReject = true, validate = null, server = null, instantUpload = false, apiRef = null,\n onProcessProgress, onReject, actions, children, sx, texts,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const {formatFileSize} = useFormatters();\n const generatedId = useId();\n const pondName = name ?? `file-dropzone-${generatedId}`;\n // el candado: apagado o subiendo. Va también en cada allow* de FilePond, no solo en lo visual\n const isLocked = disabled || busy;\n\n const resolvedAccept = useMemo(() => resolveAccept(accept), [accept]);\n const acceptedTypes = useMemo(() => (resolvedAccept ? resolvedAccept.split(',') : null), [resolvedAccept]);\n const maxBytes = parseFileSize(maxSize);\n const maxTotalBytes = multiple ? parseFileSize(maxTotalSize) : null;\n const typeErrorLabel = t('upload.invalidTitle');\n const totalErrorLabel = t('upload.totalTooLargeShort');\n const files = useMemo(() => filesOf(value, multiple), [value, multiple]);\n\n const pondRef = useRef(null);\n // lo que las funciones fijas necesitan de afuera, siempre al día (ver la trampa del encabezado)\n const latest = useRef({});\n latest.current = {files, multiple, onChange, onReject, autoReject, validate, onProcessProgress, maxFiles, typeErrorLabel, totalErrorLabel, t};\n\n useEffect(() => {\n if (apiRef) {\n apiRef.current = pondRef.current;\n }\n });\n\n // value desde FilePond. Los archivos con error se quedan en pantalla (en rojo) pero no cuentan: con un archivo malo\n // el botón de enviar no se prende. Y un removeFiles() hecho desde aquí no debe avisar un cambio fantasma\n const syncFromPond = useCallback(() => {\n const {files: currentFiles, multiple: isMultiple, onChange: emitChange} = latest.current;\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n const nextFiles = pondItems.filter((item) => item.status !== FileStatus.LOAD_ERROR).map((item) => item.file);\n if (isMultiple) {\n if (!sameFiles(nextFiles, currentFiles)) {\n emitChange(nextFiles);\n }\n return;\n }\n const nextFile = nextFiles[0] ?? null;\n if (nextFile !== (currentFiles[0] ?? null)) {\n emitChange(nextFile);\n }\n }, []);\n\n const handleError = useCallback((error, item) => {\n // el archivo acaba de quedar con error: se saca de value\n syncFromPond();\n const {onReject: reject, autoReject: shouldAutoReject, typeErrorLabel: typeLabel, totalErrorLabel: totalLabel} = latest.current;\n // el motivo se reconoce por el texto, que es nuestro: comparación estable\n let reason = FILE_REJECTION.size;\n if (error?.main === typeLabel) {\n reason = FILE_REJECTION.type;\n } else if (error?.main === totalLabel) {\n reason = FILE_REJECTION.total;\n }\n // el de tipo no permitido se va solo después de que se lea su error; el de peso se queda para saber cuál pesa de más\n if (reason === FILE_REJECTION.type && shouldAutoReject && item?.id) {\n setTimeout(() => {\n if (pondRef.current?.getFile?.(item.id)) {\n pondRef.current.removeFile(item.id);\n }\n }, AUTO_REJECT_DELAY_MILLISECONDS);\n }\n if (reject && item?.file) {\n reject(item.file, reason);\n }\n }, [syncFromPond]);\n\n // la revisión del contenido, ANTES de que el archivo entre a la lista\n const handleBeforeAdd = useCallback(async (item) => {\n const {validate: judge, onReject: reject, typeErrorLabel: fallbackMessage} = latest.current;\n if (!judge) {\n return true;\n }\n let verdict;\n try {\n verdict = await judge(item.file);\n } catch {\n verdict = false;\n }\n if (verdict === true) {\n return true;\n }\n const message = typeof verdict === 'string' ? verdict : fallbackMessage;\n if (reject) {\n reject(item.file, FILE_REJECTION.content, message);\n } else {\n toastError(`${item.file?.name}: ${message}`);\n }\n return false;\n }, []);\n\n // el «subidos X de N» del modo server: cada archivo que termina su subida avisa la cuenta\n const handleProcessed = useCallback(() => {\n const report = latest.current.onProcessProgress;\n if (!report) {\n return;\n }\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n report({done: pondItems.filter((item) => item.status === FileStatus.PROCESSING_COMPLETE).length, total: pondItems.length});\n }, []);\n\n // lo que pasa de maxFiles ni siquiera entra (FilePond corta lo soltado y solo avisa): el aviso lo damos nosotros\n const handleWarning = useCallback((warning) => {\n const {maxFiles: maxFileCount, onReject: reject, t: translate} = latest.current;\n if (maxFileCount && String(warning?.body ?? '').toLowerCase().includes(MAX_FILES_WARNING)) {\n if (reject) {\n reject(null, FILE_REJECTION.count);\n } else {\n toastError(translate('upload.tooManyFiles', {count: maxFileCount}));\n }\n }\n }, []);\n\n // limpiar desde afuera: value vacío (p. ej. después de subir bien) vacía la zona\n useEffect(() => {\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n if (files.length === 0 && pondItems.length > 0) {\n pondRef.current.removeFiles();\n }\n }, [files]);\n\n const limitHints = [\n maxBytes && t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}),\n maxTotalBytes && t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}),\n multiple && maxFiles && t('upload.maxFilesHint', {count: maxFiles}),\n ].filter(Boolean);\n // con varios archivos se dice cuántos van y cuánto pesan juntos: nadie suma tarjeta por tarjeta\n const totalBytes = multiple ? files.reduce((sum, file) => sum + (file?.size ?? 0), 0) : 0;\n const hintText = hint ?? (multiple ? t('upload.hintMultiple') : t('upload.hint'));\n const chooseText = buttonLabel ?? (multiple ? t('upload.chooseMultiple') : t('upload.choose'));\n\n return (\n <PondRoot ownSx={(theme) => pondThemeSxOf(theme)} sx={sx}>\n <FilePond\n ref={pondRef}\n name={pondName}\n server={server}\n instantUpload={instantUpload}\n beforeAddFile={handleBeforeAdd}\n onprocessfile={handleProcessed}\n onupdatefiles={syncFromPond}\n allowMultiple={multiple}\n maxFiles={multiple ? (maxFiles ?? null) : 1}\n disabled={isLocked}\n allowDrop={!isLocked}\n allowBrowse={!isLocked}\n allowPaste={!isLocked}\n allowRemove={!isLocked}\n allowReplace={!isLocked}\n credits={false}\n acceptedFileTypes={acceptedTypes}\n fileValidateTypeDetectType={detectFileType}\n fileValidateTypeLabelExpectedTypes={resolvedAccept ?? ''}\n maxFileSize={maxBytes ? String(maxBytes) : null}\n maxTotalFileSize={maxTotalBytes ? String(maxTotalBytes) : null}\n labelIdle={`${hintText} — <span class=\"filepond--label-action\">${chooseText}</span>`}\n labelFileTypeNotAllowed={typeErrorLabel}\n labelMaxFileSizeExceeded={t('upload.tooLargeShort')}\n labelMaxFileSize={maxBytes ? t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}) : ''}\n labelMaxTotalFileSizeExceeded={totalErrorLabel}\n labelMaxTotalFileSize={maxTotalBytes ? t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}) : ''}\n onwarning={handleWarning}\n onerror={handleError}\n {...pondLabelsOf(t)}\n />\n {multiple && files.length > 0 && (\n <PondSummary variant=\"body2\" ownSx={{textAlign: 'center', mt: 1, fontWeight: 500}}>\n {maxTotalBytes\n ? t('upload.summaryOf', {count: files.length, total: formatFileSize(totalBytes), max: formatFileSize(maxTotalBytes)})\n : t('upload.summary', {count: files.length, total: formatFileSize(totalBytes)})}\n </PondSummary>\n )}\n {limitHints.length > 0 && (\n <PondLimits variant=\"caption\" color=\"text.secondary\" ownSx={{display: 'block', textAlign: 'center', mt: 0.5}}>\n {limitHints.join(' · ')}\n </PondLimits>\n )}\n {actions && (\n <PondActions direction={{xs: 'column', sm: 'row'}} spacing={2} ownSx={{justifyContent: 'center', alignItems: {xs: 'stretch', sm: 'center'}, mt: 2}}>\n {actions}\n </PondActions>\n )}\n {children && <PondExtra ownSx={{mt: 3}}>{children}</PondExtra>}\n {busyModal && <ModalWaiting open={busy} title={busyModal.title} text={busyModal.text} />}\n </PondRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FileDropzone = withPieceSlots(FileDropzoneContent, THEME_NAME);\nexport default FileDropzone;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AA0DA,MAAM,aAAa;AAEnB,MAAM,WAAW,UAAU,YAAY,QAAQ,GAAG;AAClD,MAAM,cAAc,UAAU,YAAY,WAAW,UAAU;AAC/D,MAAM,aAAa,UAAU,YAAY,UAAU,UAAU;AAC7D,MAAM,cAAc,UAAU,YAAY,WAAW,KAAK;AAC1D,MAAM,YAAY,UAAU,YAAY,SAAS,GAAG;AAEpD,eAAe,gCAAgC,8BAA8B;AAG7E,MAAM,iCAAiC;AAEvC,MAAM,oBAAoB;AAS1B,SAAS,eAAe,MAAM;AAC1B,QAAM,YAAY,KAAK,QAAQ,IAAI,YAAA;AACnC,MAAI,yBAAyB,KAAK,QAAQ,GAAG;AACzC,WAAO,QAAQ,QAAQ,QAAQ;AAAA,EACnC;AACA,QAAM,cAAc,KAAK,KAAK,YAAY,GAAG;AAC7C,SAAO,QAAQ,QAAQ,eAAe,IAAI,KAAK,KAAK,MAAM,WAAW,EAAE,YAAA,IAAgB,QAAQ;AACnG;AASA,SAAS,UAAU,YAAY,aAAa;AACxC,SAAO,WAAW,WAAW,YAAY,UAAU,WAAW,MAAM,CAAC,MAAM,aAAa,SAAS,YAAY,QAAQ,CAAC;AAC1H;AASA,SAAS,QAAQ,OAAO,UAAU;AAC9B,MAAI,UAAU;AACV,WAAO,MAAM,QAAQ,KAAK,IAAI,QAAS,QAAQ,CAAC,KAAK,IAAI,CAAA;AAAA,EAC7D;AACA,SAAO,QAAQ,CAAC,KAAK,IAAI,CAAA;AAC7B;AAWA,SAAS,aAAa,GAAG;AACrB,SAAO;AAAA,IACH,kBAAkB,EAAE,qBAAqB;AAAA,IACzC,oBAAoB,EAAE,uBAAuB;AAAA,IAC7C,yBAAyB,EAAE,4BAA4B;AAAA,IACvD,2BAA2B,EAAE,8BAA8B;AAAA,IAC3D,mBAAmB,EAAE,0BAA0B;AAAA,IAC/C,qBAAqB,EAAE,wBAAwB;AAAA,IAC/C,6BAA6B,EAAE,gCAAgC;AAAA,IAC/D,4BAA4B,EAAE,+BAA+B;AAAA,IAC7D,0BAA0B,CAAC,UAAU,OAAO,QAAQ,EAAE,6BAA6B;AAAA,IACnF,gCAAgC,EAAE,yBAAyB;AAAA,IAC3D,sBAAsB,EAAE,yBAAyB;AAAA,IACjD,kBAAkB,EAAE,yBAAyB;AAAA,IAC7C,iBAAiB,EAAE,wBAAwB;AAAA,IAC3C,gBAAgB,EAAE,uBAAuB;AAAA,IACzC,uBAAuB,EAAE,oBAAoB;AAAA,IAC7C,0BAA0B,EAAE,uBAAuB;AAAA,IACnD,0BAA0B,EAAE,uBAAuB;AAAA,IACnD,gCAAgC,EAAE,6BAA6B;AAAA,IAC/D,+BAA+B,EAAE,4BAA4B;AAAA,IAC7D,gCAAgC,EAAE,6BAA6B;AAAA,IAC/D,wBAAwB,EAAE,yBAAyB;AAAA,EAAA;AAE3D;AASA,SAAS,cAAc,OAAO;AAC1B,QAAM,EAAC,QAAA,IAAW,MAAM,QAAQ;AAChC,SAAO;AAAA,IACH,qBAAqB,EAAC,YAAY,MAAM,WAAW,YAAY,IAAI,EAAA;AAAA,IACnE,2BAA2B,EAAC,SAAS,QAAQ,WAAW,OAAO,QAAQ,cAAc,QAAQ,OAAO,IAAI,cAAc,EAAA;AAAA,IACtH,2BAA2B,EAAC,OAAO,QAAQ,KAAK,QAAA;AAAA;AAAA,IAEhD,6BAA6B,EAAC,GAAG,mBAAmB,KAAK,GAAG,qBAAqB,eAAA;AAAA,IACjF,2BAA2B,EAAC,SAAS,QAAQ,QAAQ,KAAA;AAAA,IACrD,sDAAsD,EAAC,OAAO,QAAQ,QAAQ,aAAA;AAAA,EAAY;AAElG;AA6BA,SAAS,oBAAoB,OAAO;AAEhC,QAAM;AAAA,IACN,QAAQ;AAAA,IAAM;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAS,WAAW;AAAA,IAAO;AAAA,IAAU;AAAA,IAAc;AAAA,IAAM;AAAA,IAAM;AAAA,IAAa,WAAW;AAAA,IACvH,OAAO;AAAA,IAAO,YAAY;AAAA,IAAO,aAAa;AAAA,IAAM,WAAW;AAAA,IAAM,SAAS;AAAA,IAAM,gBAAgB;AAAA,IAAO,SAAS;AAAA,IACpH;AAAA,IAAmB;AAAA,IAAU;AAAA,IAAS;AAAA,IAAU;AAAA,IAAI;AAAA,IACpD,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AAC/B,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,eAAA,IAAkB,cAAA;AACzB,QAAM,cAAc,MAAA;AACpB,QAAM,WAAW,QAAQ,iBAAiB,WAAW;AAErD,QAAM,WAAW,YAAY;AAE7B,QAAM,iBAAiB,QAAQ,MAAM,cAAc,MAAM,GAAG,CAAC,MAAM,CAAC;AACpE,QAAM,gBAAgB,QAAQ,MAAO,iBAAiB,eAAe,MAAM,GAAG,IAAI,MAAO,CAAC,cAAc,CAAC;AACzG,QAAM,WAAW,cAAc,OAAO;AACtC,QAAM,gBAAgB,WAAW,cAAc,YAAY,IAAI;AAC/D,QAAM,iBAAiB,EAAE,qBAAqB;AAC9C,QAAM,kBAAkB,EAAE,2BAA2B;AACrD,QAAM,QAAQ,QAAQ,MAAM,QAAQ,OAAO,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEvE,QAAM,UAAU,OAAO,IAAI;AAE3B,QAAM,SAAS,OAAO,EAAE;AACxB,SAAO,UAAU,EAAC,OAAO,UAAU,UAAU,UAAU,YAAY,UAAU,mBAAmB,UAAU,gBAAgB,iBAAiB,EAAA;AAE3I,YAAU,MAAM;AACZ,QAAI,QAAQ;AACR,aAAO,UAAU,QAAQ;AAAA,IAC7B;AAAA,EACJ,CAAC;AAID,QAAM,eAAe,YAAY,MAAM;AACnC,UAAM,EAAC,OAAO,cAAc,UAAU,YAAY,UAAU,eAAc,OAAO;AACjF,UAAM,YAAY,QAAQ,SAAS,WAAA,KAAgB,CAAA;AACnD,UAAM,YAAY,UAAU,OAAO,CAAC,SAAS,KAAK,WAAW,WAAW,UAAU,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;AAC3G,QAAI,YAAY;AACZ,UAAI,CAAC,UAAU,WAAW,YAAY,GAAG;AACrC,mBAAW,SAAS;AAAA,MACxB;AACA;AAAA,IACJ;AACA,UAAM,WAAW,UAAU,CAAC,KAAK;AACjC,QAAI,cAAc,aAAa,CAAC,KAAK,OAAO;AACxC,iBAAW,QAAQ;AAAA,IACvB;AAAA,EACJ,GAAG,CAAA,CAAE;AAEL,QAAM,cAAc,YAAY,CAAC,OAAO,SAAS;AAE7C,iBAAA;AACA,UAAM,EAAC,UAAU,QAAQ,YAAY,kBAAkB,gBAAgB,WAAW,iBAAiB,WAAA,IAAc,OAAO;AAExH,QAAI,SAAS,eAAe;AAC5B,QAAI,OAAO,SAAS,WAAW;AAC3B,eAAS,eAAe;AAAA,IAC5B,WAAW,OAAO,SAAS,YAAY;AACnC,eAAS,eAAe;AAAA,IAC5B;AAEA,QAAI,WAAW,eAAe,QAAQ,oBAAoB,MAAM,IAAI;AAChE,iBAAW,MAAM;AACb,YAAI,QAAQ,SAAS,UAAU,KAAK,EAAE,GAAG;AACrC,kBAAQ,QAAQ,WAAW,KAAK,EAAE;AAAA,QACtC;AAAA,MACJ,GAAG,8BAA8B;AAAA,IACrC;AACA,QAAI,UAAU,MAAM,MAAM;AACtB,aAAO,KAAK,MAAM,MAAM;AAAA,IAC5B;AAAA,EACJ,GAAG,CAAC,YAAY,CAAC;AAGjB,QAAM,kBAAkB,YAAY,OAAO,SAAS;AAChD,UAAM,EAAC,UAAU,OAAO,UAAU,QAAQ,gBAAgB,oBAAmB,OAAO;AACpF,QAAI,CAAC,OAAO;AACR,aAAO;AAAA,IACX;AACA,QAAI;AACJ,QAAI;AACA,gBAAU,MAAM,MAAM,KAAK,IAAI;AAAA,IACnC,QAAQ;AACJ,gBAAU;AAAA,IACd;AACA,QAAI,YAAY,MAAM;AAClB,aAAO;AAAA,IACX;AACA,UAAM,UAAU,OAAO,YAAY,WAAW,UAAU;AACxD,QAAI,QAAQ;AACR,aAAO,KAAK,MAAM,eAAe,SAAS,OAAO;AAAA,IACrD,OAAO;AACH,iBAAW,GAAG,KAAK,MAAM,IAAI,KAAK,OAAO,EAAE;AAAA,IAC/C;AACA,WAAO;AAAA,EACX,GAAG,CAAA,CAAE;AAGL,QAAM,kBAAkB,YAAY,MAAM;AACtC,UAAM,SAAS,OAAO,QAAQ;AAC9B,QAAI,CAAC,QAAQ;AACT;AAAA,IACJ;AACA,UAAM,YAAY,QAAQ,SAAS,WAAA,KAAgB,CAAA;AACnD,WAAO,EAAC,MAAM,UAAU,OAAO,CAAC,SAAS,KAAK,WAAW,WAAW,mBAAmB,EAAE,QAAQ,OAAO,UAAU,QAAO;AAAA,EAC7H,GAAG,CAAA,CAAE;AAGL,QAAM,gBAAgB,YAAY,CAAC,YAAY;AAC3C,UAAM,EAAC,UAAU,cAAc,UAAU,QAAQ,GAAG,cAAa,OAAO;AACxE,QAAI,gBAAgB,OAAO,SAAS,QAAQ,EAAE,EAAE,YAAA,EAAc,SAAS,iBAAiB,GAAG;AACvF,UAAI,QAAQ;AACR,eAAO,MAAM,eAAe,KAAK;AAAA,MACrC,OAAO;AACH,mBAAW,UAAU,uBAAuB,EAAC,OAAO,aAAA,CAAa,CAAC;AAAA,MACtE;AAAA,IACJ;AAAA,EACJ,GAAG,CAAA,CAAE;AAGL,YAAU,MAAM;AACZ,UAAM,YAAY,QAAQ,SAAS,WAAA,KAAgB,CAAA;AACnD,QAAI,MAAM,WAAW,KAAK,UAAU,SAAS,GAAG;AAC5C,cAAQ,QAAQ,YAAA;AAAA,IACpB;AAAA,EACJ,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,aAAa;AAAA,IACf,YAAY,EAAE,sBAAsB,EAAC,KAAK,eAAe,QAAQ,GAAE;AAAA,IACnE,iBAAiB,EAAE,2BAA2B,EAAC,KAAK,eAAe,aAAa,GAAE;AAAA,IAClF,YAAY,YAAY,EAAE,uBAAuB,EAAC,OAAO,UAAS;AAAA,EAAA,EACpE,OAAO,OAAO;AAEhB,QAAM,aAAa,WAAW,MAAM,OAAO,CAAC,KAAK,SAAS,OAAO,MAAM,QAAQ,IAAI,CAAC,IAAI;AACxF,QAAM,WAAW,SAAS,WAAW,EAAE,qBAAqB,IAAI,EAAE,aAAa;AAC/E,QAAM,aAAa,gBAAgB,WAAW,EAAE,uBAAuB,IAAI,EAAE,eAAe;AAE5F,SACI,qBAAC,YAAS,OAAO,CAAC,UAAU,cAAc,KAAK,GAAG,IAC9C,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,KAAK;AAAA,QACL,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,eAAe;AAAA,QACf,eAAe;AAAA,QACf,eAAe;AAAA,QACf,eAAe;AAAA,QACf,UAAU,WAAY,YAAY,OAAQ;AAAA,QAC1C,UAAU;AAAA,QACV,WAAW,CAAC;AAAA,QACZ,aAAa,CAAC;AAAA,QACd,YAAY,CAAC;AAAA,QACb,aAAa,CAAC;AAAA,QACd,cAAc,CAAC;AAAA,QACf,SAAS;AAAA,QACT,mBAAmB;AAAA,QACnB,4BAA4B;AAAA,QAC5B,oCAAoC,kBAAkB;AAAA,QACtD,aAAa,WAAW,OAAO,QAAQ,IAAI;AAAA,QAC3C,kBAAkB,gBAAgB,OAAO,aAAa,IAAI;AAAA,QAC1D,WAAW,GAAG,QAAQ,2CAA2C,UAAU;AAAA,QAC3E,yBAAyB;AAAA,QACzB,0BAA0B,EAAE,sBAAsB;AAAA,QAClD,kBAAkB,WAAW,EAAE,sBAAsB,EAAC,KAAK,eAAe,QAAQ,EAAA,CAAE,IAAI;AAAA,QACxF,+BAA+B;AAAA,QAC/B,uBAAuB,gBAAgB,EAAE,2BAA2B,EAAC,KAAK,eAAe,aAAa,EAAA,CAAE,IAAI;AAAA,QAC5G,WAAW;AAAA,QACX,SAAS;AAAA,QACR,GAAG,aAAa,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAErB,YAAY,MAAM,SAAS,yBACvB,aAAA,EAAY,SAAQ,SAAQ,OAAO,EAAC,WAAW,UAAU,IAAI,GAAG,YAAY,IAAA,GACxE,UAAA,gBACK,EAAE,oBAAoB,EAAC,OAAO,MAAM,QAAQ,OAAO,eAAe,UAAU,GAAG,KAAK,eAAe,aAAa,GAAE,IAClH,EAAE,kBAAkB,EAAC,OAAO,MAAM,QAAQ,OAAO,eAAe,UAAU,EAAA,CAAE,GACtF;AAAA,IAEH,WAAW,SAAS,KACjB,oBAAC,cAAW,SAAQ,WAAU,OAAM,kBAAiB,OAAO,EAAC,SAAS,SAAS,WAAW,UAAU,IAAI,OACnG,UAAA,WAAW,KAAK,KAAK,GAC1B;AAAA,IAEH,WACG,oBAAC,aAAA,EAAY,WAAW,EAAC,IAAI,UAAU,IAAI,MAAA,GAAQ,SAAS,GAAG,OAAO,EAAC,gBAAgB,UAAU,YAAY,EAAC,IAAI,WAAW,IAAI,SAAA,GAAW,IAAI,EAAA,GAC3I,UAAA,QAAA,CACL;AAAA,IAEH,gCAAa,WAAA,EAAU,OAAO,EAAC,IAAI,KAAK,UAAS;AAAA,IACjD,aAAa,oBAAC,cAAA,EAAa,MAAM,MAAM,OAAO,UAAU,OAAO,MAAM,UAAU,KAAA,CAAM;AAAA,EAAA,GAC1F;AAER;AAGA,MAAM,eAAe,eAAe,qBAAqB,UAAU;"}
|
|
1
|
+
{"version":3,"file":"FileDropzone.js","sources":["../../src/filepond/FileDropzone.jsx"],"sourcesContent":["// La zona para soltar archivos con FilePond por dentro (react-filepond): el MISMO uso que la de MUI (FileDropzone de la\n// puerta principal) más varios archivos, animaciones, el error dentro de cada archivo y, si se quiere, la subida de cada\n// archivo con su propia barra de avance (server). Cambiar de una a otra es cambiar el import.\n//\n// Solo ELIGE archivos (controlado: value / onChange). Enviarlos es de la pantalla: arma su FormData y los sube\n// (uploadWithProgress + uploadMachine). Huecos: actions (botones abajo) y children (lo que va debajo).\n//\n// Configuración, además de la de la zona de MUI (accept, maxSize, hint, buttonLabel, disabled, busy, busyModal, onReject):\n// multiple varios archivos: value / onChange hablan en ARREGLOS de File\n// maxFiles tope de archivos (solo con multiple)\n// maxTotalSize tope de lo que pesan todos juntos (solo con multiple), con unidades como maxSize\n// validate revisión del CONTENIDO al entrar: async (file) => true | 'mensaje de rechazo' (un .xml bien formado\n// puede no ser una factura); lo que no pasa no entra a la lista\n// server subir cada archivo por su cuenta con su barra (createPondServer, o un process propio)\n// instantUpload con server, sube al soltar (por omisión NO: regla de la casa, sube cuando la persona oprime el botón,\n// que llama apiRef.current.processFiles())\n// apiRef la API de FilePond para ese botón (processFiles, getFiles, removeFiles…). OJO: processFiles() regresa\n// una promesa que FALLA si algún archivo no subió; el porqué ya se ve en su tarjeta, pero hay que\n// atraparla (.catch) o queda un error suelto en la consola\n// onProcessProgress({done, total}) el «subidos 2 de 5» del modo server\n// autoReject (por omisión sí) lo que no es de un tipo permitido enseña su error un momento y se quita solo\n//\n// Por trozos, con los nombres de FilePond (así se copia tal cual lo de un sistema que ya subía por trozos):\n// chunkUploads sube por trozos lo que pase de chunkSize (con createPondServer, o con las direcciones de FilePond)\n// chunkForce por trozos TODO, también lo chico (hay servidores que solo reciben por trozos)\n// chunkSize el tamaño de cada trozo, en bytes o con unidades como maxSize ('1 MB'); por omisión el de FilePond, 5 MB\n// chunkRetryDelays las esperas entre reintentos de un trozo, en milisegundos; null o [] = sin reintentos\n// Con createPondServer la barra es honesta: cuenta lo que el servidor ya confirmó y no dice 100 % antes de tiempo\n// (ver chunkedUpload.js).\n//\n// pondProps: cualquier otra opción de FilePond, tal cual (maxParallelUploads, onaddfile, onprocessfiles…). Lo que la zona\n// ya maneja gana: name, server, files, los tipos y pesos, los textos y el candado (disabled y busy apagan soltar, elegir,\n// pegar y quitar aunque pondProps los prenda). Los eventos que la zona ya usa (onupdatefiles, onprocessfile, onwarning,\n// onerror, beforeAddFile) corren después de los suyos; beforeAddFile del proyecto revisa lo que ya pasó validate.\n//\n// OJO, trampa pagada (react-filepond#198): react-filepond no siempre refresca sus funciones después de montar, y puede\n// quedarse para siempre con la del primer render: quitar el archivo no apagaba el botón porque se comparaba contra un\n// value viejo. Blindaje: las funciones que recibe FilePond no cambian nunca (useCallback sin dependencias) y todo lo que\n// necesitan de afuera lo leen de UN ref que cada render vuelve a apuntar (latest).\n//\n// OJO: FilePond va NO controlado. Pasarle los archivos como prop revive archivos recién quitados; la verdad es FilePond y\n// value es su espejo. Lo único que se manda de afuera hacia adentro es LIMPIAR (value vacío vacía la zona).\n//\n// Los colores salen del tema (claro y oscuro) con selectores que le ganan a la hoja de FilePond sin importar el orden.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaPondDropzone con defaultProps y\n// styleOverrides de sus partes: root, summary, limits, actions, extra.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useCallback, useEffect, useId, useMemo, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport {FileStatus} from 'filepond';\nimport FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size';\nimport FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';\nimport {FilePond, registerPlugin} from 'react-filepond';\n// la hoja de FilePond la carga el empaquetador del proyecto junto con la zona (en los tipos se quita: ver\n// scripts/stripStyleImportsFromTypes.js)\nimport 'filepond/dist/filepond.min.css';\n\nimport ModalWaiting from '@/feedback/ModalWaiting.jsx';\nimport {toastError} from '@/feedback/toast.jsx';\nimport {accentTextStylesOf} from '@/theme/glassSurface.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {FILE_REJECTION, parseFileSize, resolveAccept} from '@/upload/fileRules.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaPondDropzone)\nconst THEME_NAME = 'CalcitaPondDropzone';\n// la raíz: el estilo de FilePond de Calcita abajo, el tema del proyecto encima y el sx de la pantalla al final\nconst PondRoot = piecePart(THEME_NAME, 'Root', Box);\nconst PondSummary = piecePart(THEME_NAME, 'Summary', Typography);\nconst PondLimits = piecePart(THEME_NAME, 'Limits', Typography);\nconst PondActions = piecePart(THEME_NAME, 'Actions', Stack);\nconst PondExtra = piecePart(THEME_NAME, 'Extra', Box);\n\nregisterPlugin(FilePondPluginFileValidateType, FilePondPluginFileValidateSize);\n\n// cuánto se ve el error de un archivo de tipo no permitido antes de quitarse solo: lo suficiente para leer por qué\nconst AUTO_REJECT_DELAY_MILLISECONDS = 1800;\n// el aviso de FilePond cuando se sueltan más archivos de los permitidos\nconst MAX_FILES_WARNING = 'max files';\n\n/**\n * El tipo de un archivo para el complemento de tipos: el paquete habla de EXTENSIONES (.xlsx) y FilePond, por omisión,\n * del tipo MIME. Imagen, audio y video van por su tipo (para que 'image/*' funcione); lo demás, por su extensión.\n *\n * @param {File} file\n * @returns {Promise<string>}\n */\nfunction detectFileType(file) {\n const mimeType = (file.type ?? '').toLowerCase();\n if (/^(image|audio|video)\\//.test(mimeType)) {\n return Promise.resolve(mimeType);\n }\n const dotPosition = file.name.lastIndexOf('.');\n return Promise.resolve(dotPosition >= 0 ? file.name.slice(dotPosition).toLowerCase() : mimeType);\n}\n\n/**\n * pondProps con sus funciones blindadas contra la trampa del encabezado: cada función que llega se cambia por una fija que\n * llama a la de ESTE render (la lee del ref latest). files se quita: la verdad de la lista es FilePond (ver el OJO).\n *\n * @param {object|null} pondProps\n * @param {{current: Map<string, Function>}} stableFunctions las funciones fijas ya hechas, por nombre\n * @param {{current: {pondProps?: object|null}}} latest\n * @returns {object}\n */\nfunction stablePondPropsOf(pondProps, stableFunctions, latest) {\n const {files: ignoredFiles, ...pondOptions} = pondProps ?? {};\n if (ignoredFiles !== undefined) {\n warnUnknownProps(THEME_NAME, {'pondProps.files': ignoredFiles});\n }\n return Object.fromEntries(Object.entries(pondOptions).map(([option, value]) => {\n if (typeof value !== 'function') {\n return [option, value];\n }\n if (!stableFunctions.current.has(option)) {\n stableFunctions.current.set(option, (...args) => latest.current.pondProps?.[option]?.(...args));\n }\n return [option, stableFunctions.current.get(option)];\n }));\n}\n\n/**\n * Las opciones de trozos que la pantalla puso, para FilePond: solo las que vienen (sin ellas, FilePond sigue con las suyas).\n *\n * @param {{chunkUploads?: boolean, chunkForce?: boolean, chunkSize?: number|string, chunkRetryDelays?: number[]|null}} chunks\n * @returns {object}\n */\nfunction chunkOptionsOf({chunkUploads, chunkForce, chunkSize, chunkRetryDelays}) {\n return {\n ...(chunkUploads !== undefined && {chunkUploads}),\n ...(chunkForce !== undefined && {chunkForce}),\n ...(chunkSize !== undefined && {chunkSize: parseFileSize(chunkSize)}),\n // FilePond truena con null: sin reintentos es una lista vacía\n ...(chunkRetryDelays !== undefined && {chunkRetryDelays: chunkRetryDelays ?? []}),\n };\n}\n\n/**\n * ¿Los mismos archivos en el mismo orden?\n *\n * @param {File[]} firstFiles\n * @param {File[]} secondFiles\n * @returns {boolean}\n */\nfunction sameFiles(firstFiles, secondFiles) {\n return firstFiles.length === secondFiles.length && firstFiles.every((file, position) => file === secondFiles[position]);\n}\n\n/**\n * value (File | File[] | null) como arreglo.\n *\n * @param {File|File[]|null} value\n * @param {boolean} multiple\n * @returns {File[]}\n */\nfunction filesOf(value, multiple) {\n if (multiple) {\n return Array.isArray(value) ? value : (value ? [value] : []);\n }\n return value ? [value] : [];\n}\n\n/**\n * Los textos que FilePond pinta dentro de cada tarjeta (vienen en inglés), desde los textos del paquete.\n *\n * El de «no se pudo subir» enseña el mensaje del servidor cuando lo hay (createPondServer manda el título del problema\n * RFC 9457): «No se pudo subir» a secas no le dice a la persona qué corregir.\n *\n * @param {(key: string) => string} t traductor de common\n * @returns {object} props de FilePond\n */\nfunction pondLabelsOf(t) {\n return {\n labelFileLoading: t('upload.pond.loading'),\n labelFileLoadError: t('upload.pond.loadError'),\n labelFileWaitingForSize: t('upload.pond.waitingForSize'),\n labelFileSizeNotAvailable: t('upload.pond.sizeNotAvailable'),\n labelInvalidField: t('upload.pond.invalidField'),\n labelFileProcessing: t('upload.pond.processing'),\n labelFileProcessingComplete: t('upload.pond.processingComplete'),\n labelFileProcessingAborted: t('upload.pond.processingAborted'),\n labelFileProcessingError: (error) => error?.body || t('upload.pond.processingError'),\n labelFileProcessingRevertError: t('upload.pond.revertError'),\n labelFileRemoveError: t('upload.pond.removeError'),\n labelTapToCancel: t('upload.pond.tapToCancel'),\n labelTapToRetry: t('upload.pond.tapToRetry'),\n labelTapToUndo: t('upload.pond.tapToUndo'),\n labelButtonRemoveItem: t('upload.pond.remove'),\n labelButtonAbortItemLoad: t('upload.pond.abortLoad'),\n labelButtonRetryItemLoad: t('upload.pond.retryLoad'),\n labelButtonAbortItemProcessing: t('upload.pond.abortProcessing'),\n labelButtonUndoItemProcessing: t('upload.pond.undoProcessing'),\n labelButtonRetryItemProcessing: t('upload.pond.retryProcessing'),\n labelButtonProcessItem: t('upload.pond.processItem'),\n };\n}\n\n/**\n * Los colores de FilePond desde el tema. El «& » los anida dentro de la caja: pesan más que los de la hoja de FilePond\n * aunque esa hoja cargue después.\n *\n * @param {object} theme tema de MUI (con o sin variables CSS)\n * @returns {object}\n */\nfunction pondThemeSxOf(theme) {\n const {palette} = theme.vars ?? theme;\n return {\n '& .filepond--root': {fontFamily: theme.typography.fontFamily, mb: 0},\n '& .filepond--panel-root': {bgcolor: palette.background.paper, border: `2px dashed ${palette.divider}`, borderRadius: 2},\n '& .filepond--drop-label': {color: palette.text.primary},\n // la liga «Elegir archivos»: del color del tema pero legible (accentTextStylesOf); el naranja tal cual se perdía en oscuro\n '& .filepond--label-action': {...accentTextStylesOf(theme), textDecorationColor: 'currentColor'},\n '& .filepond--item-panel': {bgcolor: palette.primary.main},\n '& .filepond--file, & .filepond--file-action-button': {color: palette.primary.contrastText},\n };\n}\n\n/**\n * @param {object} props\n * @param {File|File[]|null} [props.value]\n * @param {(value: File|File[]|null) => void} props.onChange\n * @param {string|string[]} [props.accept]\n * @param {number|string} [props.maxSize] por archivo\n * @param {boolean} [props.multiple]\n * @param {number} [props.maxFiles]\n * @param {number|string} [props.maxTotalSize]\n * @param {string} [props.name] nombre del campo; por omisión uno propio por zona (dos zonas no se pisan)\n * @param {string} [props.hint]\n * @param {string} [props.buttonLabel]\n * @param {boolean} [props.disabled]\n * @param {boolean} [props.busy]\n * @param {boolean|{title?: string, text?: string}} [props.busyModal]\n * @param {boolean} [props.autoReject]\n * @param {((file: File) => Promise<true|string>)|null} [props.validate]\n * @param {object|null} [props.server] createPondServer(...) o {process} propio\n * @param {boolean} [props.instantUpload]\n * @param {{current: object|null}|null} [props.apiRef]\n * @param {(progress: {done: number, total: number}) => void} [props.onProcessProgress]\n * @param {(file: File|null, reason: string, message?: string) => void} [props.onReject] reason de FILE_REJECTION\n * @param {boolean} [props.chunkUploads] sube por trozos lo que pase de chunkSize\n * @param {boolean} [props.chunkForce] por trozos todo, también lo chico\n * @param {number|string} [props.chunkSize] en bytes o con unidades ('1 MB'); por omisión 5 MB\n * @param {number[]|null} [props.chunkRetryDelays] esperas entre reintentos de un trozo (ms); null o [] = sin reintentos\n * @param {Partial<import('filepond').FilePondOptions>|null} [props.pondProps] cualquier otra opción de FilePond, tal cual\n * @param {import('react').ReactNode} [props.actions]\n * @param {import('react').ReactNode} [props.children]\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction FileDropzoneContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaPondDropzone.defaultProps) y encima lo que llega aquí\n const {\n value = null, onChange, accept, maxSize, multiple = false, maxFiles, maxTotalSize, name, hint, buttonLabel, disabled = false,\n busy = false, busyModal = false, autoReject = true, validate = null, server = null, instantUpload = false, apiRef = null,\n onProcessProgress, onReject, chunkUploads, chunkForce, chunkSize, chunkRetryDelays, pondProps = null, actions, children, sx, texts,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const {formatFileSize} = useFormatters();\n const generatedId = useId();\n const pondName = name ?? `file-dropzone-${generatedId}`;\n // el candado: apagado o subiendo. Va también en cada allow* de FilePond, no solo en lo visual\n const isLocked = disabled || busy;\n\n const resolvedAccept = useMemo(() => resolveAccept(accept), [accept]);\n const acceptedTypes = useMemo(() => (resolvedAccept ? resolvedAccept.split(',') : null), [resolvedAccept]);\n const maxBytes = parseFileSize(maxSize);\n const maxTotalBytes = multiple ? parseFileSize(maxTotalSize) : null;\n const typeErrorLabel = t('upload.invalidTitle');\n const totalErrorLabel = t('upload.totalTooLargeShort');\n const files = useMemo(() => filesOf(value, multiple), [value, multiple]);\n\n const pondRef = useRef(null);\n // lo que las funciones fijas necesitan de afuera, siempre al día (ver la trampa del encabezado)\n const latest = useRef({});\n latest.current = {files, multiple, onChange, onReject, autoReject, validate, onProcessProgress, maxFiles, typeErrorLabel, totalErrorLabel, t, pondProps};\n const stableFunctions = useRef(new Map());\n const pondOptions = stablePondPropsOf(pondProps, stableFunctions, latest);\n // el candado gana: apagado o subiendo no se suelta, elige, pega ni quita aunque pondProps lo prenda\n const pondAllows = (option) => !isLocked && (pondProps?.[option] ?? true);\n\n useEffect(() => {\n if (apiRef) {\n apiRef.current = pondRef.current;\n }\n });\n\n // value desde FilePond. Los archivos con error se quedan en pantalla (en rojo) pero no cuentan: con un archivo malo\n // el botón de enviar no se prende. Y un removeFiles() hecho desde aquí no debe avisar un cambio fantasma\n const syncFromPond = useCallback(() => {\n const {files: currentFiles, multiple: isMultiple, onChange: emitChange} = latest.current;\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n const nextFiles = pondItems.filter((item) => item.status !== FileStatus.LOAD_ERROR).map((item) => item.file);\n if (isMultiple) {\n if (!sameFiles(nextFiles, currentFiles)) {\n emitChange(nextFiles);\n }\n return;\n }\n const nextFile = nextFiles[0] ?? null;\n if (nextFile !== (currentFiles[0] ?? null)) {\n emitChange(nextFile);\n }\n }, []);\n\n const handleError = useCallback((error, item, status) => {\n // el archivo acaba de quedar con error: se saca de value\n syncFromPond();\n const {onReject: reject, autoReject: shouldAutoReject, typeErrorLabel: typeLabel, totalErrorLabel: totalLabel} = latest.current;\n // el motivo se reconoce por el texto, que es nuestro: comparación estable\n let reason = FILE_REJECTION.size;\n if (error?.main === typeLabel) {\n reason = FILE_REJECTION.type;\n } else if (error?.main === totalLabel) {\n reason = FILE_REJECTION.total;\n }\n // el de tipo no permitido se va solo después de que se lea su error; el de peso se queda para saber cuál pesa de más\n if (reason === FILE_REJECTION.type && shouldAutoReject && item?.id) {\n setTimeout(() => {\n if (pondRef.current?.getFile?.(item.id)) {\n pondRef.current.removeFile(item.id);\n }\n }, AUTO_REJECT_DELAY_MILLISECONDS);\n }\n if (reject && item?.file) {\n reject(item.file, reason);\n }\n latest.current.pondProps?.onerror?.(error, item, status);\n }, [syncFromPond]);\n\n // la revisión del contenido, ANTES de que el archivo entre a la lista\n const handleBeforeAdd = useCallback(async (item) => {\n const {validate: judge, onReject: reject, typeErrorLabel: fallbackMessage, pondProps: projectOptions} = latest.current;\n // la revisión del proyecto en pondProps va después de validate: lo que validate rechazó ya no le llega\n const projectAccepts = async () => (projectOptions?.beforeAddFile ? (await projectOptions.beforeAddFile(item)) !== false : true);\n if (!judge) {\n return projectAccepts();\n }\n let verdict;\n try {\n verdict = await judge(item.file);\n } catch {\n verdict = false;\n }\n if (verdict === true) {\n return projectAccepts();\n }\n const message = typeof verdict === 'string' ? verdict : fallbackMessage;\n if (reject) {\n reject(item.file, FILE_REJECTION.content, message);\n } else {\n toastError(`${item.file?.name}: ${message}`);\n }\n return false;\n }, []);\n\n // el «subidos X de N» del modo server: cada archivo que termina su subida avisa la cuenta\n const handleProcessed = useCallback((error, processedItem) => {\n const {onProcessProgress: report, pondProps: projectOptions} = latest.current;\n if (report) {\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n report({done: pondItems.filter((item) => item.status === FileStatus.PROCESSING_COMPLETE).length, total: pondItems.length});\n }\n projectOptions?.onprocessfile?.(error, processedItem);\n }, []);\n\n // lo que pasa de maxFiles ni siquiera entra (FilePond corta lo soltado y solo avisa): el aviso lo damos nosotros\n const handleWarning = useCallback((warning, file, status) => {\n const {maxFiles: maxFileCount, onReject: reject, t: translate, pondProps: projectOptions} = latest.current;\n if (maxFileCount && String(warning?.body ?? '').toLowerCase().includes(MAX_FILES_WARNING)) {\n if (reject) {\n reject(null, FILE_REJECTION.count);\n } else {\n toastError(translate('upload.tooManyFiles', {count: maxFileCount}));\n }\n }\n projectOptions?.onwarning?.(warning, file, status);\n }, []);\n\n // el cambio de la lista: el espejo de value y, después, el evento del proyecto\n const handleUpdateFiles = useCallback((items) => {\n syncFromPond();\n latest.current.pondProps?.onupdatefiles?.(items);\n }, [syncFromPond]);\n\n // limpiar desde afuera: value vacío (p. ej. después de subir bien) vacía la zona\n useEffect(() => {\n const pondItems = pondRef.current?.getFiles?.() ?? [];\n if (files.length === 0 && pondItems.length > 0) {\n pondRef.current.removeFiles();\n }\n }, [files]);\n\n const limitHints = [\n maxBytes && t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}),\n maxTotalBytes && t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}),\n multiple && maxFiles && t('upload.maxFilesHint', {count: maxFiles}),\n ].filter(Boolean);\n // con varios archivos se dice cuántos van y cuánto pesan juntos: nadie suma tarjeta por tarjeta\n const totalBytes = multiple ? files.reduce((sum, file) => sum + (file?.size ?? 0), 0) : 0;\n const hintText = hint ?? (multiple ? t('upload.hintMultiple') : t('upload.hint'));\n const chooseText = buttonLabel ?? (multiple ? t('upload.chooseMultiple') : t('upload.choose'));\n\n return (\n <PondRoot ownSx={(theme) => pondThemeSxOf(theme)} sx={sx}>\n <FilePond\n {...pondOptions}\n {...chunkOptionsOf({chunkUploads, chunkForce, chunkSize, chunkRetryDelays})}\n ref={pondRef}\n name={pondName}\n server={server}\n instantUpload={instantUpload}\n beforeAddFile={handleBeforeAdd}\n onprocessfile={handleProcessed}\n onupdatefiles={handleUpdateFiles}\n allowMultiple={multiple}\n maxFiles={multiple ? (maxFiles ?? null) : 1}\n disabled={isLocked}\n allowDrop={pondAllows('allowDrop')}\n allowBrowse={pondAllows('allowBrowse')}\n allowPaste={pondAllows('allowPaste')}\n allowRemove={pondAllows('allowRemove')}\n allowReplace={pondAllows('allowReplace')}\n credits={false}\n acceptedFileTypes={acceptedTypes}\n fileValidateTypeDetectType={detectFileType}\n fileValidateTypeLabelExpectedTypes={resolvedAccept ?? ''}\n maxFileSize={maxBytes ? String(maxBytes) : null}\n maxTotalFileSize={maxTotalBytes ? String(maxTotalBytes) : null}\n labelIdle={`${hintText} — <span class=\"filepond--label-action\">${chooseText}</span>`}\n labelFileTypeNotAllowed={typeErrorLabel}\n labelMaxFileSizeExceeded={t('upload.tooLargeShort')}\n labelMaxFileSize={maxBytes ? t('upload.maxSizeHint', {max: formatFileSize(maxBytes)}) : ''}\n labelMaxTotalFileSizeExceeded={totalErrorLabel}\n labelMaxTotalFileSize={maxTotalBytes ? t('upload.maxTotalSizeHint', {max: formatFileSize(maxTotalBytes)}) : ''}\n onwarning={handleWarning}\n onerror={handleError}\n {...pondLabelsOf(t)}\n />\n {multiple && files.length > 0 && (\n <PondSummary variant=\"body2\" ownSx={{textAlign: 'center', mt: 1, fontWeight: 500}}>\n {maxTotalBytes\n ? t('upload.summaryOf', {count: files.length, total: formatFileSize(totalBytes), max: formatFileSize(maxTotalBytes)})\n : t('upload.summary', {count: files.length, total: formatFileSize(totalBytes)})}\n </PondSummary>\n )}\n {limitHints.length > 0 && (\n <PondLimits variant=\"caption\" color=\"text.secondary\" ownSx={{display: 'block', textAlign: 'center', mt: 0.5}}>\n {limitHints.join(' · ')}\n </PondLimits>\n )}\n {actions && (\n <PondActions direction={{xs: 'column', sm: 'row'}} spacing={2} ownSx={{justifyContent: 'center', alignItems: {xs: 'stretch', sm: 'center'}, mt: 2}}>\n {actions}\n </PondActions>\n )}\n {children && <PondExtra ownSx={{mt: 3}}>{children}</PondExtra>}\n {busyModal && <ModalWaiting open={busy} title={busyModal.title} text={busyModal.text} />}\n </PondRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst FileDropzone = withPieceSlots(FileDropzoneContent, THEME_NAME);\nexport default FileDropzone;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAuEA,MAAM,aAAa;AAEnB,MAAM,WAAW,UAAU,YAAY,QAAQ,GAAG;AAClD,MAAM,cAAc,UAAU,YAAY,WAAW,UAAU;AAC/D,MAAM,aAAa,UAAU,YAAY,UAAU,UAAU;AAC7D,MAAM,cAAc,UAAU,YAAY,WAAW,KAAK;AAC1D,MAAM,YAAY,UAAU,YAAY,SAAS,GAAG;AAEpD,eAAe,gCAAgC,8BAA8B;AAG7E,MAAM,iCAAiC;AAEvC,MAAM,oBAAoB;AAS1B,SAAS,eAAe,MAAM;AAC1B,QAAM,YAAY,KAAK,QAAQ,IAAI,YAAA;AACnC,MAAI,yBAAyB,KAAK,QAAQ,GAAG;AACzC,WAAO,QAAQ,QAAQ,QAAQ;AAAA,EACnC;AACA,QAAM,cAAc,KAAK,KAAK,YAAY,GAAG;AAC7C,SAAO,QAAQ,QAAQ,eAAe,IAAI,KAAK,KAAK,MAAM,WAAW,EAAE,YAAA,IAAgB,QAAQ;AACnG;AAWA,SAAS,kBAAkB,WAAW,iBAAiB,QAAQ;AAC3D,QAAM,EAAC,OAAO,cAAc,GAAG,YAAA,IAAe,aAAa,CAAA;AAC3D,MAAI,iBAAiB,QAAW;AAC5B,qBAAiB,YAAY,EAAC,mBAAmB,aAAA,CAAa;AAAA,EAClE;AACA,SAAO,OAAO,YAAY,OAAO,QAAQ,WAAW,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM;AAC3E,QAAI,OAAO,UAAU,YAAY;AAC7B,aAAO,CAAC,QAAQ,KAAK;AAAA,IACzB;AACA,QAAI,CAAC,gBAAgB,QAAQ,IAAI,MAAM,GAAG;AACtC,sBAAgB,QAAQ,IAAI,QAAQ,IAAI,SAAS,OAAO,QAAQ,YAAY,MAAM,IAAI,GAAG,IAAI,CAAC;AAAA,IAClG;AACA,WAAO,CAAC,QAAQ,gBAAgB,QAAQ,IAAI,MAAM,CAAC;AAAA,EACvD,CAAC,CAAC;AACN;AAQA,SAAS,eAAe,EAAC,cAAc,YAAY,WAAW,oBAAmB;AAC7E,SAAO;AAAA,IACH,GAAI,iBAAiB,UAAa,EAAC,aAAA;AAAA,IACnC,GAAI,eAAe,UAAa,EAAC,WAAA;AAAA,IACjC,GAAI,cAAc,UAAa,EAAC,WAAW,cAAc,SAAS,EAAA;AAAA;AAAA,IAElE,GAAI,qBAAqB,UAAa,EAAC,kBAAkB,oBAAoB,CAAA,EAAC;AAAA,EAAC;AAEvF;AASA,SAAS,UAAU,YAAY,aAAa;AACxC,SAAO,WAAW,WAAW,YAAY,UAAU,WAAW,MAAM,CAAC,MAAM,aAAa,SAAS,YAAY,QAAQ,CAAC;AAC1H;AASA,SAAS,QAAQ,OAAO,UAAU;AAC9B,MAAI,UAAU;AACV,WAAO,MAAM,QAAQ,KAAK,IAAI,QAAS,QAAQ,CAAC,KAAK,IAAI,CAAA;AAAA,EAC7D;AACA,SAAO,QAAQ,CAAC,KAAK,IAAI,CAAA;AAC7B;AAWA,SAAS,aAAa,GAAG;AACrB,SAAO;AAAA,IACH,kBAAkB,EAAE,qBAAqB;AAAA,IACzC,oBAAoB,EAAE,uBAAuB;AAAA,IAC7C,yBAAyB,EAAE,4BAA4B;AAAA,IACvD,2BAA2B,EAAE,8BAA8B;AAAA,IAC3D,mBAAmB,EAAE,0BAA0B;AAAA,IAC/C,qBAAqB,EAAE,wBAAwB;AAAA,IAC/C,6BAA6B,EAAE,gCAAgC;AAAA,IAC/D,4BAA4B,EAAE,+BAA+B;AAAA,IAC7D,0BAA0B,CAAC,UAAU,OAAO,QAAQ,EAAE,6BAA6B;AAAA,IACnF,gCAAgC,EAAE,yBAAyB;AAAA,IAC3D,sBAAsB,EAAE,yBAAyB;AAAA,IACjD,kBAAkB,EAAE,yBAAyB;AAAA,IAC7C,iBAAiB,EAAE,wBAAwB;AAAA,IAC3C,gBAAgB,EAAE,uBAAuB;AAAA,IACzC,uBAAuB,EAAE,oBAAoB;AAAA,IAC7C,0BAA0B,EAAE,uBAAuB;AAAA,IACnD,0BAA0B,EAAE,uBAAuB;AAAA,IACnD,gCAAgC,EAAE,6BAA6B;AAAA,IAC/D,+BAA+B,EAAE,4BAA4B;AAAA,IAC7D,gCAAgC,EAAE,6BAA6B;AAAA,IAC/D,wBAAwB,EAAE,yBAAyB;AAAA,EAAA;AAE3D;AASA,SAAS,cAAc,OAAO;AAC1B,QAAM,EAAC,QAAA,IAAW,MAAM,QAAQ;AAChC,SAAO;AAAA,IACH,qBAAqB,EAAC,YAAY,MAAM,WAAW,YAAY,IAAI,EAAA;AAAA,IACnE,2BAA2B,EAAC,SAAS,QAAQ,WAAW,OAAO,QAAQ,cAAc,QAAQ,OAAO,IAAI,cAAc,EAAA;AAAA,IACtH,2BAA2B,EAAC,OAAO,QAAQ,KAAK,QAAA;AAAA;AAAA,IAEhD,6BAA6B,EAAC,GAAG,mBAAmB,KAAK,GAAG,qBAAqB,eAAA;AAAA,IACjF,2BAA2B,EAAC,SAAS,QAAQ,QAAQ,KAAA;AAAA,IACrD,sDAAsD,EAAC,OAAO,QAAQ,QAAQ,aAAA;AAAA,EAAY;AAElG;AAkCA,SAAS,oBAAoB,OAAO;AAEhC,QAAM;AAAA,IACN,QAAQ;AAAA,IAAM;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAS,WAAW;AAAA,IAAO;AAAA,IAAU;AAAA,IAAc;AAAA,IAAM;AAAA,IAAM;AAAA,IAAa,WAAW;AAAA,IACvH,OAAO;AAAA,IAAO,YAAY;AAAA,IAAO,aAAa;AAAA,IAAM,WAAW;AAAA,IAAM,SAAS;AAAA,IAAM,gBAAgB;AAAA,IAAO,SAAS;AAAA,IACpH;AAAA,IAAmB;AAAA,IAAU;AAAA,IAAc;AAAA,IAAY;AAAA,IAAW;AAAA,IAAkB,YAAY;AAAA,IAAM;AAAA,IAAS;AAAA,IAAU;AAAA,IAAI;AAAA,IAC7H,GAAG;AAAA,EAAA,IACH,cAAc,OAAO,UAAU;AAC/B,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,eAAA,IAAkB,cAAA;AACzB,QAAM,cAAc,MAAA;AACpB,QAAM,WAAW,QAAQ,iBAAiB,WAAW;AAErD,QAAM,WAAW,YAAY;AAE7B,QAAM,iBAAiB,QAAQ,MAAM,cAAc,MAAM,GAAG,CAAC,MAAM,CAAC;AACpE,QAAM,gBAAgB,QAAQ,MAAO,iBAAiB,eAAe,MAAM,GAAG,IAAI,MAAO,CAAC,cAAc,CAAC;AACzG,QAAM,WAAW,cAAc,OAAO;AACtC,QAAM,gBAAgB,WAAW,cAAc,YAAY,IAAI;AAC/D,QAAM,iBAAiB,EAAE,qBAAqB;AAC9C,QAAM,kBAAkB,EAAE,2BAA2B;AACrD,QAAM,QAAQ,QAAQ,MAAM,QAAQ,OAAO,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEvE,QAAM,UAAU,OAAO,IAAI;AAE3B,QAAM,SAAS,OAAO,EAAE;AACxB,SAAO,UAAU,EAAC,OAAO,UAAU,UAAU,UAAU,YAAY,UAAU,mBAAmB,UAAU,gBAAgB,iBAAiB,GAAG,UAAA;AAC9I,QAAM,kBAAkB,OAAO,oBAAI,KAAK;AACxC,QAAM,cAAc,kBAAkB,WAAW,iBAAiB,MAAM;AAExE,QAAM,aAAa,CAAC,WAAW,CAAC,aAAa,YAAY,MAAM,KAAK;AAEpE,YAAU,MAAM;AACZ,QAAI,QAAQ;AACR,aAAO,UAAU,QAAQ;AAAA,IAC7B;AAAA,EACJ,CAAC;AAID,QAAM,eAAe,YAAY,MAAM;AACnC,UAAM,EAAC,OAAO,cAAc,UAAU,YAAY,UAAU,eAAc,OAAO;AACjF,UAAM,YAAY,QAAQ,SAAS,WAAA,KAAgB,CAAA;AACnD,UAAM,YAAY,UAAU,OAAO,CAAC,SAAS,KAAK,WAAW,WAAW,UAAU,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;AAC3G,QAAI,YAAY;AACZ,UAAI,CAAC,UAAU,WAAW,YAAY,GAAG;AACrC,mBAAW,SAAS;AAAA,MACxB;AACA;AAAA,IACJ;AACA,UAAM,WAAW,UAAU,CAAC,KAAK;AACjC,QAAI,cAAc,aAAa,CAAC,KAAK,OAAO;AACxC,iBAAW,QAAQ;AAAA,IACvB;AAAA,EACJ,GAAG,CAAA,CAAE;AAEL,QAAM,cAAc,YAAY,CAAC,OAAO,MAAM,WAAW;AAErD,iBAAA;AACA,UAAM,EAAC,UAAU,QAAQ,YAAY,kBAAkB,gBAAgB,WAAW,iBAAiB,WAAA,IAAc,OAAO;AAExH,QAAI,SAAS,eAAe;AAC5B,QAAI,OAAO,SAAS,WAAW;AAC3B,eAAS,eAAe;AAAA,IAC5B,WAAW,OAAO,SAAS,YAAY;AACnC,eAAS,eAAe;AAAA,IAC5B;AAEA,QAAI,WAAW,eAAe,QAAQ,oBAAoB,MAAM,IAAI;AAChE,iBAAW,MAAM;AACb,YAAI,QAAQ,SAAS,UAAU,KAAK,EAAE,GAAG;AACrC,kBAAQ,QAAQ,WAAW,KAAK,EAAE;AAAA,QACtC;AAAA,MACJ,GAAG,8BAA8B;AAAA,IACrC;AACA,QAAI,UAAU,MAAM,MAAM;AACtB,aAAO,KAAK,MAAM,MAAM;AAAA,IAC5B;AACA,WAAO,QAAQ,WAAW,UAAU,OAAO,MAAM,MAAM;AAAA,EAC3D,GAAG,CAAC,YAAY,CAAC;AAGjB,QAAM,kBAAkB,YAAY,OAAO,SAAS;AAChD,UAAM,EAAC,UAAU,OAAO,UAAU,QAAQ,gBAAgB,iBAAiB,WAAW,eAAA,IAAkB,OAAO;AAE/G,UAAM,iBAAiB,YAAa,gBAAgB,gBAAiB,MAAM,eAAe,cAAc,IAAI,MAAO,QAAQ;AAC3H,QAAI,CAAC,OAAO;AACR,aAAO,eAAA;AAAA,IACX;AACA,QAAI;AACJ,QAAI;AACA,gBAAU,MAAM,MAAM,KAAK,IAAI;AAAA,IACnC,QAAQ;AACJ,gBAAU;AAAA,IACd;AACA,QAAI,YAAY,MAAM;AAClB,aAAO,eAAA;AAAA,IACX;AACA,UAAM,UAAU,OAAO,YAAY,WAAW,UAAU;AACxD,QAAI,QAAQ;AACR,aAAO,KAAK,MAAM,eAAe,SAAS,OAAO;AAAA,IACrD,OAAO;AACH,iBAAW,GAAG,KAAK,MAAM,IAAI,KAAK,OAAO,EAAE;AAAA,IAC/C;AACA,WAAO;AAAA,EACX,GAAG,CAAA,CAAE;AAGL,QAAM,kBAAkB,YAAY,CAAC,OAAO,kBAAkB;AAC1D,UAAM,EAAC,mBAAmB,QAAQ,WAAW,eAAA,IAAkB,OAAO;AACtE,QAAI,QAAQ;AACR,YAAM,YAAY,QAAQ,SAAS,WAAA,KAAgB,CAAA;AACnD,aAAO,EAAC,MAAM,UAAU,OAAO,CAAC,SAAS,KAAK,WAAW,WAAW,mBAAmB,EAAE,QAAQ,OAAO,UAAU,QAAO;AAAA,IAC7H;AACA,oBAAgB,gBAAgB,OAAO,aAAa;AAAA,EACxD,GAAG,CAAA,CAAE;AAGL,QAAM,gBAAgB,YAAY,CAAC,SAAS,MAAM,WAAW;AACzD,UAAM,EAAC,UAAU,cAAc,UAAU,QAAQ,GAAG,WAAW,WAAW,eAAA,IAAkB,OAAO;AACnG,QAAI,gBAAgB,OAAO,SAAS,QAAQ,EAAE,EAAE,YAAA,EAAc,SAAS,iBAAiB,GAAG;AACvF,UAAI,QAAQ;AACR,eAAO,MAAM,eAAe,KAAK;AAAA,MACrC,OAAO;AACH,mBAAW,UAAU,uBAAuB,EAAC,OAAO,aAAA,CAAa,CAAC;AAAA,MACtE;AAAA,IACJ;AACA,oBAAgB,YAAY,SAAS,MAAM,MAAM;AAAA,EACrD,GAAG,CAAA,CAAE;AAGL,QAAM,oBAAoB,YAAY,CAAC,UAAU;AAC7C,iBAAA;AACA,WAAO,QAAQ,WAAW,gBAAgB,KAAK;AAAA,EACnD,GAAG,CAAC,YAAY,CAAC;AAGjB,YAAU,MAAM;AACZ,UAAM,YAAY,QAAQ,SAAS,WAAA,KAAgB,CAAA;AACnD,QAAI,MAAM,WAAW,KAAK,UAAU,SAAS,GAAG;AAC5C,cAAQ,QAAQ,YAAA;AAAA,IACpB;AAAA,EACJ,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,aAAa;AAAA,IACf,YAAY,EAAE,sBAAsB,EAAC,KAAK,eAAe,QAAQ,GAAE;AAAA,IACnE,iBAAiB,EAAE,2BAA2B,EAAC,KAAK,eAAe,aAAa,GAAE;AAAA,IAClF,YAAY,YAAY,EAAE,uBAAuB,EAAC,OAAO,UAAS;AAAA,EAAA,EACpE,OAAO,OAAO;AAEhB,QAAM,aAAa,WAAW,MAAM,OAAO,CAAC,KAAK,SAAS,OAAO,MAAM,QAAQ,IAAI,CAAC,IAAI;AACxF,QAAM,WAAW,SAAS,WAAW,EAAE,qBAAqB,IAAI,EAAE,aAAa;AAC/E,QAAM,aAAa,gBAAgB,WAAW,EAAE,uBAAuB,IAAI,EAAE,eAAe;AAE5F,SACI,qBAAC,YAAS,OAAO,CAAC,UAAU,cAAc,KAAK,GAAG,IAC9C,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACI,GAAG;AAAA,QACH,GAAG,eAAe,EAAC,cAAc,YAAY,WAAW,kBAAiB;AAAA,QAC1E,KAAK;AAAA,QACL,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,eAAe;AAAA,QACf,eAAe;AAAA,QACf,eAAe;AAAA,QACf,eAAe;AAAA,QACf,UAAU,WAAY,YAAY,OAAQ;AAAA,QAC1C,UAAU;AAAA,QACV,WAAW,WAAW,WAAW;AAAA,QACjC,aAAa,WAAW,aAAa;AAAA,QACrC,YAAY,WAAW,YAAY;AAAA,QACnC,aAAa,WAAW,aAAa;AAAA,QACrC,cAAc,WAAW,cAAc;AAAA,QACvC,SAAS;AAAA,QACT,mBAAmB;AAAA,QACnB,4BAA4B;AAAA,QAC5B,oCAAoC,kBAAkB;AAAA,QACtD,aAAa,WAAW,OAAO,QAAQ,IAAI;AAAA,QAC3C,kBAAkB,gBAAgB,OAAO,aAAa,IAAI;AAAA,QAC1D,WAAW,GAAG,QAAQ,2CAA2C,UAAU;AAAA,QAC3E,yBAAyB;AAAA,QACzB,0BAA0B,EAAE,sBAAsB;AAAA,QAClD,kBAAkB,WAAW,EAAE,sBAAsB,EAAC,KAAK,eAAe,QAAQ,EAAA,CAAE,IAAI;AAAA,QACxF,+BAA+B;AAAA,QAC/B,uBAAuB,gBAAgB,EAAE,2BAA2B,EAAC,KAAK,eAAe,aAAa,EAAA,CAAE,IAAI;AAAA,QAC5G,WAAW;AAAA,QACX,SAAS;AAAA,QACR,GAAG,aAAa,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAErB,YAAY,MAAM,SAAS,yBACvB,aAAA,EAAY,SAAQ,SAAQ,OAAO,EAAC,WAAW,UAAU,IAAI,GAAG,YAAY,IAAA,GACxE,UAAA,gBACK,EAAE,oBAAoB,EAAC,OAAO,MAAM,QAAQ,OAAO,eAAe,UAAU,GAAG,KAAK,eAAe,aAAa,GAAE,IAClH,EAAE,kBAAkB,EAAC,OAAO,MAAM,QAAQ,OAAO,eAAe,UAAU,EAAA,CAAE,GACtF;AAAA,IAEH,WAAW,SAAS,KACjB,oBAAC,cAAW,SAAQ,WAAU,OAAM,kBAAiB,OAAO,EAAC,SAAS,SAAS,WAAW,UAAU,IAAI,OACnG,UAAA,WAAW,KAAK,KAAK,GAC1B;AAAA,IAEH,WACG,oBAAC,aAAA,EAAY,WAAW,EAAC,IAAI,UAAU,IAAI,MAAA,GAAQ,SAAS,GAAG,OAAO,EAAC,gBAAgB,UAAU,YAAY,EAAC,IAAI,WAAW,IAAI,SAAA,GAAW,IAAI,EAAA,GAC3I,UAAA,QAAA,CACL;AAAA,IAEH,gCAAa,WAAA,EAAU,OAAO,EAAC,IAAI,KAAK,UAAS;AAAA,IACjD,aAAa,oBAAC,cAAA,EAAa,MAAM,MAAM,OAAO,UAAU,OAAO,MAAM,UAAU,KAAA,CAAM;AAAA,EAAA,GAC1F;AAER;AAGA,MAAM,eAAe,eAAe,qBAAqB,UAAU;"}
|
|
@@ -1,38 +1,96 @@
|
|
|
1
|
+
import { createLogger } from "../observability/logger.js";
|
|
1
2
|
import { problemTitle } from "../problems.js";
|
|
2
|
-
import { toolkitSettings } from "../toolkit/toolkitSettings.js";
|
|
3
|
+
import { toolkitSettings, translateToolkit } from "../toolkit/toolkitSettings.js";
|
|
4
|
+
import { defaultPatchUrlOf, startChunkedUpload, UploadFailure, UPLOAD_FAILURE, problemOfResponse } from "../upload/chunkedUpload.js";
|
|
5
|
+
import { uploadWithProgress } from "../upload/uploadWithProgress.js";
|
|
6
|
+
const log = createLogger("pond");
|
|
3
7
|
const OFFLINE_STATUS = 0;
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
function usesChunks(file, options) {
|
|
9
|
+
return options?.chunkUploads === true && (file.size > options.chunkSize || options.chunkForce === true);
|
|
10
|
+
}
|
|
11
|
+
async function failureMessageOf(failure, toProblem) {
|
|
12
|
+
if (failure?.reason === UPLOAD_FAILURE.noTransferId) {
|
|
13
|
+
return translateToolkit("common", "upload.pond.noTransferId");
|
|
14
|
+
}
|
|
15
|
+
if (failure?.reason === UPLOAD_FAILURE.noResponse) {
|
|
16
|
+
return problemTitle(null, OFFLINE_STATUS);
|
|
17
|
+
}
|
|
18
|
+
if (failure?.reason === UPLOAD_FAILURE.rejected && failure.response) {
|
|
19
|
+
return problemTitle(await problemOfResponse(failure.response, toProblem), failure.response.status);
|
|
9
20
|
}
|
|
10
|
-
|
|
21
|
+
log.error("La subida falló sin respuesta del servidor", failure);
|
|
22
|
+
return "";
|
|
11
23
|
}
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
function startSingleUpload({ url, fieldName, file, headers, withCredentials, sendRequest, onProgress }) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const formData = new FormData();
|
|
27
|
+
formData.append(fieldName, file, file.name);
|
|
28
|
+
const done = sendRequest(url, formData, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers,
|
|
31
|
+
withCredentials,
|
|
32
|
+
signal: controller.signal,
|
|
33
|
+
onProgress: (percent, transfer) => {
|
|
34
|
+
if (!controller.signal.aborted) {
|
|
35
|
+
onProgress(transfer.loaded, transfer.total);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}).then(
|
|
39
|
+
async (response) => {
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new UploadFailure(UPLOAD_FAILURE.rejected, response);
|
|
20
42
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
return await response.text() || file.name;
|
|
44
|
+
},
|
|
45
|
+
(failure) => {
|
|
46
|
+
if (failure?.name === "AbortError") {
|
|
47
|
+
throw failure;
|
|
48
|
+
}
|
|
49
|
+
throw new UploadFailure(UPLOAD_FAILURE.noResponse);
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
return { done, abort: () => controller.abort() };
|
|
53
|
+
}
|
|
54
|
+
function createPondServer({ url, fieldName, headers = {}, withCredentials = false, patchUrl, toProblem, sendRequest = uploadWithProgress }) {
|
|
55
|
+
const chunkUrl = patchUrl ?? defaultPatchUrlOf(url);
|
|
56
|
+
return {
|
|
57
|
+
// FilePond llama process con su progress, su folio (transfer) y, al final, sus opciones de trozos
|
|
58
|
+
process: (pondFieldName, file, metadata, load, error, progress, abort, transfer, options = {}) => {
|
|
59
|
+
const requestHeaders = { "Accept-Language": toolkitSettings().language, ...headers };
|
|
60
|
+
const reportProgress = (loaded, total) => progress(true, loaded, total);
|
|
61
|
+
const upload = usesChunks(file, options) ? startChunkedUpload({
|
|
62
|
+
file,
|
|
63
|
+
fileName: file.name,
|
|
64
|
+
fieldName: fieldName ?? pondFieldName,
|
|
65
|
+
metadata,
|
|
66
|
+
url,
|
|
67
|
+
patchUrl: chunkUrl,
|
|
68
|
+
headers: requestHeaders,
|
|
69
|
+
withCredentials,
|
|
70
|
+
chunkSize: options.chunkSize,
|
|
71
|
+
retryDelays: options.chunkRetryDelays,
|
|
72
|
+
transferId: options.chunkTransferId ?? null,
|
|
73
|
+
onTransferId: transfer,
|
|
74
|
+
onProgress: reportProgress,
|
|
75
|
+
sendRequest
|
|
76
|
+
}) : startSingleUpload({
|
|
77
|
+
url,
|
|
78
|
+
fieldName: fieldName ?? pondFieldName,
|
|
79
|
+
file,
|
|
80
|
+
headers: requestHeaders,
|
|
81
|
+
withCredentials,
|
|
82
|
+
sendRequest,
|
|
83
|
+
onProgress: reportProgress
|
|
84
|
+
});
|
|
85
|
+
upload.done.then(load, async (failure) => {
|
|
86
|
+
if (failure?.name === "AbortError") {
|
|
25
87
|
return;
|
|
26
88
|
}
|
|
27
|
-
error(
|
|
28
|
-
};
|
|
29
|
-
request.onerror = () => error(problemTitle(null, OFFLINE_STATUS));
|
|
30
|
-
const formData = new FormData();
|
|
31
|
-
formData.append(fieldName ?? pondFieldName, file, file.name);
|
|
32
|
-
request.send(formData);
|
|
89
|
+
error(await failureMessageOf(failure, toProblem));
|
|
90
|
+
});
|
|
33
91
|
return {
|
|
34
92
|
abort: () => {
|
|
35
|
-
|
|
93
|
+
upload.abort();
|
|
36
94
|
abort();
|
|
37
95
|
}
|
|
38
96
|
};
|
|
@@ -43,6 +101,7 @@ function createPondServer({ url, fieldName, headers = {}, withCredentials = fals
|
|
|
43
101
|
};
|
|
44
102
|
}
|
|
45
103
|
export {
|
|
46
|
-
createPondServer
|
|
104
|
+
createPondServer,
|
|
105
|
+
usesChunks
|
|
47
106
|
};
|
|
48
107
|
//# sourceMappingURL=pondServer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pondServer.js","sources":["../../src/filepond/pondServer.js"],"sourcesContent":["// El «server» de FilePond con lo de la casa: cada archivo se sube en su propio POST con su barra de avance dentro de su\n// tarjeta, el idioma activo en Accept-Language, el error RFC 9457 del backend escrito en la tarjeta y la ✕ que cancela la\n// petición de verdad (no solo la pantalla).\n//\n// Cuándo SÍ: archivos que valen cada uno por su cuenta (adjuntos sueltos, una galería).\n// Cuándo NO: el envío de siempre, «todos los archivos y los campos en UN solo envío»: ahí la zona solo elige y la\n// pantalla arma su FormData. OJO: aquí el backend ve UN archivo por petición; las reglas del lote completo (cuántos,\n// cuánto pesan juntos) ya no las puede revisar por petición.\n//\n// <FileDropzone multiple server={createPondServer({url: '/api/v1/adjuntos', headers: {Authorization: `Bearer ${token}`}})} />\n//\n// Funciones del navegador, sin React: function (regla de la casa).\nimport {problemTitle} from '@/problems.js';\nimport {toolkitSettings} from '@/toolkit/toolkitSettings.js';\n\n// status de «no hubo respuesta» para los textos del paquete (problems: offline)\nconst OFFLINE_STATUS = 0;\n\n/**\n * El título del problema RFC 9457 de una respuesta (o el genérico del paquete, ya traducido).\n *\n * @param {XMLHttpRequest} request\n * @returns {string}\n */\nfunction problemTitleOf(request) {\n let problem = null;\n try {\n problem = JSON.parse(request.responseText);\n } catch {\n // la respuesta no era JSON (un 502 del proxy en HTML): va el título genérico de su status\n }\n return problemTitle(problem, request.status);\n}\n\n/**\n * @param {object} options\n * @param {string} options.url a dónde va cada archivo\n * @param {string} [options.fieldName] el campo del archivo en el FormData; por omisión el name de la zona\n * @param {Record<string, string>} [options.headers] p. ej. Authorization\n * @param {boolean} [options.withCredentials] manda las cookies a otro dominio\n * @returns {{process: (...args: any[]) => any, revert: (...args: any[]) => any}} lo que recibe la prop server de FileDropzone\n */\nexport function createPondServer({url, fieldName, headers = {}, withCredentials = false}) {\n return {\n process: (pondFieldName, file, metadata, load, error, progress, abort) => {\n const request = new XMLHttpRequest();\n request.open('POST', url);\n request.withCredentials = withCredentials;\n for (const [headerName, headerValue] of Object.entries({'Accept-Language': toolkitSettings().language, ...headers})) {\n request.setRequestHeader(headerName, headerValue);\n }\n\n request.upload.onprogress = (event) => progress(event.lengthComputable, event.loaded, event.total);\n request.onload = () => {\n if (request.status >= 200 && request.status < 300) {\n // lo que responda el servidor es el id del archivo (FilePond lo guarda para deshacer)\n load(request.responseText || file.name);\n return;\n }\n error(problemTitleOf(request));\n };\n request.onerror = () => error(problemTitle(null, OFFLINE_STATUS));\n\n const formData = new FormData();\n formData.append(fieldName ?? pondFieldName, file, file.name);\n request.send(formData);\n\n // la ✕ de la tarjeta cancela la petición de verdad\n return {\n abort: () => {\n request.abort();\n abort();\n },\n };\n },\n // sin nada que deshacer en el servidor: el «deshacer» de la tarjeta es local. Si el backend guarda el archivo,\n // aquí iría su DELETE con el id que regresó\n revert: (serverId, load) => load(),\n };\n}\n"],"names":[],"mappings":";;AAgBA,MAAM,iBAAiB;AAQvB,SAAS,eAAe,SAAS;AAC7B,MAAI,UAAU;AACd,MAAI;AACA,cAAU,KAAK,MAAM,QAAQ,YAAY;AAAA,EAC7C,QAAQ;AAAA,EAER;AACA,SAAO,aAAa,SAAS,QAAQ,MAAM;AAC/C;AAUO,SAAS,iBAAiB,EAAC,KAAK,WAAW,UAAU,IAAI,kBAAkB,MAAK,GAAG;AACtF,SAAO;AAAA,IACH,SAAS,CAAC,eAAe,MAAM,UAAU,MAAM,OAAO,UAAU,UAAU;AACtE,YAAM,UAAU,IAAI,eAAc;AAClC,cAAQ,KAAK,QAAQ,GAAG;AACxB,cAAQ,kBAAkB;AAC1B,iBAAW,CAAC,YAAY,WAAW,KAAK,OAAO,QAAQ,EAAC,mBAAmB,gBAAe,EAAG,UAAU,GAAG,QAAO,CAAC,GAAG;AACjH,gBAAQ,iBAAiB,YAAY,WAAW;AAAA,MACpD;AAEA,cAAQ,OAAO,aAAa,CAAC,UAAU,SAAS,MAAM,kBAAkB,MAAM,QAAQ,MAAM,KAAK;AACjG,cAAQ,SAAS,MAAM;AACnB,YAAI,QAAQ,UAAU,OAAO,QAAQ,SAAS,KAAK;AAE/C,eAAK,QAAQ,gBAAgB,KAAK,IAAI;AACtC;AAAA,QACJ;AACA,cAAM,eAAe,OAAO,CAAC;AAAA,MACjC;AACA,cAAQ,UAAU,MAAM,MAAM,aAAa,MAAM,cAAc,CAAC;AAEhE,YAAM,WAAW,IAAI,SAAQ;AAC7B,eAAS,OAAO,aAAa,eAAe,MAAM,KAAK,IAAI;AAC3D,cAAQ,KAAK,QAAQ;AAGrB,aAAO;AAAA,QACH,OAAO,MAAM;AACT,kBAAQ,MAAK;AACb,gBAAK;AAAA,QACT;AAAA,MAChB;AAAA,IACQ;AAAA;AAAA;AAAA,IAGA,QAAQ,CAAC,UAAU,SAAS,KAAI;AAAA,EACxC;AACA;"}
|
|
1
|
+
{"version":3,"file":"pondServer.js","sources":["../../src/filepond/pondServer.js"],"sourcesContent":["// El «server» de FilePond con lo de la casa: cada archivo se sube en su propio POST con su barra de avance dentro de su\n// tarjeta, el idioma activo en Accept-Language, el error del backend escrito en la tarjeta y la ✕ que cancela la petición\n// de verdad (no solo la pantalla).\n//\n// Cuándo SÍ: archivos que valen cada uno por su cuenta (adjuntos sueltos, una galería).\n// Cuándo NO: el envío de siempre, «todos los archivos y los campos en UN solo envío»: ahí la zona solo elige y la\n// pantalla arma su FormData. OJO: aquí el backend ve UN archivo por petición; las reglas del lote completo (cuántos,\n// cuánto pesan juntos) ya no las puede revisar por petición.\n//\n// <FileDropzone multiple server={createPondServer({url: '/api/v1/adjuntos', headers: {Authorization: `Bearer ${token}`}})} />\n//\n// POR TROZOS: cuando la zona pide trozos (chunkUploads, y el archivo pasa de chunkSize o va chunkForce), cada archivo sube\n// con el protocolo de trozos de FilePond y la barra honesta de chunkedUpload.js: el folio con un POST a url, cada trozo con\n// un PATCH a patchUrl + folio, y retomar con un HEAD (lo mismo que usa el cargador mini con createChunkedUpload). Las reglas son las de FilePond (mismos nombres de opciones), así un\n// backend que ya recibía trozos de FilePond no cambia.\n//\n// <FileDropzone chunkUploads chunkForce chunkSize=\"1 MB\"\n// server={createPondServer({url: '/upload', patchUrl: '/upload?patch=', headers: {'X-Upload-Context': JSON.stringify(data)}})} />\n//\n// OJO con los headers: por trozos viajan en UTF-8 byte por byte (como los manda FilePond): se pasan como texto normal, sin\n// convertirlos a mano antes (convertidos dos veces, los acentos llegan rotos). El envío de un solo POST los manda tal cual.\n//\n// Un backend que no contesta RFC 9457 (Laravel manda {message, errors}): toProblem convierte su error, el mismo adaptador\n// de createApiClient. Cada petición sale por sendRequest (por omisión uploadWithProgress de la casa): se puede pasar por el\n// cliente del proyecto o probar sin servidor.\n//\n// Funciones del navegador, sin React: function (regla de la casa).\nimport {createLogger} from '@/observability/logger.js';\nimport {problemTitle} from '@/problems.js';\nimport {toolkitSettings, translateToolkit} from '@/toolkit/toolkitSettings.js';\nimport {UPLOAD_FAILURE, UploadFailure, defaultPatchUrlOf, problemOfResponse, startChunkedUpload} from '@/upload/chunkedUpload.js';\nimport {uploadWithProgress} from '@/upload/uploadWithProgress.js';\n\nconst log = createLogger('pond');\n\n// status de «no hubo respuesta» para los textos del paquete (problems: offline)\nconst OFFLINE_STATUS = 0;\n\n/**\n * ¿Este archivo va por trozos? La regla de FilePond: la zona los pidió y el archivo pasa del tamaño del trozo, o la zona los\n * fuerza para todos (hay servidores que solo reciben por trozos).\n *\n * @param {Blob} file\n * @param {{chunkUploads?: boolean, chunkForce?: boolean, chunkSize?: number}} options lo que FilePond le pasa al server\n * @returns {boolean}\n */\nexport function usesChunks(file, options) {\n return options?.chunkUploads === true && (file.size > options.chunkSize || options.chunkForce === true);\n}\n\n/**\n * Lo que se escribe en la tarjeta cuando un archivo no subió: el título del problema del servidor, o el genérico de su\n * status, ya traducido. Vacío deja el «No se pudo subir» de la zona.\n *\n * @param {unknown} failure\n * @param {((body: any, response: Response) => any)|undefined} toProblem\n * @returns {Promise<string>}\n */\nasync function failureMessageOf(failure, toProblem) {\n if (failure?.reason === UPLOAD_FAILURE.noTransferId) {\n return translateToolkit('common', 'upload.pond.noTransferId');\n }\n if (failure?.reason === UPLOAD_FAILURE.noResponse) {\n return problemTitle(null, OFFLINE_STATUS);\n }\n if (failure?.reason === UPLOAD_FAILURE.rejected && failure.response) {\n return problemTitle(await problemOfResponse(failure.response, toProblem), failure.response.status);\n }\n // no es de la red ni del servidor: un error de programación, que se anota para encontrarlo\n log.error('La subida falló sin respuesta del servidor', failure);\n return '';\n}\n\n/**\n * La subida de siempre: el archivo en un solo POST. done termina con lo que contestó el servidor (el id del archivo) o,\n * si contestó vacío, con el nombre del archivo. Los errores tienen la forma de la subida por trozos (UploadFailure): los\n * dos envíos se leen igual.\n *\n * @param {object} options\n * @returns {{done: Promise<string>, abort: () => void}}\n */\nfunction startSingleUpload({url, fieldName, file, headers, withCredentials, sendRequest, onProgress}) {\n const controller = new AbortController();\n const formData = new FormData();\n formData.append(fieldName, file, file.name);\n const done = sendRequest(url, formData, {\n method: 'POST', headers, withCredentials, signal: controller.signal,\n onProgress: (percent, transfer) => {\n // cancelada, ya no hay avance que contar: la tarjeta se queda en «cancelada»\n if (!controller.signal.aborted) {\n onProgress(transfer.loaded, transfer.total);\n }\n },\n }).then(\n async (response) => {\n if (!response.ok) {\n throw new UploadFailure(UPLOAD_FAILURE.rejected, response);\n }\n return (await response.text()) || file.name;\n },\n (failure) => {\n if (failure?.name === 'AbortError') {\n throw failure;\n }\n throw new UploadFailure(UPLOAD_FAILURE.noResponse);\n },\n );\n return {done, abort: () => controller.abort()};\n}\n\n/**\n * @param {object} options\n * @param {string} options.url a dónde va cada archivo (por trozos: el POST que pide el folio)\n * @param {string} [options.fieldName] el campo del archivo en el FormData; por omisión el name de la zona\n * @param {Record<string, string>} [options.headers] p. ej. Authorization; por trozos van en las tres peticiones\n * @param {boolean} [options.withCredentials] manda las cookies a otro dominio\n * @param {string} [options.patchUrl] por trozos: a dónde va cada trozo, con el folio pegado al final. Por omisión, el de\n * FilePond: url + '?patch='\n * @param {(body: any, response: Response) => any} [options.toProblem] el error del backend a la forma RFC 9457 (Laravel:\n * body => ({title: body.message}))\n * @param {import('@/upload/chunkedUpload.js').UploadSendRequest} [options.sendRequest] cómo viaja cada petición; por omisión\n * uploadWithProgress de la casa\n * @returns {{process: (...args: any[]) => any, revert: (...args: any[]) => any}} lo que recibe la prop server de FileDropzone\n */\nexport function createPondServer({url, fieldName, headers = {}, withCredentials = false, patchUrl, toProblem, sendRequest = uploadWithProgress}) {\n const chunkUrl = patchUrl ?? defaultPatchUrlOf(url);\n return {\n // FilePond llama process con su progress, su folio (transfer) y, al final, sus opciones de trozos\n process: (pondFieldName, file, metadata, load, error, progress, abort, transfer, options = {}) => {\n const requestHeaders = {'Accept-Language': toolkitSettings().language, ...headers};\n const reportProgress = (loaded, total) => progress(true, loaded, total);\n const upload = usesChunks(file, options)\n ? startChunkedUpload({\n file, fileName: file.name, fieldName: fieldName ?? pondFieldName, metadata, url, patchUrl: chunkUrl,\n headers: requestHeaders, withCredentials, chunkSize: options.chunkSize, retryDelays: options.chunkRetryDelays,\n transferId: options.chunkTransferId ?? null, onTransferId: transfer, onProgress: reportProgress, sendRequest,\n })\n : startSingleUpload({\n url, fieldName: fieldName ?? pondFieldName, file, headers: requestHeaders, withCredentials, sendRequest,\n onProgress: reportProgress,\n });\n\n // lo que responda el servidor es el id del archivo (por trozos, el folio): FilePond lo guarda para deshacer\n upload.done.then(load, async (failure) => {\n // la ✕ ya le avisó a FilePond (abort de abajo)\n if (failure?.name === 'AbortError') {\n return;\n }\n error(await failureMessageOf(failure, toProblem));\n });\n\n // la ✕ de la tarjeta cancela la petición de verdad\n return {\n abort: () => {\n upload.abort();\n abort();\n },\n };\n },\n // sin nada que deshacer en el servidor: el «deshacer» de la tarjeta es local. Si el backend guarda el archivo,\n // aquí iría su DELETE con el id que regresó\n revert: (serverId, load) => load(),\n };\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -72,6 +72,7 @@ import { transferProgressOf } from "./upload/transferProgress.js";
|
|
|
72
72
|
import { useFilePicker } from "./upload/useFilePicker.js";
|
|
73
73
|
import { buildUploadActors, uploadMachine } from "./upload/uploadMachine.js";
|
|
74
74
|
import { uploadWithProgress } from "./upload/uploadWithProgress.js";
|
|
75
|
+
import { createChunkedUpload } from "./upload/chunkedUpload.js";
|
|
75
76
|
import { APP_BAR_HEIGHT_CSS_VARIABLE, SCROLL_MARGIN_BELOW_APP_BAR, useAppBarHeightPublisher } from "./ui/layout/appBarHeight.js";
|
|
76
77
|
import { default as default30 } from "./ui/layout/CollapsibleSection.js";
|
|
77
78
|
import { default as default31 } from "./ui/layout/ContentDialog.js";
|
|
@@ -367,6 +368,7 @@ export {
|
|
|
367
368
|
createAccessChecker,
|
|
368
369
|
createApiClient,
|
|
369
370
|
createBrowserNavigation,
|
|
371
|
+
createChunkedUpload,
|
|
370
372
|
createHashNavigation,
|
|
371
373
|
createLogger,
|
|
372
374
|
createPathIndex,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/locales/en.js
CHANGED
|
@@ -375,6 +375,7 @@ const en = Object.freeze({
|
|
|
375
375
|
"processingComplete": "Uploaded",
|
|
376
376
|
"processingAborted": "Upload canceled",
|
|
377
377
|
"processingError": "Could not upload",
|
|
378
|
+
"noTransferId": "The server did not return an upload id",
|
|
378
379
|
"revertError": "Could not undo",
|
|
379
380
|
"removeError": "Could not remove",
|
|
380
381
|
"tapToCancel": "tap to cancel",
|