@topconsultnpm/sdkui-react 6.21.0-dev1.48 → 6.21.0-dev1.49

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.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState, useRef, useCallback } from "react";
2
+ import { useEffect, useState, useRef, useCallback, Component } from "react";
3
3
  import styled from "styled-components";
4
4
  import { LoadIndicator } from 'devextreme-react/load-indicator';
5
5
  import { IconCloseOutline } from "./TMIcons";
@@ -7,6 +7,58 @@ import { SDKUI_Localizator } from "./SDKUI_Localizator";
7
7
  import { TMColors } from "../utils/theme";
8
8
  import { TMMessageBoxManager, ButtonNames } from "../components/base/TMPopUp";
9
9
  import { Document, Page, pdfjs } from "react-pdf";
10
+ let workerStatus = 'pending';
11
+ let workerCheckPromise = null;
12
+ /**
13
+ * Verifica se il worker PDF.js è disponibile e lo configura.
14
+ * La Promise viene creata una sola volta e riutilizzata per tutte le istanze.
15
+ */
16
+ const checkAndConfigureWorker = () => {
17
+ if (workerCheckPromise) {
18
+ return workerCheckPromise;
19
+ }
20
+ workerCheckPromise = new Promise((resolve) => {
21
+ const workerUrl = window.location.origin + "/assets/pdfjs-dist/pdf.worker.min.mjs";
22
+ fetch(workerUrl, { method: 'HEAD' })
23
+ .then(response => {
24
+ if (response.ok) {
25
+ pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
26
+ workerStatus = 'available';
27
+ resolve(true);
28
+ }
29
+ else {
30
+ console.warn('PDF.js worker not found at:', workerUrl);
31
+ workerStatus = 'unavailable';
32
+ resolve(false);
33
+ }
34
+ })
35
+ .catch(error => {
36
+ console.warn('PDF.js worker check failed:', error);
37
+ workerStatus = 'unavailable';
38
+ resolve(false);
39
+ });
40
+ });
41
+ return workerCheckPromise;
42
+ };
43
+ class PdfErrorBoundary extends Component {
44
+ constructor(props) {
45
+ super(props);
46
+ this.state = { hasError: false };
47
+ }
48
+ static getDerivedStateFromError(_) {
49
+ return { hasError: true };
50
+ }
51
+ componentDidCatch(error, errorInfo) {
52
+ console.warn('PDF rendering error, switching to iframe fallback:', error.message);
53
+ this.props.onError();
54
+ }
55
+ render() {
56
+ if (this.state.hasError) {
57
+ return this.props.fallback;
58
+ }
59
+ return this.props.children;
60
+ }
61
+ }
10
62
  const PDFViewerContainer = styled.div `
11
63
  width: 100%;
12
64
  height: 100%;
@@ -56,23 +108,23 @@ const TMPdfViewer = (props) => {
56
108
  const [isCheckingPdf, setIsCheckingPdf] = useState(true);
57
109
  const [jsMatches, setJsMatches] = useState([]);
58
110
  const observerRef = useRef(null);
59
- const [useIframeFallback, setUseIframeFallback] = useState(false);
60
- // Configura il worker PDF.js - se non esiste usa iframe fallback
111
+ // Stati per gestione worker PDF.js
112
+ const [workerChecked, setWorkerChecked] = useState(workerStatus !== 'pending');
113
+ const [useIframeFallback, setUseIframeFallback] = useState(workerStatus === 'unavailable');
114
+ // Verifica il worker PDF.js - la Promise è condivisa, quindi la fetch avviene una sola volta
61
115
  useEffect(() => {
62
- const workerUrl = window.location.origin + "/assets/pdfjs-dist/pdf.worker.min.mjs";
63
- fetch(workerUrl, { method: 'HEAD' })
64
- .then(response => {
65
- if (response.ok) {
66
- pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
67
- setUseIframeFallback(false);
68
- }
69
- else {
70
- setUseIframeFallback(true);
71
- }
72
- })
73
- .catch(() => {
74
- setUseIframeFallback(true);
75
- });
116
+ if (workerStatus === 'pending') {
117
+ checkAndConfigureWorker().then(available => {
118
+ setWorkerChecked(true);
119
+ if (!available) {
120
+ setUseIframeFallback(true);
121
+ }
122
+ });
123
+ }
124
+ }, []);
125
+ // Handler per errori di react-pdf - fallback su iframe
126
+ const handlePdfError = useCallback(() => {
127
+ setUseIframeFallback(true);
76
128
  }, []);
77
129
  useEffect(() => {
78
130
  const checkIsMobile = () => {
@@ -270,8 +322,8 @@ const TMPdfViewer = (props) => {
270
322
  }, children: highlightMatch(match.context, match.match) }) })] }, index)))) : (_jsx("div", { style: { textAlign: 'center', padding: '20px', color: '#999' }, children: "Nessun dettaglio disponibile" }))] }))
271
323
  });
272
324
  };
273
- // Mostra loading durante la validazione
274
- if (isCheckingPdf) {
325
+ // Mostra loading durante la validazione del PDF e verifica worker
326
+ if (isCheckingPdf || !workerChecked) {
275
327
  return (_jsx(PDFViewerContainer, { children: _jsxs("div", { style: {
276
328
  display: 'flex',
277
329
  justifyContent: 'center',
@@ -281,22 +333,24 @@ const TMPdfViewer = (props) => {
281
333
  gap: '10px'
282
334
  }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: "Validazione PDF in corso..." })] }) }));
283
335
  }
336
+ // Componente iframe per fallback
337
+ const IframeViewer = (_jsx(PDFViewerContainer, { children: _jsx("iframe", { src: `${pdfUrl}#${enableFitToWidth ? 'view=FitH&' : ''}scrollbar=1`, title: title, style: {
338
+ width: '100%',
339
+ height: '100%',
340
+ border: 'none',
341
+ zIndex: 0,
342
+ pointerEvents: isResizingActive === true ? "none" : "auto"
343
+ } }, pdfUrl) }));
284
344
  /**
285
345
  * Usa <iframe> nei seguenti casi:
286
- * 1. Worker PDF.js non disponibile (fallback)
346
+ * 1. Worker PDF.js non disponibile o in errore (fallback)
287
347
  * 2. Desktop E nessun contenuto JavaScript rilevato (visualizzazione nativa del browser più performante)
288
348
  *
289
349
  * L'iframe sfrutta il visualizzatore PDF nativo del browser, ma non può prevenire
290
350
  * l'esecuzione di JavaScript embedded nel PDF.
291
351
  */
