@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 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 true;
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 true;
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.onProcessProgress;
191
- if (!report) {
192
- return;
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
- const pondItems = pondRef.current?.getFiles?.() ?? [];
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: syncFromPond,
272
+ onupdatefiles: handleUpdateFiles,
232
273
  allowMultiple: multiple,
233
274
  maxFiles: multiple ? maxFiles ?? null : 1,
234
275
  disabled: isLocked,
235
- allowDrop: !isLocked,
236
- allowBrowse: !isLocked,
237
- allowPaste: !isLocked,
238
- allowRemove: !isLocked,
239
- allowReplace: !isLocked,
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":"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"}
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":"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"}
@@ -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 problemTitleOf(request) {
5
- let problem = null;
6
- try {
7
- problem = JSON.parse(request.responseText);
8
- } catch {
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
- return problemTitle(problem, request.status);
21
+ log.error("La subida falló sin respuesta del servidor", failure);
22
+ return "";
11
23
  }
12
- function createPondServer({ url, fieldName, headers = {}, withCredentials = false }) {
13
- return {
14
- process: (pondFieldName, file, metadata, load, error, progress, abort) => {
15
- const request = new XMLHttpRequest();
16
- request.open("POST", url);
17
- request.withCredentials = withCredentials;
18
- for (const [headerName, headerValue] of Object.entries({ "Accept-Language": toolkitSettings().language, ...headers })) {
19
- request.setRequestHeader(headerName, headerValue);
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
- request.upload.onprogress = (event) => progress(event.lengthComputable, event.loaded, event.total);
22
- request.onload = () => {
23
- if (request.status >= 200 && request.status < 300) {
24
- load(request.responseText || file.name);
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(problemTitleOf(request));
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
- request.abort();
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -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",