@topconsultnpm/sdkui-react 6.20.0-dev1.10 → 6.20.0-dev1.12
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/lib/components/NewComponents/ContextMenu/TMContextMenu.d.ts +4 -0
- package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +187 -0
- package/lib/components/NewComponents/ContextMenu/hooks.d.ts +11 -0
- package/lib/components/NewComponents/ContextMenu/hooks.js +48 -0
- package/lib/components/NewComponents/ContextMenu/index.d.ts +2 -0
- package/lib/components/NewComponents/ContextMenu/index.js +1 -0
- package/lib/components/NewComponents/ContextMenu/styles.d.ts +27 -0
- package/lib/components/NewComponents/ContextMenu/styles.js +308 -0
- package/lib/components/NewComponents/ContextMenu/types.d.ts +26 -0
- package/lib/components/NewComponents/ContextMenu/types.js +1 -0
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.d.ts +4 -0
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +370 -0
- package/lib/components/NewComponents/FloatingMenuBar/index.d.ts +2 -0
- package/lib/components/NewComponents/FloatingMenuBar/index.js +2 -0
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +38 -0
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +267 -0
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +30 -0
- package/lib/components/NewComponents/FloatingMenuBar/types.js +1 -0
- package/lib/components/NewComponents/Notification/Notification.d.ts +4 -0
- package/lib/components/NewComponents/Notification/Notification.js +60 -0
- package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +8 -0
- package/lib/components/NewComponents/Notification/NotificationContainer.js +33 -0
- package/lib/components/NewComponents/Notification/index.d.ts +2 -0
- package/lib/components/NewComponents/Notification/index.js +2 -0
- package/lib/components/NewComponents/Notification/styles.d.ts +21 -0
- package/lib/components/NewComponents/Notification/styles.js +180 -0
- package/lib/components/NewComponents/Notification/types.d.ts +18 -0
- package/lib/components/NewComponents/Notification/types.js +1 -0
- package/lib/components/features/documents/TMDcmtPreview.js +72 -26
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TMContextMenuItemProps } from '../ContextMenu';
|
|
2
|
+
export interface TMFloatingMenuItem {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
icon: React.ReactNode;
|
|
6
|
+
onClick: () => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
isPinned?: boolean;
|
|
9
|
+
originalMenuItem?: TMContextMenuItemProps;
|
|
10
|
+
}
|
|
11
|
+
export interface TMFloatingMenuBarProps {
|
|
12
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
13
|
+
contextMenuItems?: TMContextMenuItemProps[];
|
|
14
|
+
storageKey?: string;
|
|
15
|
+
isConstrained?: boolean;
|
|
16
|
+
defaultPosition?: Position;
|
|
17
|
+
maxItems?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface Position {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
}
|
|
23
|
+
export interface TMFloatingMenuBarState {
|
|
24
|
+
position: Position;
|
|
25
|
+
isDragging: boolean;
|
|
26
|
+
isConfigMode: boolean;
|
|
27
|
+
orientation: 'horizontal' | 'vertical';
|
|
28
|
+
items: TMFloatingMenuItem[];
|
|
29
|
+
draggedItemIndex: number | null;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import * as S from './styles';
|
|
4
|
+
const Notification = ({ title, message, mode = 'info', position = 'top-right', duration = 3000, closable = false, stopOnMouseEnter = true, hasProgress = true, onClose, }) => {
|
|
5
|
+
const [state, setState] = useState({
|
|
6
|
+
visible: true,
|
|
7
|
+
isPaused: false,
|
|
8
|
+
progress: 100,
|
|
9
|
+
});
|
|
10
|
+
const timeoutRef = useRef(undefined);
|
|
11
|
+
const remainingTimeRef = useRef(duration);
|
|
12
|
+
const pauseTimeRef = useRef(0);
|
|
13
|
+
const closeNotification = () => {
|
|
14
|
+
setState(prev => ({ ...prev, visible: false }));
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
onClose?.();
|
|
17
|
+
}, 300);
|
|
18
|
+
};
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
// Set up auto-close timer
|
|
21
|
+
timeoutRef.current = setTimeout(() => {
|
|
22
|
+
closeNotification();
|
|
23
|
+
}, duration);
|
|
24
|
+
return () => {
|
|
25
|
+
if (timeoutRef.current) {
|
|
26
|
+
clearTimeout(timeoutRef.current);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}, [duration]);
|
|
30
|
+
const handleMouseEnter = () => {
|
|
31
|
+
if (!stopOnMouseEnter)
|
|
32
|
+
return;
|
|
33
|
+
// Pause the timer
|
|
34
|
+
if (timeoutRef.current) {
|
|
35
|
+
clearTimeout(timeoutRef.current);
|
|
36
|
+
pauseTimeRef.current = Date.now();
|
|
37
|
+
}
|
|
38
|
+
setState(prev => ({ ...prev, isPaused: true }));
|
|
39
|
+
};
|
|
40
|
+
const handleMouseLeave = () => {
|
|
41
|
+
if (!stopOnMouseEnter)
|
|
42
|
+
return;
|
|
43
|
+
// Resume the timer with remaining time
|
|
44
|
+
const pauseDuration = Date.now() - pauseTimeRef.current;
|
|
45
|
+
remainingTimeRef.current = Math.max(0, remainingTimeRef.current - pauseDuration);
|
|
46
|
+
timeoutRef.current = setTimeout(() => {
|
|
47
|
+
closeNotification();
|
|
48
|
+
}, remainingTimeRef.current);
|
|
49
|
+
setState(prev => ({ ...prev, isPaused: false }));
|
|
50
|
+
};
|
|
51
|
+
const handleClose = (e) => {
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
if (timeoutRef.current) {
|
|
54
|
+
clearTimeout(timeoutRef.current);
|
|
55
|
+
}
|
|
56
|
+
closeNotification();
|
|
57
|
+
};
|
|
58
|
+
return (_jsxs(S.NotificationContainer, { "$position": position, "$mode": mode, "$visible": state.visible, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, role: "alert", "aria-live": "assertive", children: [_jsxs(S.NotificationContent, { children: [_jsx(S.NotificationTitle, { children: title }), _jsx(S.NotificationMessage, { children: message })] }), closable && (_jsx(S.CloseButton, { onClick: handleClose, "aria-label": "Close notification", children: "\u00D7" })), hasProgress && (_jsx(S.ProgressBar, { "$duration": duration, "$mode": mode, "$isPaused": state.isPaused }))] }));
|
|
59
|
+
};
|
|
60
|
+
export default Notification;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { NotificationPosition } from './types';
|
|
3
|
+
interface NotificationContainerProps {
|
|
4
|
+
position: NotificationPosition;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
declare const NotificationContainer: React.FC<NotificationContainerProps>;
|
|
8
|
+
export default NotificationContainer;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
const Container = styled.div `
|
|
4
|
+
position: fixed;
|
|
5
|
+
z-index: 10002;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: 12px;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
|
|
11
|
+
${props => {
|
|
12
|
+
const isTop = props.$position.startsWith('top');
|
|
13
|
+
const isBottom = props.$position.startsWith('bottom');
|
|
14
|
+
const isLeft = props.$position.endsWith('left');
|
|
15
|
+
const isRight = props.$position.endsWith('right');
|
|
16
|
+
const isCenter = props.$position.endsWith('center');
|
|
17
|
+
return `
|
|
18
|
+
${isTop ? 'top: 24px;' : ''}
|
|
19
|
+
${isBottom ? 'bottom: 24px;' : ''}
|
|
20
|
+
${isLeft ? 'left: 24px;' : ''}
|
|
21
|
+
${isRight ? 'right: 24px;' : ''}
|
|
22
|
+
${isCenter ? 'left: 50%; transform: translateX(-50%);' : ''}
|
|
23
|
+
`;
|
|
24
|
+
}}
|
|
25
|
+
|
|
26
|
+
& > * {
|
|
27
|
+
pointer-events: auto;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
const NotificationContainer = ({ position, children }) => {
|
|
31
|
+
return _jsx(Container, { "$position": position, children: children });
|
|
32
|
+
};
|
|
33
|
+
export default NotificationContainer;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NotificationMode, NotificationPosition } from './types';
|
|
2
|
+
export declare const NotificationContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
3
|
+
$position: NotificationPosition;
|
|
4
|
+
$mode: NotificationMode;
|
|
5
|
+
$visible: boolean;
|
|
6
|
+
}>> & string;
|
|
7
|
+
export declare const NotificationContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
8
|
+
export declare const NotificationTitle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
9
|
+
export declare const NotificationMessage: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
10
|
+
export declare const CloseButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
|
|
11
|
+
export declare const ProgressBar: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("styled-components/dist/types").Substitute<import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
12
|
+
ref?: ((instance: HTMLDivElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLDivElement> | null | undefined;
|
|
13
|
+
}>, {
|
|
14
|
+
$duration: number;
|
|
15
|
+
$mode: NotificationMode;
|
|
16
|
+
$isPaused: boolean;
|
|
17
|
+
}>, {
|
|
18
|
+
$duration: number;
|
|
19
|
+
$mode: NotificationMode;
|
|
20
|
+
$isPaused: boolean;
|
|
21
|
+
}>> & string;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import styled, { keyframes } from 'styled-components';
|
|
2
|
+
const slideInFromTop = keyframes `
|
|
3
|
+
from {
|
|
4
|
+
opacity: 0;
|
|
5
|
+
transform: translateY(-100%);
|
|
6
|
+
}
|
|
7
|
+
to {
|
|
8
|
+
opacity: 1;
|
|
9
|
+
transform: translateY(0);
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
const slideInFromBottom = keyframes `
|
|
13
|
+
from {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
transform: translateY(100%);
|
|
16
|
+
}
|
|
17
|
+
to {
|
|
18
|
+
opacity: 1;
|
|
19
|
+
transform: translateY(0);
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
const slideOut = keyframes `
|
|
23
|
+
from {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
transform: scale(1);
|
|
26
|
+
}
|
|
27
|
+
to {
|
|
28
|
+
opacity: 0;
|
|
29
|
+
transform: scale(0.9);
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
const getModeColors = (mode) => {
|
|
33
|
+
const colors = {
|
|
34
|
+
success: {
|
|
35
|
+
bg: '#10b981',
|
|
36
|
+
bgDark: '#059669',
|
|
37
|
+
border: '#34d399',
|
|
38
|
+
text: '#ffffff',
|
|
39
|
+
},
|
|
40
|
+
error: {
|
|
41
|
+
bg: '#ef4444',
|
|
42
|
+
bgDark: '#dc2626',
|
|
43
|
+
border: '#f87171',
|
|
44
|
+
text: '#ffffff',
|
|
45
|
+
},
|
|
46
|
+
warning: {
|
|
47
|
+
bg: '#f59e0b',
|
|
48
|
+
bgDark: '#d97706',
|
|
49
|
+
border: '#fbbf24',
|
|
50
|
+
text: '#ffffff',
|
|
51
|
+
},
|
|
52
|
+
info: {
|
|
53
|
+
bg: '#3b82f6',
|
|
54
|
+
bgDark: '#2563eb',
|
|
55
|
+
border: '#60a5fa',
|
|
56
|
+
text: '#ffffff',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return colors[mode];
|
|
60
|
+
};
|
|
61
|
+
export const NotificationContainer = styled.div `
|
|
62
|
+
position: relative;
|
|
63
|
+
z-index: 1;
|
|
64
|
+
min-width: 320px;
|
|
65
|
+
max-width: 420px;
|
|
66
|
+
background: ${props => getModeColors(props.$mode).bg};
|
|
67
|
+
border-radius: 12px;
|
|
68
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2),
|
|
69
|
+
0 4px 12px rgba(0, 0, 0, 0.15);
|
|
70
|
+
padding: 16px 20px;
|
|
71
|
+
animation: ${props => {
|
|
72
|
+
if (!props.$visible)
|
|
73
|
+
return slideOut;
|
|
74
|
+
return props.$position.startsWith('top') ? slideInFromTop : slideInFromBottom;
|
|
75
|
+
}} 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
76
|
+
backdrop-filter: blur(10px);
|
|
77
|
+
border: 2px solid ${props => getModeColors(props.$mode).border};
|
|
78
|
+
color: ${props => getModeColors(props.$mode).text};
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
|
|
81
|
+
[data-theme='dark'] & {
|
|
82
|
+
background: ${props => getModeColors(props.$mode).bgDark};
|
|
83
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4),
|
|
84
|
+
0 4px 12px rgba(0, 0, 0, 0.3);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@media (max-width: 768px) {
|
|
88
|
+
min-width: 280px;
|
|
89
|
+
max-width: calc(100vw - 48px);
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
export const NotificationContent = styled.div `
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
gap: 6px;
|
|
96
|
+
padding-right: 24px;
|
|
97
|
+
`;
|
|
98
|
+
export const NotificationTitle = styled.div `
|
|
99
|
+
font-size: 16px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
line-height: 1.4;
|
|
102
|
+
letter-spacing: -0.01em;
|
|
103
|
+
`;
|
|
104
|
+
export const NotificationMessage = styled.div `
|
|
105
|
+
font-size: 14px;
|
|
106
|
+
font-weight: 400;
|
|
107
|
+
line-height: 1.5;
|
|
108
|
+
opacity: 0.95;
|
|
109
|
+
`;
|
|
110
|
+
export const CloseButton = styled.button `
|
|
111
|
+
position: absolute;
|
|
112
|
+
top: 12px;
|
|
113
|
+
right: 12px;
|
|
114
|
+
background: transparent;
|
|
115
|
+
border: none;
|
|
116
|
+
color: inherit;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
width: 24px;
|
|
119
|
+
height: 24px;
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
123
|
+
border-radius: 6px;
|
|
124
|
+
transition: all 0.15s ease;
|
|
125
|
+
font-size: 18px;
|
|
126
|
+
line-height: 1;
|
|
127
|
+
padding: 0;
|
|
128
|
+
opacity: 0.8;
|
|
129
|
+
|
|
130
|
+
&:hover {
|
|
131
|
+
opacity: 1;
|
|
132
|
+
background: rgba(255, 255, 255, 0.2);
|
|
133
|
+
transform: scale(1.1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
&:active {
|
|
137
|
+
transform: scale(0.95);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&:focus {
|
|
141
|
+
outline: 2px solid rgba(255, 255, 255, 0.5);
|
|
142
|
+
outline-offset: 2px;
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
export const ProgressBar = styled.div.attrs(props => ({
|
|
146
|
+
style: {
|
|
147
|
+
animationDuration: `${props.$duration}ms`,
|
|
148
|
+
},
|
|
149
|
+
})) `
|
|
150
|
+
position: absolute;
|
|
151
|
+
bottom: 0;
|
|
152
|
+
left: 0;
|
|
153
|
+
height: 4px;
|
|
154
|
+
width: 100%;
|
|
155
|
+
background: ${props => getModeColors(props.$mode).border};
|
|
156
|
+
border-radius: 0 0 0 10px;
|
|
157
|
+
box-shadow: 0 0 8px ${props => getModeColors(props.$mode).border};
|
|
158
|
+
transform-origin: left;
|
|
159
|
+
animation: progress-shrink linear forwards;
|
|
160
|
+
animation-play-state: ${props => props.$isPaused ? 'paused' : 'running'};
|
|
161
|
+
|
|
162
|
+
@keyframes progress-shrink {
|
|
163
|
+
from {
|
|
164
|
+
transform: scaleX(1);
|
|
165
|
+
}
|
|
166
|
+
to {
|
|
167
|
+
transform: scaleX(0);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&::after {
|
|
172
|
+
content: '';
|
|
173
|
+
position: absolute;
|
|
174
|
+
top: 0;
|
|
175
|
+
right: 0;
|
|
176
|
+
width: 20px;
|
|
177
|
+
height: 100%;
|
|
178
|
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4));
|
|
179
|
+
}
|
|
180
|
+
`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type NotificationMode = 'warning' | 'info' | 'error' | 'success';
|
|
2
|
+
export type NotificationPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
3
|
+
export interface NotificationProps {
|
|
4
|
+
title: string;
|
|
5
|
+
message: string;
|
|
6
|
+
mode?: NotificationMode;
|
|
7
|
+
position?: NotificationPosition;
|
|
8
|
+
duration?: number;
|
|
9
|
+
closable?: boolean;
|
|
10
|
+
stopOnMouseEnter?: boolean;
|
|
11
|
+
hasProgress?: boolean;
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface NotificationState {
|
|
15
|
+
visible: boolean;
|
|
16
|
+
isPaused: boolean;
|
|
17
|
+
progress: number;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -15,6 +15,9 @@ import { TMSaveFormButtonPrevious, TMSaveFormButtonNext } from '../../forms/TMSa
|
|
|
15
15
|
import { StyledAnimatedComponentOpacity } from '../../base/Styled';
|
|
16
16
|
import TMPanel from '../../base/TMPanel';
|
|
17
17
|
import TMTooltip from '../../base/TMTooltip';
|
|
18
|
+
import { Document, Page, pdfjs } from 'react-pdf';
|
|
19
|
+
// Configure PDF.js worker
|
|
20
|
+
pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
|
18
21
|
const ErrorContent = ({ error, isAbortError, onRetry }) => {
|
|
19
22
|
if (isAbortError) {
|
|
20
23
|
return (_jsx(StyledAnimatedComponentOpacity, { style: { width: '100%', height: '100%' }, children: _jsxs(StyledPanelStatusContainer, { children: [_jsx(IconCloseOutline, { fontSize: 92, color: TMColors.error }), _jsxs(StyledPreviewNotAvailable, { children: [_jsx("div", { children: error }), _jsx("div", { children: SDKUI_Localizator.PreviewNotAvailable })] }), _jsx(TMButton, { caption: SDKUI_Localizator.TryAgain, onClick: onRetry, showTooltip: false })] }) }));
|
|
@@ -136,32 +139,33 @@ export const TMFileViewer = ({ fileBlob, isResizingActive }) => {
|
|
|
136
139
|
const [blobUrl, setBlobUrl] = useState(undefined);
|
|
137
140
|
const [fileType, setFileType] = useState(undefined);
|
|
138
141
|
const [formattedXml, setFormattedXml] = useState(undefined);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
142
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
143
|
+
const [numPages, setNumPages] = useState(0);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const checkIsMobile = () => {
|
|
146
|
+
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
147
|
+
// Only detect actual mobile/tablet devices, NOT desktop browsers
|
|
148
|
+
const isMobileDevice =
|
|
149
|
+
// Traditional mobile detection (phones and tablets)
|
|
150
|
+
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent) ||
|
|
151
|
+
// Additional Android tablet detection (covers tablets in landscape)
|
|
152
|
+
/android.*tablet|android.*mobile/i.test(userAgent) ||
|
|
153
|
+
// Touch-only devices (excludes laptops with touchscreen)
|
|
154
|
+
(('ontouchstart' in window || navigator.maxTouchPoints > 0) &&
|
|
155
|
+
!/Windows NT|Macintosh|Linux/.test(userAgent)) ||
|
|
156
|
+
// Small screen mobile devices only
|
|
157
|
+
(window.screen.width <= 768 && /Mobi|Android/i.test(userAgent));
|
|
158
|
+
setIsMobile(isMobileDevice);
|
|
159
|
+
};
|
|
160
|
+
checkIsMobile();
|
|
161
|
+
// Listen for orientation changes (important for tablets)
|
|
162
|
+
window.addEventListener('orientationchange', checkIsMobile);
|
|
163
|
+
window.addEventListener('resize', checkIsMobile);
|
|
164
|
+
return () => {
|
|
165
|
+
window.removeEventListener('orientationchange', checkIsMobile);
|
|
166
|
+
window.removeEventListener('resize', checkIsMobile);
|
|
167
|
+
};
|
|
168
|
+
}, []);
|
|
165
169
|
useEffect(() => {
|
|
166
170
|
if (fileBlob) {
|
|
167
171
|
setFileType(fileBlob.type);
|
|
@@ -289,6 +293,23 @@ export const TMFileViewer = ({ fileBlob, isResizingActive }) => {
|
|
|
289
293
|
// </object>
|
|
290
294
|
// );
|
|
291
295
|
// }
|
|
296
|
+
if (fileType === 'application/pdf' && isMobile) {
|
|
297
|
+
return (_jsx(PDFViewerContainer, { children: _jsx(Document, { file: blobUrl, onLoadSuccess: ({ numPages }) => setNumPages(numPages), loading: _jsxs("div", { style: {
|
|
298
|
+
display: 'flex',
|
|
299
|
+
justifyContent: 'center',
|
|
300
|
+
alignItems: 'center',
|
|
301
|
+
height: '100%',
|
|
302
|
+
flexDirection: 'column',
|
|
303
|
+
gap: '10px'
|
|
304
|
+
}, children: [_jsx(IconPreview, { fontSize: 64 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }), error: _jsxs("div", { style: {
|
|
305
|
+
display: 'flex',
|
|
306
|
+
justifyContent: 'center',
|
|
307
|
+
alignItems: 'center',
|
|
308
|
+
height: '100%',
|
|
309
|
+
flexDirection: 'column',
|
|
310
|
+
gap: '10px'
|
|
311
|
+
}, children: [_jsx(IconCloseOutline, { fontSize: 64, color: TMColors.error }), _jsx("div", { children: "Errore nel caricamento del PDF" })] }), children: Array.from(new Array(numPages), (el, index) => (_jsx(Page, { pageNumber: index + 1, renderTextLayer: false, renderAnnotationLayer: false, width: window.innerWidth }, `page_${index + 1}`))) }) }));
|
|
312
|
+
}
|
|
292
313
|
return (_jsx("iframe", { srcDoc: formattedXml ? `<html><body>${formattedXml}</body></html>` : undefined, src: !formattedXml
|
|
293
314
|
? (fileType === 'application/pdf' ? `${blobUrl}#view=FitH&scrollbar=1` : blobUrl)
|
|
294
315
|
: undefined, title: "File Viewer", width: "100%", height: "100%", style: { border: 'none', zIndex: 0, pointerEvents: isResizingActive === true ? "none" : "auto" } }, blobUrl));
|
|
@@ -510,3 +531,28 @@ const StyledImage = styled.img.attrs(props => ({
|
|
|
510
531
|
pointer-events: none;
|
|
511
532
|
position: absolute;
|
|
512
533
|
`;
|
|
534
|
+
const PDFViewerContainer = styled.div `
|
|
535
|
+
width: 100%;
|
|
536
|
+
height: 100%;
|
|
537
|
+
overflow-y: auto;
|
|
538
|
+
overflow-x: hidden;
|
|
539
|
+
background-color: #f5f5f5;
|
|
540
|
+
|
|
541
|
+
.react-pdf__Document {
|
|
542
|
+
display: flex;
|
|
543
|
+
flex-direction: column;
|
|
544
|
+
align-items: center;
|
|
545
|
+
gap: 10px;
|
|
546
|
+
padding: 10px 0;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.react-pdf__Page {
|
|
550
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
551
|
+
margin: 0 auto;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.react-pdf__Page__canvas {
|
|
555
|
+
max-width: 100%;
|
|
556
|
+
height: auto !important;
|
|
557
|
+
}
|
|
558
|
+
`;
|