292
352
  if (useIframeFallback || (!isMobile && !hasUnsafeContent && pdfUrl)) {
293
- return (_jsx(PDFViewerContainer, { children: _jsx("iframe", { src: `${pdfUrl}#${enableFitToWidth ? 'view=FitH&' : ''}scrollbar=1`, title: title, style: {
294
- width: '100%',
295
- height: '100%',
296
- border: 'none',
297
- zIndex: 0,
298
- pointerEvents: isResizingActive === true ? "none" : "auto"
299
- } }, pdfUrl) }));
353
+ return IframeViewer;
300
354
  }
301
355
  /**
302
356
  * Usa react-pdf nei seguenti casi:
@@ -305,70 +359,72 @@ const TMPdfViewer = (props) => {
305
359
  *
306
360
  * react-pdf renderizza il PDF come canvas, prevenendo l'esecuzione di JavaScript embedded,
307
361
  * ma è meno performante dell'iframe nativo su desktop.
362
+ *
363
+ * ErrorBoundary garantisce fallback su iframe in caso di qualsiasi errore runtime.
308
364
  */
309
- return _jsxs(PDFViewerContainer, { style: { display: 'flex', flexDirection: 'column' }, children: [loadedPagesNumber === 0 && totalPagesNumber > 0 && _jsx(LoadingOverlay, { children: _jsxs("div", { style: {
310
- display: 'flex',
311
- justifyContent: 'center',
312
- alignItems: 'center',
313
- flexDirection: 'column',
314
- gap: '10px'
315
- }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }) }), _jsx("div", { style: {
316
- display: loadedPagesNumber > 0 && totalPagesNumber > 0 ? 'block' : 'none',
317
- flex: 1,
318
- overflow: 'auto'
319
- }, children: _jsx(Document, { file: pdfBlob, onLoadSuccess: ({ numPages }) => {
320
- setTotalPagesNumber(numPages);
321
- setLoadedPagesNumber(0);
322
- }, loading: _jsxs("div", { style: {
365
+ return (_jsx(PdfErrorBoundary, { onError: handlePdfError, fallback: IframeViewer, children: _jsxs(PDFViewerContainer, { style: { display: 'flex', flexDirection: 'column' }, children: [loadedPagesNumber === 0 && totalPagesNumber > 0 && _jsx(LoadingOverlay, { children: _jsxs("div", { style: {
323
366
  display: 'flex',
324
367
  justifyContent: 'center',
325
368
  alignItems: 'center',
326
- height: '100%',
327
369
  flexDirection: 'column',
328
370
  gap: '10px'
329
- }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }), error: _jsxs("div", { style: {
330
- display: 'flex',
331
- justifyContent: 'center',
332
- alignItems: 'center',
333
- height: '100%',
334
- flexDirection: 'column',
335
- gap: '10px'
336
- }, children: [_jsx(IconCloseOutline, { fontSize: 64, color: TMColors.error }), _jsx("div", { children: "Errore nel caricamento del PDF" })] }), children: Array.from(new Array(totalPagesNumber), (el, index) => {
337
- const pageNumber = index + 1;
338
- const shouldRender = visiblePages.has(pageNumber);
339
- return (_jsx("div", { "data-page-number": pageNumber, ref: (el) => {
340
- if (el && observerRef.current) {
341
- observerRef.current.observe(el);
342
- }
343
- }, style: {
344
- minHeight: shouldRender ? 'auto' : '1000px',
371
+ }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }) }), _jsx("div", { style: {
372
+ display: loadedPagesNumber > 0 && totalPagesNumber > 0 ? 'block' : 'none',
373
+ flex: 1,
374
+ overflow: 'auto'
375
+ }, children: _jsx(Document, { file: pdfBlob, onLoadSuccess: ({ numPages }) => {
376
+ setTotalPagesNumber(numPages);
377
+ setLoadedPagesNumber(0);
378
+ }, onLoadError: handlePdfError, loading: _jsxs("div", { style: {
345
379
  display: 'flex',
346
380
  justifyContent: 'center',
347
- alignItems: 'center'
348
- }, children: shouldRender && (_jsx(Page, { pageNumber: pageNumber, renderTextLayer: false, renderAnnotationLayer: false, width: Math.min(window.innerWidth - 40, 1200), loading: _jsx("div", { style: { padding: '20px' }, children: _jsx(LoadIndicator, { height: 40, width: 40 }) }), onLoadSuccess: () => {
349
- setLoadedPagesNumber(prev => prev + 1);
350
- } })) }, `page_${pageNumber}`));
351
- }) }) }), hasUnsafeContent && (_jsxs("div", { style: {
352
- display: 'flex',
353
- justifyContent: 'center',
354
- alignItems: 'center',
355
- padding: '12px 20px',
356
- background: '#fff3cd',
357
- borderTop: '2px solid #ffc107',
358
- gap: '8px',
359
- flexShrink: 0
360
- }, children: [_jsxs("span", { style: {
361
- color: '#856404',
362
- whiteSpace: 'nowrap',
363
- overflow: 'hidden',
364
- textOverflow: 'ellipsis',
365
- flex: 1
366
- }, children: [_jsx("strong", { children: "Attenzione:" }), " Questo documento contiene contenuti potenzialmente non sicuri."] }), jsMatches.length > 0 && (_jsx("span", { className: "dx-icon-info", style: {
367
- fontSize: '20px',
368
- color: '#d32f2f',
369
- cursor: 'pointer',
370
- transition: 'color 0.2s',
371
- marginLeft: '4px'
372
- }, onClick: showMatchDetails, title: "Clicca per vedere i dettagli", onMouseEnter: (e) => e.currentTarget.style.color = '#b71c1c', onMouseLeave: (e) => e.currentTarget.style.color = '#d32f2f' }))] }))] });
381
+ alignItems: 'center',
382
+ height: '100%',
383
+ flexDirection: 'column',
384
+ gap: '10px'
385
+ }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }), error: _jsxs("div", { style: {
386
+ display: 'flex',
387
+ justifyContent: 'center',
388
+ alignItems: 'center',
389
+ height: '100%',
390
+ flexDirection: 'column',
391
+ gap: '10px'
392
+ }, children: [_jsx(IconCloseOutline, { fontSize: 64, color: TMColors.error }), _jsx("div", { children: "Errore nel caricamento del PDF" })] }), children: Array.from(new Array(totalPagesNumber), (el, index) => {
393
+ const pageNumber = index + 1;
394
+ const shouldRender = visiblePages.has(pageNumber);
395
+ return (_jsx("div", { "data-page-number": pageNumber, ref: (el) => {
396
+ if (el && observerRef.current) {
397
+ observerRef.current.observe(el);
398
+ }
399
+ }, style: {
400
+ minHeight: shouldRender ? 'auto' : '1000px',
401
+ display: 'flex',
402
+ justifyContent: 'center',
403
+ alignItems: 'center'
404
+ }, children: shouldRender && (_jsx(Page, { pageNumber: pageNumber, renderTextLayer: false, renderAnnotationLayer: false, width: Math.min(window.innerWidth - 40, 1200), loading: _jsx("div", { style: { padding: '20px' }, children: _jsx(LoadIndicator, { height: 40, width: 40 }) }), onLoadSuccess: () => {
405
+ setLoadedPagesNumber(prev => prev + 1);
406
+ } })) }, `page_${pageNumber}`));
407
+ }) }) }), hasUnsafeContent && (_jsxs("div", { style: {
408
+ display: 'flex',
409
+ justifyContent: 'center',
410
+ alignItems: 'center',
411
+ padding: '12px 20px',
412
+ background: '#fff3cd',
413
+ borderTop: '2px solid #ffc107',
414
+ gap: '8px',
415
+ flexShrink: 0
416
+ }, children: [_jsxs("span", { style: {
417
+ color: '#856404',
418
+ whiteSpace: 'nowrap',
419
+ overflow: 'hidden',
420
+ textOverflow: 'ellipsis',
421
+ flex: 1
422
+ }, children: [_jsx("strong", { children: "Attenzione:" }), " Questo documento contiene contenuti potenzialmente non sicuri."] }), jsMatches.length > 0 && (_jsx("span", { className: "dx-icon-info", style: {
423
+ fontSize: '20px',
424
+ color: '#d32f2f',
425
+ cursor: 'pointer',
426
+ transition: 'color 0.2s',
427
+ marginLeft: '4px'
428
+ }, onClick: showMatchDetails, title: "Clicca per vedere i dettagli", onMouseEnter: (e) => e.currentTarget.style.color = '#b71c1c', onMouseLeave: (e) => e.currentTarget.style.color = '#d32f2f' }))] }))] }) }));
373
429
  };
374
430
  export default TMPdfViewer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.21.0-dev1.48",
3
+ "version": "6.21.0-dev1.49",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",