2025-09-11 19:08:44 +01:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Button, Paper, Group, NumberInput } from '@mantine/core';
|
|
|
|
|
import { useTranslation } from 'react-i18next';
|
2025-09-16 19:36:36 +01:00
|
|
|
|
import { useViewer } from '../../contexts/ViewerContext';
|
2025-09-11 19:08:44 +01:00
|
|
|
|
import FirstPageIcon from '@mui/icons-material/FirstPage';
|
|
|
|
|
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
|
|
|
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
|
|
|
|
import LastPageIcon from '@mui/icons-material/LastPage';
|
|
|
|
|
import DescriptionIcon from '@mui/icons-material/Description';
|
|
|
|
|
import ViewWeekIcon from '@mui/icons-material/ViewWeek';
|
2025-09-15 13:33:39 +01:00
|
|
|
|
import '../../types/embedPdf';
|
2025-09-11 19:08:44 +01:00
|
|
|
|
|
|
|
|
|
interface PdfViewerToolbarProps {
|
|
|
|
|
// Page navigation props (placeholders for now)
|
|
|
|
|
currentPage?: number;
|
|
|
|
|
totalPages?: number;
|
|
|
|
|
onPageChange?: (page: number) => void;
|
|
|
|
|
|
|
|
|
|
// Dual page toggle (placeholder for now)
|
|
|
|
|
dualPage?: boolean;
|
|
|
|
|
onDualPageToggle?: () => void;
|
|
|
|
|
|
2025-09-16 19:36:36 +01:00
|
|
|
|
// Zoom controls (connected via ViewerContext)
|
2025-09-11 19:08:44 +01:00
|
|
|
|
currentZoom?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function PdfViewerToolbar({
|
|
|
|
|
currentPage = 1,
|
2025-09-16 19:36:36 +01:00
|
|
|
|
totalPages: _totalPages = 1,
|
2025-09-11 19:08:44 +01:00
|
|
|
|
onPageChange,
|
|
|
|
|
dualPage = false,
|
|
|
|
|
onDualPageToggle,
|
2025-09-16 19:36:36 +01:00
|
|
|
|
currentZoom: _currentZoom = 100,
|
2025-09-11 19:08:44 +01:00
|
|
|
|
}: PdfViewerToolbarProps) {
|
|
|
|
|
const { t } = useTranslation();
|
2025-09-17 14:35:44 +01:00
|
|
|
|
const { getScrollState, getZoomState, scrollActions, zoomActions, registerImmediateZoomUpdate, registerImmediateScrollUpdate } = useViewer();
|
2025-09-16 19:36:36 +01:00
|
|
|
|
|
|
|
|
|
const scrollState = getScrollState();
|
|
|
|
|
const zoomState = getZoomState();
|
|
|
|
|
const [pageInput, setPageInput] = useState(scrollState.currentPage || currentPage);
|
2025-09-17 12:00:20 +01:00
|
|
|
|
const [displayZoomPercent, setDisplayZoomPercent] = useState(zoomState.zoomPercent || 140);
|
2025-09-11 19:08:44 +01:00
|
|
|
|
|
2025-09-17 14:35:44 +01:00
|
|
|
|
// Register for immediate scroll updates and sync with actual scroll state
|
2025-09-11 19:08:44 +01:00
|
|
|
|
useEffect(() => {
|
2025-09-17 14:35:44 +01:00
|
|
|
|
registerImmediateScrollUpdate((currentPage, totalPages) => {
|
|
|
|
|
setPageInput(currentPage);
|
|
|
|
|
});
|
2025-09-16 19:36:36 +01:00
|
|
|
|
setPageInput(scrollState.currentPage);
|
2025-09-17 14:35:44 +01:00
|
|
|
|
}, [registerImmediateScrollUpdate]);
|
2025-09-11 19:08:44 +01:00
|
|
|
|
|
2025-09-17 12:00:20 +01:00
|
|
|
|
// Register for immediate zoom updates and sync with actual zoom state
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
registerImmediateZoomUpdate(setDisplayZoomPercent);
|
|
|
|
|
setDisplayZoomPercent(zoomState.zoomPercent || 140);
|
|
|
|
|
}, [zoomState.zoomPercent, registerImmediateZoomUpdate]);
|
|
|
|
|
|
2025-09-11 19:08:44 +01:00
|
|
|
|
const handleZoomOut = () => {
|
2025-09-16 19:36:36 +01:00
|
|
|
|
zoomActions.zoomOut();
|
2025-09-11 19:08:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleZoomIn = () => {
|
2025-09-16 19:36:36 +01:00
|
|
|
|
zoomActions.zoomIn();
|
2025-09-11 19:08:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePageNavigation = (page: number) => {
|
2025-09-16 19:36:36 +01:00
|
|
|
|
scrollActions.scrollToPage(page);
|
|
|
|
|
if (onPageChange) {
|
2025-09-11 19:08:44 +01:00
|
|
|
|
onPageChange(page);
|
|
|
|
|
}
|
|
|
|
|
setPageInput(page);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleFirstPage = () => {
|
2025-09-16 19:36:36 +01:00
|
|
|
|
scrollActions.scrollToFirstPage();
|
2025-09-11 19:08:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePreviousPage = () => {
|
2025-09-16 19:36:36 +01:00
|
|
|
|
scrollActions.scrollToPreviousPage();
|
2025-09-11 19:08:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleNextPage = () => {
|
2025-09-16 19:36:36 +01:00
|
|
|
|
scrollActions.scrollToNextPage();
|
2025-09-11 19:08:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleLastPage = () => {
|
2025-09-16 19:36:36 +01:00
|
|
|
|
scrollActions.scrollToLastPage();
|
2025-09-11 19:08:44 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Paper
|
|
|
|
|
radius="xl xl 0 0"
|
|
|
|
|
shadow="sm"
|
|
|
|
|
p={12}
|
|
|
|
|
pb={12}
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
gap: 10,
|
|
|
|
|
borderTopLeftRadius: 16,
|
|
|
|
|
borderTopRightRadius: 16,
|
|
|
|
|
borderBottomLeftRadius: 0,
|
|
|
|
|
borderBottomRightRadius: 0,
|
|
|
|
|
boxShadow: "0 -2px 8px rgba(0,0,0,0.04)",
|
|
|
|
|
pointerEvents: "auto",
|
2025-09-12 16:38:29 +01:00
|
|
|
|
minWidth: '26.5rem',
|
2025-09-11 19:08:44 +01:00
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{/* First Page Button */}
|
|
|
|
|
<Button
|
|
|
|
|
variant="subtle"
|
|
|
|
|
color="blue"
|
|
|
|
|
size="md"
|
|
|
|
|
px={8}
|
|
|
|
|
radius="xl"
|
|
|
|
|
onClick={handleFirstPage}
|
2025-09-16 19:36:36 +01:00
|
|
|
|
disabled={scrollState.currentPage === 1}
|
2025-09-12 16:38:29 +01:00
|
|
|
|
style={{ minWidth: '2.5rem' }}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
title={t("viewer.firstPage", "First Page")}
|
|
|
|
|
>
|
|
|
|
|
<FirstPageIcon fontSize="small" />
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{/* Previous Page Button */}
|
|
|
|
|
<Button
|
|
|
|
|
variant="subtle"
|
|
|
|
|
color="blue"
|
|
|
|
|
size="md"
|
|
|
|
|
px={8}
|
|
|
|
|
radius="xl"
|
|
|
|
|
onClick={handlePreviousPage}
|
2025-09-16 19:36:36 +01:00
|
|
|
|
disabled={scrollState.currentPage === 1}
|
2025-09-12 16:38:29 +01:00
|
|
|
|
style={{ minWidth: '2.5rem' }}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
title={t("viewer.previousPage", "Previous Page")}
|
|
|
|
|
>
|
|
|
|
|
<ArrowBackIosIcon fontSize="small" />
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{/* Page Input */}
|
|
|
|
|
<NumberInput
|
|
|
|
|
value={pageInput}
|
|
|
|
|
onChange={(value) => {
|
|
|
|
|
const page = Number(value);
|
|
|
|
|
setPageInput(page);
|
2025-09-16 19:36:36 +01:00
|
|
|
|
if (!isNaN(page) && page >= 1 && page <= scrollState.totalPages) {
|
2025-09-11 19:08:44 +01:00
|
|
|
|
handlePageNavigation(page);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
min={1}
|
2025-09-16 19:36:36 +01:00
|
|
|
|
max={scrollState.totalPages}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
hideControls
|
|
|
|
|
styles={{
|
|
|
|
|
input: { width: 48, textAlign: "center", fontWeight: 500, fontSize: 16 },
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<span style={{ fontWeight: 500, fontSize: 16 }}>
|
2025-09-16 19:36:36 +01:00
|
|
|
|
/ {scrollState.totalPages}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Next Page Button */}
|
|
|
|
|
<Button
|
|
|
|
|
variant="subtle"
|
|
|
|
|
color="blue"
|
|
|
|
|
size="md"
|
|
|
|
|
px={8}
|
|
|
|
|
radius="xl"
|
|
|
|
|
onClick={handleNextPage}
|
2025-09-16 19:36:36 +01:00
|
|
|
|
disabled={scrollState.currentPage === scrollState.totalPages}
|
2025-09-12 16:38:29 +01:00
|
|
|
|
style={{ minWidth: '2.5rem' }}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
title={t("viewer.nextPage", "Next Page")}
|
|
|
|
|
>
|
|
|
|
|
<ArrowForwardIosIcon fontSize="small" />
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{/* Last Page Button */}
|
|
|
|
|
<Button
|
|
|
|
|
variant="subtle"
|
|
|
|
|
color="blue"
|
|
|
|
|
size="md"
|
|
|
|
|
px={8}
|
|
|
|
|
radius="xl"
|
|
|
|
|
onClick={handleLastPage}
|
2025-09-16 19:36:36 +01:00
|
|
|
|
disabled={scrollState.currentPage === scrollState.totalPages}
|
2025-09-12 16:38:29 +01:00
|
|
|
|
style={{ minWidth: '2.5rem' }}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
title={t("viewer.lastPage", "Last Page")}
|
|
|
|
|
>
|
|
|
|
|
<LastPageIcon fontSize="small" />
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{/* Dual Page Toggle */}
|
|
|
|
|
<Button
|
|
|
|
|
variant={dualPage ? "filled" : "light"}
|
|
|
|
|
color="blue"
|
|
|
|
|
size="md"
|
|
|
|
|
radius="xl"
|
|
|
|
|
onClick={onDualPageToggle}
|
2025-09-12 16:38:29 +01:00
|
|
|
|
style={{ minWidth: '2.5rem' }}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
title={dualPage ? t("viewer.singlePageView", "Single Page View") : t("viewer.dualPageView", "Dual Page View")}
|
|
|
|
|
>
|
|
|
|
|
{dualPage ? <DescriptionIcon fontSize="small" /> : <ViewWeekIcon fontSize="small" />}
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{/* Zoom Controls */}
|
|
|
|
|
<Group gap={4} align="center" style={{ marginLeft: 16 }}>
|
|
|
|
|
<Button
|
|
|
|
|
variant="subtle"
|
|
|
|
|
color="blue"
|
|
|
|
|
size="md"
|
|
|
|
|
radius="xl"
|
|
|
|
|
onClick={handleZoomOut}
|
2025-09-12 16:38:29 +01:00
|
|
|
|
style={{ minWidth: '2rem', padding: 0 }}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
title={t("viewer.zoomOut", "Zoom out")}
|
|
|
|
|
>
|
|
|
|
|
−
|
|
|
|
|
</Button>
|
2025-09-12 16:38:29 +01:00
|
|
|
|
<span style={{ minWidth: '2.5rem', textAlign: "center" }}>
|
2025-09-17 12:00:20 +01:00
|
|
|
|
{displayZoomPercent}%
|
2025-09-11 19:08:44 +01:00
|
|
|
|
</span>
|
|
|
|
|
<Button
|
|
|
|
|
variant="subtle"
|
|
|
|
|
color="blue"
|
|
|
|
|
size="md"
|
|
|
|
|
radius="xl"
|
|
|
|
|
onClick={handleZoomIn}
|
2025-09-12 16:38:29 +01:00
|
|
|
|
style={{ minWidth: '2rem', padding: 0 }}
|
2025-09-11 19:08:44 +01:00
|
|
|
|
title={t("viewer.zoomIn", "Zoom in")}
|
|
|
|
|
>
|
|
|
|
|
+
|
|
|
|
|
</Button>
|
|
|
|
|
</Group>
|
|
|
|
|
</Paper>
|
|
|
|
|
);
|
|
|
|
|
}
|