105 lines
3.6 KiB
TypeScript
105 lines
3.6 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Button, Tooltip, Spin } from 'antd';
|
|
import { ScissorOutlined } from '@ant-design/icons';
|
|
import { getEnv } from '../utils/env';
|
|
import { getAccessToken } from '../auth/oidc';
|
|
|
|
interface PdfSplitViewerProps {
|
|
attachmentId: number;
|
|
pageCount: number;
|
|
startPage?: number;
|
|
endPage?: number;
|
|
onSplit: (pageIndex: number) => void;
|
|
disabled: boolean;
|
|
}
|
|
|
|
export default function PdfSplitViewer({ attachmentId, pageCount, startPage, endPage, onSplit, disabled }: PdfSplitViewerProps) {
|
|
const [token, setToken] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
getAccessToken().then(t => setToken(t));
|
|
}, []);
|
|
|
|
if (!token) return <Spin />;
|
|
|
|
if (pageCount === 0) {
|
|
return (
|
|
<div style={{ padding: 16, background: '#fafafa', borderRadius: 8, textAlign: 'center' }}>
|
|
<p>Vorschau nicht verfügbar (Dokument wurde vor dem Update geladen).</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const actualStart = startPage || 1;
|
|
const actualEnd = endPage || pageCount;
|
|
|
|
const pagesToRender = [];
|
|
for (let i = actualStart; i <= actualEnd; i++) {
|
|
pagesToRender.push(i);
|
|
}
|
|
|
|
const getImageUrl = (page: number) => {
|
|
// We add the token as a query parameter because <img> tags don't support Authorization headers easily.
|
|
// However, it's more secure to fetch the blob and create an object URL, or rely on cookie auth.
|
|
// Let's fetch the blob and use object URLs to pass the bearer token securely.
|
|
return `${getEnv('VITE_API_URL')}/api/email-import/attachments/${attachmentId}/pages/${page}/thumb`;
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: 'flex', overflowX: 'auto', padding: '16px 8px', background: '#fafafa', borderRadius: 8 }}>
|
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
{pagesToRender.map((pageNum) => (
|
|
<div key={pageNum} style={{ display: 'flex', alignItems: 'center' }}>
|
|
<div style={{ border: '1px solid #d9d9d9', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', background: 'white', height: 200, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
<ImageWithAuth url={getImageUrl(pageNum)} token={token} />
|
|
</div>
|
|
|
|
{!disabled && pageNum < actualEnd && (
|
|
<div style={{ width: 24, display: 'flex', justifyContent: 'center', zIndex: 10, marginLeft: 8 }}>
|
|
<Tooltip title="Hier trennen">
|
|
<Button
|
|
type="primary"
|
|
danger
|
|
shape="circle"
|
|
size="small"
|
|
icon={<ScissorOutlined />}
|
|
onClick={() => onSplit(pageNum)}
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ImageWithAuth({ url, token }: { url: string; token: string }) {
|
|
const [imgSrc, setImgSrc] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
let objectUrl: string | undefined;
|
|
fetch(url, { headers: { Authorization: `Bearer ${token}` } })
|
|
.then(res => {
|
|
if (!res.ok) throw new Error('Network response was not ok');
|
|
return res.blob();
|
|
})
|
|
.then(blob => {
|
|
if (cancelled) return;
|
|
objectUrl = URL.createObjectURL(blob);
|
|
setImgSrc(objectUrl);
|
|
})
|
|
.catch(() => {});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
};
|
|
}, [url, token]);
|
|
|
|
if (!imgSrc) return <Spin style={{ margin: '0 20px' }} />;
|
|
return <img src={imgSrc} alt="PDF Page" style={{ height: '100%', objectFit: 'contain' }} />;
|
|
}
|