@devultur/convex 1.4.0 → 1.4.2

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
@@ -654,7 +654,12 @@ any other error, so your app keeps its own fallback. A code it doesn't know gets
654
654
  `devulturErrorLabels` is the English map, typed `Record<DevulturErrorCode, string | ((details) => string)>`:
655
655
  `DevulturErrorCode` is the union of every stable code (`DEVULTUR_ERROR_CODES` lists them), and a function label reads
656
656
  the error's details, such as `file_too_large`'s `limit`. `@devultur/convex/locales/es` exports the Spanish map under
657
- the same name:
657
+ the same name.
658
+
659
+ `@devultur/convex/locales/es` addresses the reader with «tú» ("Vuelve a intentarlo"). For «usted» ("Vuelva a
660
+ intentarlo", "No tiene permiso…"), import the same names from `@devultur/convex/locales/es-formal`: it exports exactly
661
+ what `locales/es` does (error, attachment, preview, library and picker labels), and differs only in the strings that
662
+ address the reader.
658
663
 
659
664
  ```tsx
660
665
  import { describeDevulturError } from "@devultur/convex";
@@ -0,0 +1,185 @@
1
+ import {
2
+ formatDuration,
3
+ formatFileSize
4
+ } from "./chunk-QBHIZBOQ.js";
5
+ import {
6
+ describeDevulturError,
7
+ formatBytes,
8
+ formatDate,
9
+ labelText
10
+ } from "./chunk-YYOZ3PDH.js";
11
+
12
+ // src/locales/es.ts
13
+ var LOCALE = "es";
14
+ var devulturErrorLabels = {
15
+ file_too_large: (details) => {
16
+ const limit = typeof details.limit === "number" ? formatBytes(details.limit, LOCALE) : null;
17
+ return limit ? `El archivo supera el tama\xF1o m\xE1ximo (${limit}).` : "El archivo supera el tama\xF1o m\xE1ximo.";
18
+ },
19
+ content_type_not_allowed: "Este tipo de archivo no est\xE1 permitido.",
20
+ content_type_mismatch: "El contenido del archivo no coincide con su tipo.",
21
+ content_unverified: "No se pudo verificar la subida. Vuelve a intentarlo.",
22
+ unsupported_content_type: "Este tipo de archivo no es compatible.",
23
+ media_type_mismatch: "El tipo de archivo no coincide con el tipo de contenido esperado.",
24
+ already_exists: "Ya existe un archivo con este nombre.",
25
+ write_conflict: "El archivo cambi\xF3 mientras se guardaba. Vuelve a intentarlo.",
26
+ upload_not_found: "No se encontr\xF3 la subida o ya venci\xF3. Vuelve a empezarla.",
27
+ invalid_upload_key: "Esta subida no es v\xE1lida.",
28
+ invalid_path: "La ruta del archivo no es v\xE1lida.",
29
+ path_required: "Esta subida necesita una ruta de archivo.",
30
+ invalid_expires_in: "El tiempo de vencimiento no es v\xE1lido.",
31
+ invalid_file_expires_in: "El tiempo de vencimiento del archivo no es v\xE1lido.",
32
+ invalid_cache_control: "La configuraci\xF3n de cach\xE9 no es v\xE1lida.",
33
+ invalid_limits: "Los l\xEDmites de la subida no son v\xE1lidos.",
34
+ invalid_prefix: "La carpeta no es v\xE1lida.",
35
+ invalid_visibility: "La visibilidad debe ser p\xFAblica o privada.",
36
+ invalid_cursor: "La p\xE1gina de resultados no es v\xE1lida. Vuelve a cargar la lista.",
37
+ invalid_sha256: "La suma de verificaci\xF3n del archivo no es v\xE1lida.",
38
+ sha256_mismatch: "El archivo se da\xF1\xF3 durante la subida. Vuelve a intentarlo.",
39
+ max_files_reached: "Alcanzaste el n\xFAmero m\xE1ximo de archivos para esta subida.",
40
+ invalid_multipart: "La solicitud de subida no es v\xE1lida.",
41
+ invalid_size: "El tama\xF1o del archivo no es v\xE1lido.",
42
+ invalid_part: "Una parte de la subida no es v\xE1lida. Vuelve a intentarlo.",
43
+ invalid_parts: "La subida est\xE1 incompleta. Vuelve a intentarlo.",
44
+ part_size_mismatch: "Una parte de la subida tiene un tama\xF1o incorrecto. Vuelve a intentarlo.",
45
+ part_sha256_required: "A una parte de la subida le falta la suma de verificaci\xF3n.",
46
+ invalid_media_metadata: (details) => {
47
+ if (details.field === "title") return "El t\xEDtulo no puede tener m\xE1s de 200 caracteres.";
48
+ if (details.field === "alt") return "El texto alternativo no puede tener m\xE1s de 1000 caracteres.";
49
+ if (details.field === "tags") return "Usa como m\xE1ximo 20 etiquetas, de hasta 50 caracteres cada una.";
50
+ return "Los datos del archivo no son v\xE1lidos.";
51
+ },
52
+ expired: "Este archivo ya venci\xF3.",
53
+ invalid_key: "La referencia del archivo no es v\xE1lida.",
54
+ invalid_max_uses: "El n\xFAmero de usos no es v\xE1lido.",
55
+ link_exists: "Este enlace de descarga ya existe.",
56
+ link_not_found: "Este enlace de descarga no existe.",
57
+ link_expired: "Este enlace de descarga ya venci\xF3.",
58
+ link_used: "Este enlace de descarga ya se us\xF3.",
59
+ not_configured: "Esta funci\xF3n no est\xE1 configurada.",
60
+ retention_active: (details) => {
61
+ if (details.hold === true) return "Este archivo tiene una retenci\xF3n legal y no se puede modificar ni eliminar.";
62
+ const until = formatDate(details.until, LOCALE);
63
+ return until ? `Este archivo est\xE1 protegido hasta el ${until} y no se puede modificar ni eliminar antes de esa fecha.` : "Este archivo est\xE1 protegido y no se puede modificar ni eliminar.";
64
+ },
65
+ lock_conflict: "La protecci\xF3n del archivo cambi\xF3 mientras tanto. Vuelve a intentarlo.",
66
+ compliance_extend_only: "La protecci\xF3n de este archivo solo se puede extender.",
67
+ lock_privilege_required: "No tienes permiso para acortar ni quitar la protecci\xF3n de este archivo.",
68
+ lock_record_unreadable: "No se pudo leer la protecci\xF3n del archivo.",
69
+ lock_below_minimum: "Esta subida requiere una protecci\xF3n m\xE1s estricta.",
70
+ invalid_lock: "La configuraci\xF3n de protecci\xF3n no es v\xE1lida.",
71
+ invalid_hold: "La configuraci\xF3n de retenci\xF3n legal no es v\xE1lida.",
72
+ reserved_key: "Este nombre de archivo est\xE1 reservado.",
73
+ keys_bucket_missing: "En este momento no se pueden guardar archivos de forma segura.",
74
+ encryption_key_missing: "Este archivo ya no se puede leer. Vuelve a subirlo.",
75
+ unauthorized: "No tienes autorizaci\xF3n para hacer esto.",
76
+ session_mismatch: "Tu sesi\xF3n cambi\xF3. Vuelve a cargar la p\xE1gina.",
77
+ token_rejected: "Se deneg\xF3 el acceso.",
78
+ reserved_user_id: "Este identificador de usuario est\xE1 reservado.",
79
+ invalid_scopes: "Los permisos solicitados no son v\xE1lidos.",
80
+ user_mismatch: "Solo puedes ver y cambiar tu propio progreso.",
81
+ transcode_not_configured: "El procesamiento de video no est\xE1 configurado.",
82
+ transcode_runner_unavailable: "El procesamiento de video est\xE1 ocupado en este momento. Vuelve a intentarlo m\xE1s tarde.",
83
+ transcode_launch_failed: "No se pudo procesar el video.",
84
+ original_not_retained: "El video original ya no est\xE1 disponible, as\xED que no se puede volver a procesar. Vuelve a subirlo.",
85
+ locale_not_allowed: "Los subt\xEDtulos no est\xE1n disponibles en este idioma.",
86
+ captions_provider_rate_limited: "El servicio de subt\xEDtulos est\xE1 ocupado en este momento. Vuelve a intentarlo m\xE1s tarde.",
87
+ captions_provider_error: "No se pudieron generar los subt\xEDtulos.",
88
+ captions_failed: "No se pudieron generar los subt\xEDtulos.",
89
+ webhook_secret_missing: "El procesamiento de video no est\xE1 configurado.",
90
+ worker_auth_secret_missing: "El servicio de medios no est\xE1 configurado.",
91
+ worker_error: "El servicio de medios no pudo completar la solicitud.",
92
+ http_error: "No se pudo cargar el archivo.",
93
+ too_large: "El archivo es demasiado grande para la vista previa.",
94
+ unsupported_type: "Este tipo de archivo no tiene vista previa.",
95
+ type_mismatch: "El contenido del archivo no coincide con su tipo.",
96
+ token_unavailable: "No se pudo autorizar el acceso a este archivo. Vuelve a intentarlo.",
97
+ page_cap: (details) => {
98
+ const { shown, total } = details;
99
+ if (typeof shown !== "number" || typeof total !== "number") return "Solo se muestran las primeras p\xE1ginas.";
100
+ return `Solo se muestran las primeras ${shown.toLocaleString(LOCALE)} de ${total.toLocaleString(LOCALE)} p\xE1ginas.`;
101
+ },
102
+ render_failed: "No se pudo mostrar el archivo.",
103
+ capability_unavailable: "Esta aplicaci\xF3n no puede hacer eso aqu\xED."
104
+ };
105
+ var previewLabels = {
106
+ document: (title, pageCount) => `${title}, ${pagesText(pageCount)}`,
107
+ page: (page, pageCount) => `P\xE1gina ${page} de ${pageCount}`,
108
+ loading: "Cargando\u2026",
109
+ pageCap: (shown, total) => `Se muestran las primeras ${shown} de ${total} p\xE1ginas.`,
110
+ error: (code) => code === "page_cap" ? "Descarga el archivo para ver todas las p\xE1ginas." : labelText(devulturErrorLabels[code]),
111
+ thumbnail: (title) => title,
112
+ untitledPdf: "PDF",
113
+ untitledMedia: "Vista previa",
114
+ download: "Descargar"
115
+ };
116
+ function pagesText(pageCount) {
117
+ return pageCount === 1 ? "1 p\xE1gina" : `${pageCount.toLocaleString(LOCALE)} p\xE1ginas`;
118
+ }
119
+ var KIND_TEXT = {
120
+ image: "Imagen",
121
+ video: "Video",
122
+ pdf: "PDF",
123
+ audio: "Audio",
124
+ text: "Texto",
125
+ file: "Archivo"
126
+ };
127
+ var attachmentLabels = {
128
+ locale: LOCALE,
129
+ size: formatFileSize,
130
+ pages: pagesText,
131
+ duration: formatDuration,
132
+ kind: (kind) => KIND_TEXT[kind],
133
+ download: "Descargar",
134
+ open: "Abrir vista previa",
135
+ untitled: "Archivo sin t\xEDtulo"
136
+ };
137
+ var mediaLibraryLabels = {
138
+ search: "Buscar archivos",
139
+ searchPlaceholder: "Busca por t\xEDtulo o nombre de archivo",
140
+ typeFilter: "Tipo",
141
+ allTypes: "Todos los tipos",
142
+ types: { image: "Im\xE1genes", video: "Videos", file: "Archivos" },
143
+ results: "Archivos",
144
+ loading: "Cargando archivos\u2026",
145
+ empty: "Todav\xEDa no hay archivos.",
146
+ noResults: (search) => `Nada coincide con \xAB${search}\xBB.`,
147
+ loadMore: "Cargar m\xE1s",
148
+ loadingMore: "Cargando\u2026",
149
+ error: (error) => describeDevulturError(error, devulturErrorLabels) ?? "No se pudieron cargar los archivos.",
150
+ retry: "Reintentar",
151
+ size: (bytes) => formatFileSize(bytes, LOCALE)
152
+ };
153
+ var mediaPickerLabels = {
154
+ upload: (multiple) => multiple ? "Subir archivos" : "Subir un archivo",
155
+ dropHint: (multiple) => multiple ? "o suelta los archivos aqu\xED" : "o suelta un archivo aqu\xED",
156
+ uploads: "Subidas",
157
+ progress: (filename) => `Subiendo ${filename}`,
158
+ percent: (percent) => `${percent} %`,
159
+ uploading: (filename) => `Subiendo ${filename}.`,
160
+ uploaded: (filename) => `${filename} se subi\xF3 y qued\xF3 seleccionado.`,
161
+ cancel: "Cancelar",
162
+ cancelUpload: (filename) => `Cancelar la subida de ${filename}`,
163
+ cancelled: (filename) => `Se cancel\xF3 la subida de ${filename}.`,
164
+ notAccepted: (filename) => `${filename} no es un tipo de archivo que se pueda elegir aqu\xED.`,
165
+ uploadError: (error) => describeDevulturError(error, devulturErrorLabels) ?? "La subida fall\xF3. Vuelve a intentarlo.",
166
+ retry: "Reintentar",
167
+ dismiss: "Descartar",
168
+ selection: "Archivos seleccionados",
169
+ empty: "No hay nada seleccionado.",
170
+ remove: "Quitar",
171
+ removeItem: (name) => `Quitar ${name}`,
172
+ clear: "Limpiar",
173
+ replace: "Reemplazar",
174
+ selected: (name) => `${name} seleccionado.`,
175
+ removed: (name) => `${name} se quit\xF3.`,
176
+ cleared: "Se limpi\xF3 la selecci\xF3n."
177
+ };
178
+
179
+ export {
180
+ devulturErrorLabels,
181
+ previewLabels,
182
+ attachmentLabels,
183
+ mediaLibraryLabels,
184
+ mediaPickerLabels
185
+ };
@@ -81,6 +81,8 @@ var DEVULTUR_ERROR_CODES = [
81
81
  "unsupported_type",
82
82
  "type_mismatch",
83
83
  "token_unavailable",
84
+ "page_cap",
85
+ "render_failed",
84
86
  // `DevulturCapabilityError` (an action the provider wasn't given)
85
87
  "capability_unavailable"
86
88
  ];
@@ -104,6 +106,9 @@ function devulturErrorOf(error) {
104
106
  }
105
107
 
106
108
  // src/error-labels.ts
109
+ function labelText(label, details = {}) {
110
+ return typeof label === "function" ? label(details) : label;
111
+ }
107
112
  function formatBytes(bytes, locale) {
108
113
  const isMegabytes = bytes >= 1e6;
109
114
  const amount = isMegabytes ? bytes / 1e6 : bytes / 1e3;
@@ -202,6 +207,12 @@ var devulturErrorLabels = {
202
207
  unsupported_type: "This file type can't be previewed.",
203
208
  type_mismatch: "The file's content doesn't match its type.",
204
209
  token_unavailable: "Access to this file couldn't be granted. Try again.",
210
+ page_cap: (details) => {
211
+ const { shown, total } = details;
212
+ if (typeof shown !== "number" || typeof total !== "number") return "Only the first pages are shown.";
213
+ return `Only the first ${shown.toLocaleString("en")} of ${total.toLocaleString("en")} pages are shown.`;
214
+ },
215
+ render_failed: "The file couldn't be displayed.",
205
216
  capability_unavailable: "This app can't do that here."
206
217
  };
207
218
  function asRecord(value) {
@@ -211,7 +222,9 @@ function codedErrorOf(error) {
211
222
  if (!(error instanceof Error)) return null;
212
223
  const { code, status, details, limit } = error;
213
224
  const statusField = typeof status === "number" ? { status } : {};
214
- if (error.name === "MediaFetchError" && typeof code === "string") return { code, details: statusField };
225
+ if (error.name === "MediaFetchError" && typeof code === "string") {
226
+ return { code, details: { ...asRecord(details), ...statusField } };
227
+ }
215
228
  if (error.name === "DevulturCapabilityError") return { code: "capability_unavailable", details: {} };
216
229
  if (error.name !== "DevulturApiError") return null;
217
230
  const limitField = typeof limit === "number" ? { limit } : {};
@@ -229,8 +242,7 @@ function describeDevulturError(error, labels) {
229
242
  const descriptor = descriptorOf(error);
230
243
  if (!descriptor) return null;
231
244
  const code = isDevulturErrorCode(descriptor.code) ? descriptor.code : "worker_error";
232
- const label = labels?.[code] ?? devulturErrorLabels[code];
233
- return typeof label === "function" ? label(descriptor.details) : label;
245
+ return labelText(labels?.[code] ?? devulturErrorLabels[code], descriptor.details);
234
246
  }
235
247
 
236
248
  export {
@@ -238,6 +250,7 @@ export {
238
250
  isDevulturErrorCode,
239
251
  isDevulturError,
240
252
  devulturErrorOf,
253
+ labelText,
241
254
  formatBytes,
242
255
  formatDate,
243
256
  devulturErrorLabels,
@@ -1,4 +1,4 @@
1
- declare const DEVULTUR_ERROR_CODES: readonly ["file_too_large", "content_type_not_allowed", "content_type_mismatch", "content_unverified", "unsupported_content_type", "media_type_mismatch", "already_exists", "write_conflict", "upload_not_found", "invalid_upload_key", "invalid_path", "path_required", "invalid_expires_in", "invalid_file_expires_in", "invalid_cache_control", "invalid_limits", "invalid_prefix", "invalid_visibility", "invalid_cursor", "invalid_sha256", "sha256_mismatch", "max_files_reached", "invalid_multipart", "invalid_size", "invalid_part", "invalid_parts", "part_size_mismatch", "part_sha256_required", "invalid_media_metadata", "expired", "invalid_key", "invalid_max_uses", "link_exists", "link_not_found", "link_expired", "link_used", "not_configured", "retention_active", "lock_conflict", "compliance_extend_only", "lock_privilege_required", "lock_record_unreadable", "lock_below_minimum", "invalid_lock", "invalid_hold", "reserved_key", "keys_bucket_missing", "encryption_key_missing", "unauthorized", "session_mismatch", "token_rejected", "reserved_user_id", "invalid_scopes", "user_mismatch", "transcode_not_configured", "transcode_runner_unavailable", "transcode_launch_failed", "original_not_retained", "locale_not_allowed", "captions_provider_rate_limited", "captions_provider_error", "captions_failed", "webhook_secret_missing", "worker_auth_secret_missing", "worker_error", "http_error", "too_large", "unsupported_type", "type_mismatch", "token_unavailable", "capability_unavailable"];
1
+ declare const DEVULTUR_ERROR_CODES: readonly ["file_too_large", "content_type_not_allowed", "content_type_mismatch", "content_unverified", "unsupported_content_type", "media_type_mismatch", "already_exists", "write_conflict", "upload_not_found", "invalid_upload_key", "invalid_path", "path_required", "invalid_expires_in", "invalid_file_expires_in", "invalid_cache_control", "invalid_limits", "invalid_prefix", "invalid_visibility", "invalid_cursor", "invalid_sha256", "sha256_mismatch", "max_files_reached", "invalid_multipart", "invalid_size", "invalid_part", "invalid_parts", "part_size_mismatch", "part_sha256_required", "invalid_media_metadata", "expired", "invalid_key", "invalid_max_uses", "link_exists", "link_not_found", "link_expired", "link_used", "not_configured", "retention_active", "lock_conflict", "compliance_extend_only", "lock_privilege_required", "lock_record_unreadable", "lock_below_minimum", "invalid_lock", "invalid_hold", "reserved_key", "keys_bucket_missing", "encryption_key_missing", "unauthorized", "session_mismatch", "token_rejected", "reserved_user_id", "invalid_scopes", "user_mismatch", "transcode_not_configured", "transcode_runner_unavailable", "transcode_launch_failed", "original_not_retained", "locale_not_allowed", "captions_provider_rate_limited", "captions_provider_error", "captions_failed", "webhook_secret_missing", "worker_auth_secret_missing", "worker_error", "http_error", "too_large", "unsupported_type", "type_mismatch", "token_unavailable", "page_cap", "render_failed", "capability_unavailable"];
2
2
  /** A stable error code: match on it, not on `message`. */
3
3
  type DevulturErrorCode = (typeof DEVULTUR_ERROR_CODES)[number];
4
4
  declare function isDevulturErrorCode(code: unknown): code is DevulturErrorCode;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { FunctionReference, FunctionVisibility, DefaultFunctionArgs, GenericQuer
4
4
  import { PropertyValidators, Value, ConvexError } from 'convex/values';
5
5
  import { D as DevulturComponentConfig, M as MediaRecord } from './types-LrusGVDf.js';
6
6
  export { P as ProgressRecord } from './types-LrusGVDf.js';
7
- export { a as DEVULTUR_ERROR_CODES, b as DevulturErrorCode, c as DevulturErrorDetails, d as DevulturErrorLabel, D as DevulturErrorLabels, e as describeDevulturError, f as devulturErrorLabels, i as isDevulturErrorCode } from './error-labels-BfqcFh2R.js';
7
+ export { a as DEVULTUR_ERROR_CODES, b as DevulturErrorCode, c as DevulturErrorDetails, d as DevulturErrorLabel, D as DevulturErrorLabels, e as describeDevulturError, f as devulturErrorLabels, i as isDevulturErrorCode } from './error-labels-D0Vbp-qA.js';
8
8
  export { dv } from './validators.js';
9
9
 
10
10
  /**
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  devulturErrorOf,
12
12
  isDevulturError,
13
13
  isDevulturErrorCode
14
- } from "./chunk-EX3W77VY.js";
14
+ } from "./chunk-YYOZ3PDH.js";
15
15
 
16
16
  // src/index.ts
17
17
  import {
@@ -0,0 +1,12 @@
1
+ import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels, P as PreviewLabels } from '../preview-labels-B-2mra1h.js';
2
+ import { D as DevulturErrorLabels } from '../error-labels-D0Vbp-qA.js';
3
+ import '../types-LrusGVDf.js';
4
+ import '@devultur/core';
5
+
6
+ declare const devulturErrorLabels: DevulturErrorLabels;
7
+ declare const attachmentLabels: AttachmentLabels;
8
+ declare const previewLabels: PreviewLabels;
9
+ declare const mediaLibraryLabels: MediaLibraryLabels;
10
+ declare const mediaPickerLabels: MediaPickerLabels;
11
+
12
+ export { attachmentLabels, devulturErrorLabels, mediaLibraryLabels, mediaPickerLabels, previewLabels };
@@ -0,0 +1,64 @@
1
+ import {
2
+ attachmentLabels,
3
+ devulturErrorLabels,
4
+ mediaLibraryLabels,
5
+ mediaPickerLabels,
6
+ previewLabels
7
+ } from "../chunk-JZYKPLI4.js";
8
+ import "../chunk-QBHIZBOQ.js";
9
+ import {
10
+ describeDevulturError,
11
+ labelText
12
+ } from "../chunk-YYOZ3PDH.js";
13
+
14
+ // src/locales/es-formal.ts
15
+ var TRY_AGAIN = "Vuelva a intentarlo.";
16
+ var TRY_AGAIN_LATER = "Vuelva a intentarlo m\xE1s tarde.";
17
+ var devulturErrorLabels2 = {
18
+ ...devulturErrorLabels,
19
+ content_unverified: `No se pudo verificar la subida. ${TRY_AGAIN}`,
20
+ write_conflict: `El archivo cambi\xF3 mientras se guardaba. ${TRY_AGAIN}`,
21
+ upload_not_found: "No se encontr\xF3 la subida o ya venci\xF3. Vuelva a empezarla.",
22
+ invalid_cursor: "La p\xE1gina de resultados no es v\xE1lida. Vuelva a cargar la lista.",
23
+ sha256_mismatch: `El archivo se da\xF1\xF3 durante la subida. ${TRY_AGAIN}`,
24
+ max_files_reached: "Alcanz\xF3 el n\xFAmero m\xE1ximo de archivos para esta subida.",
25
+ invalid_part: `Una parte de la subida no es v\xE1lida. ${TRY_AGAIN}`,
26
+ invalid_parts: `La subida est\xE1 incompleta. ${TRY_AGAIN}`,
27
+ part_size_mismatch: `Una parte de la subida tiene un tama\xF1o incorrecto. ${TRY_AGAIN}`,
28
+ invalid_media_metadata: (details) => {
29
+ if (details.field === "tags") return "Use como m\xE1ximo 20 etiquetas, de hasta 50 caracteres cada una.";
30
+ return labelText(devulturErrorLabels.invalid_media_metadata, details);
31
+ },
32
+ lock_conflict: `La protecci\xF3n del archivo cambi\xF3 mientras tanto. ${TRY_AGAIN}`,
33
+ lock_privilege_required: "No tiene permiso para acortar ni quitar la protecci\xF3n de este archivo.",
34
+ encryption_key_missing: "Este archivo ya no se puede leer. Vuelva a subirlo.",
35
+ unauthorized: "No tiene autorizaci\xF3n para hacer esto.",
36
+ session_mismatch: "Su sesi\xF3n cambi\xF3. Vuelva a cargar la p\xE1gina.",
37
+ user_mismatch: "Solo puede ver y cambiar su propio progreso.",
38
+ transcode_runner_unavailable: `El procesamiento de video est\xE1 ocupado en este momento. ${TRY_AGAIN_LATER}`,
39
+ original_not_retained: "El video original ya no est\xE1 disponible, as\xED que no se puede volver a procesar. Vuelva a subirlo.",
40
+ captions_provider_rate_limited: `El servicio de subt\xEDtulos est\xE1 ocupado en este momento. ${TRY_AGAIN_LATER}`,
41
+ token_unavailable: `No se pudo autorizar el acceso a este archivo. ${TRY_AGAIN}`
42
+ };
43
+ var attachmentLabels2 = attachmentLabels;
44
+ var previewLabels2 = {
45
+ ...previewLabels,
46
+ error: (code) => code === "page_cap" ? "Descargue el archivo para ver todas las p\xE1ginas." : labelText(devulturErrorLabels2[code])
47
+ };
48
+ var mediaLibraryLabels2 = {
49
+ ...mediaLibraryLabels,
50
+ searchPlaceholder: "Busque por t\xEDtulo o nombre de archivo",
51
+ error: (error) => describeDevulturError(error, devulturErrorLabels2) ?? "No se pudieron cargar los archivos."
52
+ };
53
+ var mediaPickerLabels2 = {
54
+ ...mediaPickerLabels,
55
+ dropHint: (multiple) => multiple ? "o suelte los archivos aqu\xED" : "o suelte un archivo aqu\xED",
56
+ uploadError: (error) => describeDevulturError(error, devulturErrorLabels2) ?? `La subida fall\xF3. ${TRY_AGAIN}`
57
+ };
58
+ export {
59
+ attachmentLabels2 as attachmentLabels,
60
+ devulturErrorLabels2 as devulturErrorLabels,
61
+ mediaLibraryLabels2 as mediaLibraryLabels,
62
+ mediaPickerLabels2 as mediaPickerLabels,
63
+ previewLabels2 as previewLabels
64
+ };
@@ -1,11 +1,12 @@
1
- import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels } from '../media-picker-labels-CiVcNUZo.js';
2
- import { D as DevulturErrorLabels } from '../error-labels-BfqcFh2R.js';
1
+ import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels, P as PreviewLabels } from '../preview-labels-B-2mra1h.js';
2
+ import { D as DevulturErrorLabels } from '../error-labels-D0Vbp-qA.js';
3
3
  import '../types-LrusGVDf.js';
4
4
  import '@devultur/core';
5
5
 
6
6
  declare const devulturErrorLabels: DevulturErrorLabels;
7
+ declare const previewLabels: PreviewLabels;
7
8
  declare const attachmentLabels: AttachmentLabels;
8
9
  declare const mediaLibraryLabels: MediaLibraryLabels;
9
10
  declare const mediaPickerLabels: MediaPickerLabels;
10
11
 
11
- export { attachmentLabels, devulturErrorLabels, mediaLibraryLabels, mediaPickerLabels };
12
+ export { attachmentLabels, devulturErrorLabels, mediaLibraryLabels, mediaPickerLabels, previewLabels };
@@ -1,162 +1,16 @@
1
1
  import {
2
- formatDuration,
3
- formatFileSize
4
- } from "../chunk-QBHIZBOQ.js";
5
- import {
6
- describeDevulturError,
7
- formatBytes,
8
- formatDate
9
- } from "../chunk-EX3W77VY.js";
10
-
11
- // src/locales/es.ts
12
- var LOCALE = "es";
13
- var devulturErrorLabels = {
14
- file_too_large: (details) => {
15
- const limit = typeof details.limit === "number" ? formatBytes(details.limit, LOCALE) : null;
16
- return limit ? `El archivo supera el tama\xF1o m\xE1ximo (${limit}).` : "El archivo supera el tama\xF1o m\xE1ximo.";
17
- },
18
- content_type_not_allowed: "Este tipo de archivo no est\xE1 permitido.",
19
- content_type_mismatch: "El contenido del archivo no coincide con su tipo.",
20
- content_unverified: "No se pudo verificar la subida. Vuelve a intentarlo.",
21
- unsupported_content_type: "Este tipo de archivo no es compatible.",
22
- media_type_mismatch: "El tipo de archivo no coincide con el tipo de contenido esperado.",
23
- already_exists: "Ya existe un archivo con este nombre.",
24
- write_conflict: "El archivo cambi\xF3 mientras se guardaba. Vuelve a intentarlo.",
25
- upload_not_found: "No se encontr\xF3 la subida o ya venci\xF3. Vuelve a empezarla.",
26
- invalid_upload_key: "Esta subida no es v\xE1lida.",
27
- invalid_path: "La ruta del archivo no es v\xE1lida.",
28
- path_required: "Esta subida necesita una ruta de archivo.",
29
- invalid_expires_in: "El tiempo de vencimiento no es v\xE1lido.",
30
- invalid_file_expires_in: "El tiempo de vencimiento del archivo no es v\xE1lido.",
31
- invalid_cache_control: "La configuraci\xF3n de cach\xE9 no es v\xE1lida.",
32
- invalid_limits: "Los l\xEDmites de la subida no son v\xE1lidos.",
33
- invalid_prefix: "La carpeta no es v\xE1lida.",
34
- invalid_visibility: "La visibilidad debe ser p\xFAblica o privada.",
35
- invalid_cursor: "La p\xE1gina de resultados no es v\xE1lida. Vuelve a cargar la lista.",
36
- invalid_sha256: "La suma de verificaci\xF3n del archivo no es v\xE1lida.",
37
- sha256_mismatch: "El archivo se da\xF1\xF3 durante la subida. Vuelve a intentarlo.",
38
- max_files_reached: "Alcanzaste el n\xFAmero m\xE1ximo de archivos para esta subida.",
39
- invalid_multipart: "La solicitud de subida no es v\xE1lida.",
40
- invalid_size: "El tama\xF1o del archivo no es v\xE1lido.",
41
- invalid_part: "Una parte de la subida no es v\xE1lida. Vuelve a intentarlo.",
42
- invalid_parts: "La subida est\xE1 incompleta. Vuelve a intentarlo.",
43
- part_size_mismatch: "Una parte de la subida tiene un tama\xF1o incorrecto. Vuelve a intentarlo.",
44
- part_sha256_required: "A una parte de la subida le falta la suma de verificaci\xF3n.",
45
- invalid_media_metadata: (details) => {
46
- if (details.field === "title") return "El t\xEDtulo no puede tener m\xE1s de 200 caracteres.";
47
- if (details.field === "alt") return "El texto alternativo no puede tener m\xE1s de 1000 caracteres.";
48
- if (details.field === "tags") return "Usa como m\xE1ximo 20 etiquetas, de hasta 50 caracteres cada una.";
49
- return "Los datos del archivo no son v\xE1lidos.";
50
- },
51
- expired: "Este archivo ya venci\xF3.",
52
- invalid_key: "La referencia del archivo no es v\xE1lida.",
53
- invalid_max_uses: "El n\xFAmero de usos no es v\xE1lido.",
54
- link_exists: "Este enlace de descarga ya existe.",
55
- link_not_found: "Este enlace de descarga no existe.",
56
- link_expired: "Este enlace de descarga ya venci\xF3.",
57
- link_used: "Este enlace de descarga ya se us\xF3.",
58
- not_configured: "Esta funci\xF3n no est\xE1 configurada.",
59
- retention_active: (details) => {
60
- if (details.hold === true) return "Este archivo tiene una retenci\xF3n legal y no se puede modificar ni eliminar.";
61
- const until = formatDate(details.until, LOCALE);
62
- return until ? `Este archivo est\xE1 protegido hasta el ${until} y no se puede modificar ni eliminar antes de esa fecha.` : "Este archivo est\xE1 protegido y no se puede modificar ni eliminar.";
63
- },
64
- lock_conflict: "La protecci\xF3n del archivo cambi\xF3 mientras tanto. Vuelve a intentarlo.",
65
- compliance_extend_only: "La protecci\xF3n de este archivo solo se puede extender.",
66
- lock_privilege_required: "No tienes permiso para acortar ni quitar la protecci\xF3n de este archivo.",
67
- lock_record_unreadable: "No se pudo leer la protecci\xF3n del archivo.",
68
- lock_below_minimum: "Esta subida requiere una protecci\xF3n m\xE1s estricta.",
69
- invalid_lock: "La configuraci\xF3n de protecci\xF3n no es v\xE1lida.",
70
- invalid_hold: "La configuraci\xF3n de retenci\xF3n legal no es v\xE1lida.",
71
- reserved_key: "Este nombre de archivo est\xE1 reservado.",
72
- keys_bucket_missing: "En este momento no se pueden guardar archivos de forma segura.",
73
- encryption_key_missing: "Este archivo ya no se puede leer. Vuelve a subirlo.",
74
- unauthorized: "No tienes autorizaci\xF3n para hacer esto.",
75
- session_mismatch: "Tu sesi\xF3n cambi\xF3. Vuelve a cargar la p\xE1gina.",
76
- token_rejected: "Se deneg\xF3 el acceso.",
77
- reserved_user_id: "Este identificador de usuario est\xE1 reservado.",
78
- invalid_scopes: "Los permisos solicitados no son v\xE1lidos.",
79
- user_mismatch: "Solo puedes ver y cambiar tu propio progreso.",
80
- transcode_not_configured: "El procesamiento de video no est\xE1 configurado.",
81
- transcode_runner_unavailable: "El procesamiento de video est\xE1 ocupado en este momento. Vuelve a intentarlo m\xE1s tarde.",
82
- transcode_launch_failed: "No se pudo procesar el video.",
83
- original_not_retained: "El video original ya no est\xE1 disponible, as\xED que no se puede volver a procesar. Vuelve a subirlo.",
84
- locale_not_allowed: "Los subt\xEDtulos no est\xE1n disponibles en este idioma.",
85
- captions_provider_rate_limited: "El servicio de subt\xEDtulos est\xE1 ocupado en este momento. Vuelve a intentarlo m\xE1s tarde.",
86
- captions_provider_error: "No se pudieron generar los subt\xEDtulos.",
87
- captions_failed: "No se pudieron generar los subt\xEDtulos.",
88
- webhook_secret_missing: "El procesamiento de video no est\xE1 configurado.",
89
- worker_auth_secret_missing: "El servicio de medios no est\xE1 configurado.",
90
- worker_error: "El servicio de medios no pudo completar la solicitud.",
91
- http_error: "No se pudo cargar el archivo.",
92
- too_large: "El archivo es demasiado grande para la vista previa.",
93
- unsupported_type: "Este tipo de archivo no tiene vista previa.",
94
- type_mismatch: "El contenido del archivo no coincide con su tipo.",
95
- token_unavailable: "No se pudo autorizar el acceso a este archivo. Vuelve a intentarlo.",
96
- capability_unavailable: "Esta aplicaci\xF3n no puede hacer eso aqu\xED."
97
- };
98
- var KIND_TEXT = {
99
- image: "Imagen",
100
- video: "Video",
101
- pdf: "PDF",
102
- audio: "Audio",
103
- text: "Texto",
104
- file: "Archivo"
105
- };
106
- var attachmentLabels = {
107
- locale: LOCALE,
108
- size: formatFileSize,
109
- pages: (pageCount) => pageCount === 1 ? "1 p\xE1gina" : `${pageCount.toLocaleString(LOCALE)} p\xE1ginas`,
110
- duration: formatDuration,
111
- kind: (kind) => KIND_TEXT[kind],
112
- download: "Descargar",
113
- open: "Abrir vista previa",
114
- untitled: "Archivo sin t\xEDtulo"
115
- };
116
- var mediaLibraryLabels = {
117
- search: "Buscar archivos",
118
- searchPlaceholder: "Busca por t\xEDtulo o nombre de archivo",
119
- typeFilter: "Tipo",
120
- allTypes: "Todos los tipos",
121
- types: { image: "Im\xE1genes", video: "Videos", file: "Archivos" },
122
- results: "Archivos",
123
- loading: "Cargando archivos\u2026",
124
- empty: "Todav\xEDa no hay archivos.",
125
- noResults: (search) => `Nada coincide con \xAB${search}\xBB.`,
126
- loadMore: "Cargar m\xE1s",
127
- loadingMore: "Cargando\u2026",
128
- error: (error) => describeDevulturError(error, devulturErrorLabels) ?? "No se pudieron cargar los archivos.",
129
- retry: "Reintentar",
130
- size: (bytes) => formatFileSize(bytes, LOCALE)
131
- };
132
- var mediaPickerLabels = {
133
- upload: (multiple) => multiple ? "Subir archivos" : "Subir un archivo",
134
- dropHint: (multiple) => multiple ? "o suelta los archivos aqu\xED" : "o suelta un archivo aqu\xED",
135
- uploads: "Subidas",
136
- progress: (filename) => `Subiendo ${filename}`,
137
- percent: (percent) => `${percent} %`,
138
- uploading: (filename) => `Subiendo ${filename}.`,
139
- uploaded: (filename) => `${filename} se subi\xF3 y qued\xF3 seleccionado.`,
140
- cancel: "Cancelar",
141
- cancelUpload: (filename) => `Cancelar la subida de ${filename}`,
142
- cancelled: (filename) => `Se cancel\xF3 la subida de ${filename}.`,
143
- notAccepted: (filename) => `${filename} no es un tipo de archivo que se pueda elegir aqu\xED.`,
144
- uploadError: (error) => describeDevulturError(error, devulturErrorLabels) ?? "La subida fall\xF3. Vuelve a intentarlo.",
145
- retry: "Reintentar",
146
- dismiss: "Descartar",
147
- selection: "Archivos seleccionados",
148
- empty: "No hay nada seleccionado.",
149
- remove: "Quitar",
150
- removeItem: (name) => `Quitar ${name}`,
151
- clear: "Limpiar",
152
- replace: "Reemplazar",
153
- selected: (name) => `${name} seleccionado.`,
154
- removed: (name) => `${name} se quit\xF3.`,
155
- cleared: "Se limpi\xF3 la selecci\xF3n."
156
- };
2
+ attachmentLabels,
3
+ devulturErrorLabels,
4
+ mediaLibraryLabels,
5
+ mediaPickerLabels,
6
+ previewLabels
7
+ } from "../chunk-JZYKPLI4.js";
8
+ import "../chunk-QBHIZBOQ.js";
9
+ import "../chunk-YYOZ3PDH.js";
157
10
  export {
158
11
  attachmentLabels,
159
12
  devulturErrorLabels,
160
13
  mediaLibraryLabels,
161
- mediaPickerLabels
14
+ mediaPickerLabels,
15
+ previewLabels
162
16
  };
@@ -111,4 +111,61 @@ interface MediaPickerLabels {
111
111
  }
112
112
  declare const defaultMediaPickerLabels: MediaPickerLabels;
113
113
 
114
- export { type AttachmentLabels as A, type MediaLibraryLabels as M, type MediaPickerLabels as a, type AttachmentKind as b, defaultMediaLibraryLabels as c, defaultAttachmentLabels as d, defaultMediaPickerLabels as e, formatDuration as f, formatFileSize as g };
114
+ /** Why a media couldn't be fetched or shown. Stable: match on it, not on `message`. */
115
+ type MediaFetchErrorCode =
116
+ /** The delivery answered with a non-2xx status (`status` has it), or never answered (a network failure: no `status`). */
117
+ "http_error"
118
+ /** `Content-Length`, or the streamed body, exceeds `maxBytes`. */
119
+ | "too_large"
120
+ /** The expected content type isn't one that can be previewed inline. */
121
+ | "unsupported_type"
122
+ /** The response's `Content-Type` doesn't agree with the expected one. */
123
+ | "type_mismatch"
124
+ /** No viewer token for private media: its issuance failed (`useDevultur().tokenError` has why) or was refused. */
125
+ | "token_unavailable"
126
+ /** The delivery refused the token for this browser session, also after a re-issue (video playback). */
127
+ | "session_mismatch"
128
+ /** A PDF has more pages than `maxPages`: only the first ones are shown (`details: { shown, total }`). */
129
+ | "page_cap"
130
+ /** The bytes arrived but couldn't be shown: pdf.js can't open the PDF, or the video can't be played. */
131
+ | "render_failed";
132
+ /** `ErrorOptions` plus the code's `details`. */
133
+ interface MediaFetchErrorOptions extends ErrorOptions {
134
+ details?: Readonly<Record<string, unknown>>;
135
+ }
136
+ declare class MediaFetchError extends Error {
137
+ readonly code: MediaFetchErrorCode;
138
+ /** HTTP status, for `http_error` when the delivery answered. */
139
+ readonly status?: number;
140
+ /** The code's extra fields, e.g. `page_cap`'s `{ shown, total }`. */
141
+ readonly details?: Readonly<Record<string, unknown>>;
142
+ constructor(code: MediaFetchErrorCode, message: string, status?: number, options?: MediaFetchErrorOptions);
143
+ }
144
+
145
+ /**
146
+ * Every string `<Preview>`, `<PdfThumbnail>` and their PDF pages show or announce. Pass any subset as `labels` on a
147
+ * component or on `PdfPreviewProvider`; the component's wins over the provider's, which wins over the English defaults.
148
+ */
149
+ interface PreviewLabels {
150
+ /** Accessible name of a pdf.js document: its title and page count. */
151
+ document: (title: string, pageCount: number) => string;
152
+ /** Accessible name of one pdf.js page. */
153
+ page: (page: number, pageCount: number) => string;
154
+ /** Announced (visually hidden) while a preview loads, when no `loading` is passed. */
155
+ loading: string;
156
+ /** Note after the last rendered page when a PDF has more than `maxPages`; `fallback` follows it. */
157
+ pageCap: (shown: number, total: number) => string;
158
+ /** `title` of the default fallback's download link, for why it's shown (see `<Preview>`'s `fallback`). */
159
+ error: (code: MediaFetchErrorCode) => string;
160
+ /** Accessible name of a `<PdfThumbnail>`, from its title. */
161
+ thumbnail: (title: string) => string;
162
+ /** Name of a PDF (pdf.js document, `<iframe>` title, thumbnail) with no `title` and no filename. */
163
+ untitledPdf: string;
164
+ /** Name of an image or audio preview with no `title` and no filename. */
165
+ untitledMedia: string;
166
+ /** Text of the default fallback's download link when the media has no filename. */
167
+ download: string;
168
+ }
169
+ declare const defaultPreviewLabels: PreviewLabels;
170
+
171
+ export { type AttachmentLabels as A, type MediaLibraryLabels as M, type PreviewLabels as P, type MediaPickerLabels as a, MediaFetchError as b, type AttachmentKind as c, type MediaFetchErrorCode as d, defaultAttachmentLabels as e, defaultMediaLibraryLabels as f, defaultMediaPickerLabels as g, defaultPreviewLabels as h, formatDuration as i, formatFileSize as j };
package/dist/react.d.ts CHANGED
@@ -3,34 +3,13 @@ import { DevulturMedia, MediaUrlOptions, Visibility, ProcessingPhase, FailureRea
3
3
  import { BakeOutputFormat, MediaUploadState, MediaUploadError, UploadProgress } from '@devultur/react';
4
4
  export { MediaUploadError, MediaUploadState, UploadProgress } from '@devultur/react';
5
5
  import { FunctionReference } from 'convex/server';
6
+ import { A as AttachmentLabels, M as MediaLibraryLabels, P as PreviewLabels, a as MediaPickerLabels, b as MediaFetchError } from './preview-labels-B-2mra1h.js';
7
+ export { c as AttachmentKind, d as MediaFetchErrorCode, e as defaultAttachmentLabels, f as defaultMediaLibraryLabels, g as defaultMediaPickerLabels, h as defaultPreviewLabels, i as formatDuration, j as formatFileSize } from './preview-labels-B-2mra1h.js';
6
8
  import { Value } from 'convex/values';
7
- import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels } from './media-picker-labels-CiVcNUZo.js';
8
- export { b as AttachmentKind, d as defaultAttachmentLabels, c as defaultMediaLibraryLabels, e as defaultMediaPickerLabels, f as formatDuration, g as formatFileSize } from './media-picker-labels-CiVcNUZo.js';
9
9
  import { M as MediaRecord } from './types-LrusGVDf.js';
10
10
  import { P as PdfjsLoader } from './pdf-render-BwJGagvz.js';
11
11
  export { a as PdfjsModule } from './pdf-render-BwJGagvz.js';
12
12
 
13
- /** Why a media fetch was refused. Stable: match on it, not on `message`. */
14
- type MediaFetchErrorCode =
15
- /** The delivery answered with a non-2xx status (`status` has it). */
16
- "http_error"
17
- /** `Content-Length`, or the streamed body, exceeds `maxBytes`. */
18
- | "too_large"
19
- /** The expected content type isn't one that can be previewed inline. */
20
- | "unsupported_type"
21
- /** The response's `Content-Type` doesn't agree with the expected one. */
22
- | "type_mismatch"
23
- /** No viewer token for private media: its issuance failed (`useDevultur().tokenError` has why) or was refused. */
24
- | "token_unavailable"
25
- /** The delivery refused the token for this browser session, also after a re-issue (video playback). */
26
- | "session_mismatch";
27
- declare class MediaFetchError extends Error {
28
- readonly code: MediaFetchErrorCode;
29
- /** HTTP status, for `http_error`. */
30
- readonly status?: number;
31
- constructor(code: MediaFetchErrorCode, message: string, status?: number, options?: ErrorOptions);
32
- }
33
-
34
13
  /** What a viewer token action returns: the token with its lifetime, or the bare token (default lifetime). */
35
14
  type ViewerTokenResult = {
36
15
  token: string;
@@ -311,32 +290,6 @@ interface MediaObjectUrl {
311
290
  */
312
291
  declare function useMediaObjectUrl(media: DevulturMedia | null | undefined, options?: UseMediaObjectUrlOptions): MediaObjectUrl;
313
292
 
314
- /**
315
- * Every string `<Preview>`, `<PdfThumbnail>` and their PDF pages show or announce. Pass any subset as `labels` on a
316
- * component or on `PdfPreviewProvider`; the component's wins over the provider's, which wins over the English defaults.
317
- */
318
- interface PreviewLabels {
319
- /** Accessible name of a pdf.js document: its title and page count. */
320
- document: (title: string, pageCount: number) => string;
321
- /** Accessible name of one pdf.js page. */
322
- page: (page: number, pageCount: number) => string;
323
- /** Announced (visually hidden) while a preview loads, when no `loading` is passed. */
324
- loading: string;
325
- /** Note after the last rendered page when a PDF has more than `maxPages`; `fallback` follows it. */
326
- pageCap: (shown: number, total: number) => string;
327
- /** `title` of the default fallback's download link when a fetch was refused or the type can't be previewed. */
328
- error: (code: MediaFetchErrorCode) => string;
329
- /** Accessible name of a `<PdfThumbnail>`, from its title. */
330
- thumbnail: (title: string) => string;
331
- /** Name of a PDF (pdf.js document, `<iframe>` title, thumbnail) with no `title` and no filename. */
332
- untitledPdf: string;
333
- /** Name of an image or audio preview with no `title` and no filename. */
334
- untitledMedia: string;
335
- /** Text of the default fallback's download link when the media has no filename. */
336
- download: string;
337
- }
338
- declare const defaultPreviewLabels: PreviewLabels;
339
-
340
293
  interface PdfPreviewConfig {
341
294
  /**
342
295
  * Imports pdf.js. Without it, PDFs render in an `<iframe>` (the browser's own viewer) and `<PdfThumbnail>` renders
@@ -547,13 +500,15 @@ interface PreviewProps {
547
500
  /** Size cap for text previews. Default 1 MiB. */
548
501
  maxTextBytes?: number;
549
502
  /**
550
- * Shown for types that don't preview inline and when the fetch fails. Default: a `<DownloadLink>`.
551
- * As a function it receives why: a `MediaFetchError` when the fetch was refused (`http_error`, `too_large`,
552
- * `type_mismatch`), the viewer token couldn't be issued (`token_unavailable`, nothing fetched), a video's session
553
- * was refused (`session_mismatch`) or the type doesn't preview inline (`unsupported_type`), `null` otherwise
554
- * (a network failure, a PDF pdf.js can't open, or the link after the `maxPages` note).
503
+ * Shown for types that don't preview inline, when the fetch or render fails, and after the `maxPages` note.
504
+ * Default: a `<DownloadLink>`. As a function it receives why, always a `MediaFetchError` (never `null`: it's only
505
+ * called with a cause): `http_error` when the delivery refused the fetch (`status`) or never answered (a network
506
+ * failure, no `status`), `too_large`, `type_mismatch`, `token_unavailable` (no viewer token, nothing fetched),
507
+ * `session_mismatch` (a video's session was refused), `unsupported_type` (the type doesn't preview inline, nothing
508
+ * fetched), `render_failed` (pdf.js can't open the PDF, or the video can't be played) or `page_cap` (the link after
509
+ * the `maxPages` note, with `details: { shown, total }`).
555
510
  */
556
- fallback?: React.ReactNode | ((error: MediaFetchError | null) => React.ReactNode);
511
+ fallback?: React.ReactNode | ((error: MediaFetchError) => React.ReactNode);
557
512
  /** Shown while a file loads. Default: `labels.loading`, visually hidden, in a `role="status"`. `null` → nothing. */
558
513
  loading?: React.ReactNode;
559
514
  /** Accessible name of the preview (`alt`, `title`, `aria-label`). Default: the filename (text: none). */
@@ -869,4 +824,4 @@ interface UseMediaUploadReturn {
869
824
  */
870
825
  declare function useMediaUpload(options?: UseMediaUploadOptions): UseMediaUploadReturn;
871
826
 
872
- export { AttachmentCard, type AttachmentCardProps, AttachmentLabels, type CaptionTrack, CropUpload, type CropUploadLabels, type CropUploadProps, type CropUploadRenderProps, type DevulturApi, type DevulturCapability, DevulturCapabilityError, DevulturProvider, DevulturViewerProvider, DocumentLink, type DocumentLinkProps, DownloadLink, Image, type ImageProps, MediaFetchError, type MediaFetchErrorCode, MediaLibrary, type MediaLibraryItemState, MediaLibraryLabels, type MediaLibraryPart, type MediaLibraryProps, type MediaLibraryState, type MediaLibraryStatus, type MediaObjectUrl, MediaPicker, MediaPickerLabels, type MediaPickerLibraryProps, type MediaPickerProps, type MediaPickerSelectionProps, type MediaPickerState, type MediaPickerUpload$1 as MediaPickerUpload, type MediaPickerUploadProps, type MediaPickerUploads, type PdfPreviewConfig, PdfPreviewProvider, PdfThumbnail, type PdfThumbnailProps, PdfjsLoader, Preview, type PreviewKind, type PreviewLabels, type PreviewProps, type TokenStatus, type UploadedMedia, type UseMediaLibraryOptions, type UseMediaObjectUrlOptions, type UseMediaUploadOptions, type UseMediaUploadReturn, type VideoProps, type VideoState, type ViewerTokenArgs, type ViewerTokenResult, defaultPreviewLabels, matchesAccept, useDevultur, useDevulturApi, useDevulturMedia, useDevulturProgress, useMediaLibrary, useMediaObjectUrl, useMediaPicker, useMediaUpload, useMediaUrl, useVideoProps, useVideoState };
827
+ export { AttachmentCard, type AttachmentCardProps, AttachmentLabels, type CaptionTrack, CropUpload, type CropUploadLabels, type CropUploadProps, type CropUploadRenderProps, type DevulturApi, type DevulturCapability, DevulturCapabilityError, DevulturProvider, DevulturViewerProvider, DocumentLink, type DocumentLinkProps, DownloadLink, Image, type ImageProps, MediaFetchError, MediaLibrary, type MediaLibraryItemState, MediaLibraryLabels, type MediaLibraryPart, type MediaLibraryProps, type MediaLibraryState, type MediaLibraryStatus, type MediaObjectUrl, MediaPicker, MediaPickerLabels, type MediaPickerLibraryProps, type MediaPickerProps, type MediaPickerSelectionProps, type MediaPickerState, type MediaPickerUpload$1 as MediaPickerUpload, type MediaPickerUploadProps, type MediaPickerUploads, type PdfPreviewConfig, PdfPreviewProvider, PdfThumbnail, type PdfThumbnailProps, PdfjsLoader, Preview, type PreviewKind, PreviewLabels, type PreviewProps, type TokenStatus, type UploadedMedia, type UseMediaLibraryOptions, type UseMediaObjectUrlOptions, type UseMediaUploadOptions, type UseMediaUploadReturn, type VideoProps, type VideoState, type ViewerTokenArgs, type ViewerTokenResult, matchesAccept, useDevultur, useDevulturApi, useDevulturMedia, useDevulturProgress, useMediaLibrary, useMediaObjectUrl, useMediaPicker, useMediaUpload, useMediaUrl, useVideoProps, useVideoState };
package/dist/react.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  } from "./chunk-QBHIZBOQ.js";
24
24
  import {
25
25
  describeDevulturError
26
- } from "./chunk-EX3W77VY.js";
26
+ } from "./chunk-YYOZ3PDH.js";
27
27
 
28
28
  // src/react.tsx
29
29
  import React22 from "react";
@@ -71,15 +71,28 @@ async function isSessionMismatch(response) {
71
71
  // src/media-fetch.ts
72
72
  var MediaFetchError = class extends Error {
73
73
  code;
74
- /** HTTP status, for `http_error`. */
74
+ /** HTTP status, for `http_error` when the delivery answered. */
75
75
  status;
76
+ /** The code's extra fields, e.g. `page_cap`'s `{ shown, total }`. */
77
+ details;
76
78
  constructor(code, message, status, options) {
77
79
  super(message, options);
78
80
  this.name = "MediaFetchError";
79
81
  this.code = code;
80
82
  this.status = status;
83
+ this.details = options?.details;
81
84
  }
82
85
  };
86
+ function networkError(cause) {
87
+ return new MediaFetchError("http_error", "Media fetch failed: no response", void 0, { cause });
88
+ }
89
+ function pageCapError(shown, total) {
90
+ const message = `Showing the first ${shown} of ${total} pages`;
91
+ return new MediaFetchError("page_cap", message, void 0, { details: { shown, total } });
92
+ }
93
+ function renderFailedError(cause) {
94
+ return new MediaFetchError("render_failed", "The media couldn't be rendered", void 0, { cause });
95
+ }
83
96
  function tokenUnavailableError(cause) {
84
97
  return new MediaFetchError("token_unavailable", "No viewer token: its issuance failed", void 0, { cause });
85
98
  }
@@ -753,7 +766,9 @@ var ERROR_TEXT = {
753
766
  unsupported_type: "This file type can't be previewed.",
754
767
  type_mismatch: "The file's content doesn't match its type.",
755
768
  token_unavailable: "Access to this file couldn't be granted. Try again.",
756
- session_mismatch: "Your session changed. Reload the page."
769
+ session_mismatch: "Your session changed. Reload the page.",
770
+ page_cap: "Download the file to see every page.",
771
+ render_failed: "The file couldn't be displayed."
757
772
  };
758
773
  var defaultPreviewLabels = {
759
774
  document: (title, pageCount) => `${title}, ${pageCount === 1 ? "1 page" : `${pageCount} pages`}`,
@@ -961,7 +976,7 @@ function PdfCanvasDocument(props) {
961
976
  React9.useLayoutEffect(() => {
962
977
  if (document2) setWidth(containerRef.current?.clientWidth ?? 0);
963
978
  }, [document2]);
964
- if (error) return /* @__PURE__ */ jsx6(Fragment2, { children: renderFallback(error) });
979
+ if (error) return /* @__PURE__ */ jsx6(Fragment2, { children: renderFallback(renderFailedError(error)) });
965
980
  if (!document2) return /* @__PURE__ */ jsx6(Fragment2, { children: loading });
966
981
  const shownPageCount = Math.min(document2.numPages, Math.max(1, maxPages));
967
982
  const isTruncated = shownPageCount < document2.numPages;
@@ -987,7 +1002,7 @@ function PdfCanvasDocument(props) {
987
1002
  isTruncated && /* @__PURE__ */ jsxs3("p", { children: [
988
1003
  labels.pageCap(shownPageCount, document2.numPages),
989
1004
  " ",
990
- renderFallback(null)
1005
+ renderFallback(pageCapError(shownPageCount, document2.numPages))
991
1006
  ] })
992
1007
  ]
993
1008
  }
@@ -1591,13 +1606,15 @@ import React17 from "react";
1591
1606
  import { VideoPlayer } from "@devultur/react";
1592
1607
  import { Fragment as Fragment6, jsx as jsx11 } from "react/jsx-runtime";
1593
1608
  var UNAUTHORIZED = 401;
1609
+ var HLS_NETWORK_FAILURE = /Load(Error|TimeOut)$/;
1594
1610
  function fetchErrorOf(error) {
1595
1611
  if (error.status === UNAUTHORIZED && error.code === "session_mismatch") {
1596
1612
  return new MediaFetchError("session_mismatch", "The delivery refused this session's token", UNAUTHORIZED);
1597
1613
  }
1598
1614
  if (error.status === UNAUTHORIZED) return tokenUnavailableError();
1599
1615
  if (error.status) return new MediaFetchError("http_error", `Video fetch failed: ${error.status}`, error.status);
1600
- return null;
1616
+ if (HLS_NETWORK_FAILURE.test(error.details)) return networkError(error);
1617
+ return renderFailedError(error);
1601
1618
  }
1602
1619
  function VideoPreview({ media, renderFallback, className }) {
1603
1620
  const { tokenStatus, tokenError, reissueToken } = useDevultur();
@@ -1633,7 +1650,7 @@ function Preview({ media, contentType, fallback, loading, labels, ...viewProps }
1633
1650
  const text = usePreviewLabels(labels);
1634
1651
  if (!media) return null;
1635
1652
  const renderFallback = (error) => {
1636
- const fetchError = error instanceof MediaFetchError ? error : null;
1653
+ const fetchError = error instanceof MediaFetchError ? error : networkError(error);
1637
1654
  if (typeof fallback === "function") return fallback(fetchError);
1638
1655
  if (fallback !== void 0) return fallback;
1639
1656
  return /* @__PURE__ */ jsx12(DefaultFallback, { media, error: fetchError, labels: text });
@@ -1660,7 +1677,7 @@ function Preview({ media, contentType, fallback, loading, labels, ...viewProps }
1660
1677
  }
1661
1678
  var unsupportedType = (contentType) => new MediaFetchError("unsupported_type", `Can't preview ${contentType ?? "an unknown type"} inline`);
1662
1679
  function DefaultFallback({ media, error, labels }) {
1663
- return /* @__PURE__ */ jsx12(DownloadLink, { media, title: error ? labels.error(error.code) : void 0, children: media.filename ?? labels.download });
1680
+ return /* @__PURE__ */ jsx12(DownloadLink, { media, title: labels.error(error.code), children: media.filename ?? labels.download });
1664
1681
  }
1665
1682
  function LoadingStatus({ text }) {
1666
1683
  return /* @__PURE__ */ jsx12("span", { role: "status", style: VISUALLY_HIDDEN, children: text });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devultur/convex",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "Convex component for Devultur media: media records, uploads, transcodes, watch progress and React bindings",
5
5
  "keywords": [
6
6
  "convex",
@@ -49,6 +49,10 @@
49
49
  "./locales/es": {
50
50
  "import": "./dist/locales/es.js",
51
51
  "types": "./dist/locales/es.d.ts"
52
+ },
53
+ "./locales/es-formal": {
54
+ "import": "./dist/locales/es-formal.js",
55
+ "types": "./dist/locales/es-formal.d.ts"
52
56
  }
53
57
  },
54
58
  "bin": {
@@ -59,8 +63,8 @@
59
63
  "dist"
60
64
  ],
61
65
  "dependencies": {
62
- "@devultur/core": "1.4.0",
63
- "@devultur/react": "1.4.0"
66
+ "@devultur/core": "1.4.2",
67
+ "@devultur/react": "1.4.2"
64
68
  },
65
69
  "peerDependencies": {
66
70
  "convex": ">=1.25.0",
@@ -92,7 +96,7 @@
92
96
  "access": "public"
93
97
  },
94
98
  "scripts": {
95
- "build": "tsup src/index.ts src/validators.ts src/react.tsx src/pdfjs.ts src/locales/es.ts src/bin/init.ts --format esm --dts && tsc -p src/component/tsconfig.json",
99
+ "build": "tsup src/index.ts src/validators.ts src/react.tsx src/pdfjs.ts src/locales/es.ts src/locales/es-formal.ts src/bin/init.ts --format esm --dts && tsc -p src/component/tsconfig.json",
96
100
  "codegen": "node scripts/codegen.ts",
97
101
  "dev": "tsup src/index.ts src/react.tsx --format esm --dts --watch",
98
102
  "typecheck": "tsc --noEmit"