Compare commits

...

4 Commits

Author SHA1 Message Date
Reece Browne
1709ca9049 Rems 2025-09-12 16:38:29 +01:00
Reece Browne
18e4e03220 rename APIBridge 2025-09-12 16:26:05 +01:00
Reece Browne
9901771572 improve search 2025-09-12 16:19:07 +01:00
Reece Browne
514956570c pan state improvements 2025-09-12 15:06:06 +01:00
14 changed files with 168 additions and 146 deletions

View File

@ -7,16 +7,16 @@ import { useRightRail } from '../../contexts/RightRailContext';
import { useFileState, useFileSelection, useFileManagement } from '../../contexts/FileContext'; import { useFileState, useFileSelection, useFileManagement } from '../../contexts/FileContext';
import { useNavigationState } from '../../contexts/NavigationContext'; import { useNavigationState } from '../../contexts/NavigationContext';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { usePanState } from '../../hooks/usePanState';
import LanguageSelector from '../shared/LanguageSelector'; import LanguageSelector from '../shared/LanguageSelector';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider'; import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
import { Tooltip } from '../shared/Tooltip'; import { Tooltip } from '../shared/Tooltip';
import BulkSelectionPanel from '../pageEditor/BulkSelectionPanel'; import BulkSelectionPanel from '../pageEditor/BulkSelectionPanel';
import { SearchInterface } from '../viewer/SearchInterface';
export default function RightRail() { export default function RightRail() {
const { t } = useTranslation(); const { t } = useTranslation();
const isPanning = usePanState(); const [isPanning, setIsPanning] = useState(false);
const { toggleTheme } = useRainbowThemeContext(); const { toggleTheme } = useRainbowThemeContext();
const { buttons, actions } = useRightRail(); const { buttons, actions } = useRightRail();
const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]); const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]);
@ -215,15 +215,29 @@ export default function RightRail() {
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }}>
{/* Search */} {/* Search */}
<Tooltip content={t('rightRail.search', 'Search PDF')} position="left" offset={12} arrow> <Tooltip content={t('rightRail.search', 'Search PDF')} position="left" offset={12} arrow>
<Popover position="left" withArrow shadow="md" offset={8}>
<Popover.Target>
<div style={{ display: 'inline-flex' }}>
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
radius="md" radius="md"
className="right-rail-icon" className="right-rail-icon"
onClick={() => (window as any).togglePdfSearch?.()}
disabled={currentView !== 'viewer'} disabled={currentView !== 'viewer'}
aria-label={typeof t === 'function' ? t('rightRail.search', 'Search PDF') : 'Search PDF'}
> >
<LocalIcon icon="search" width="1.5rem" height="1.5rem" /> <LocalIcon icon="search" width="1.5rem" height="1.5rem" />
</ActionIcon> </ActionIcon>
</div>
</Popover.Target>
<Popover.Dropdown>
<div style={{ minWidth: '20rem' }}>
<SearchInterface
visible={true}
onClose={() => {}}
/>
</div>
</Popover.Dropdown>
</Popover>
</Tooltip> </Tooltip>
@ -234,7 +248,10 @@ export default function RightRail() {
color={isPanning ? "blue" : undefined} color={isPanning ? "blue" : undefined}
radius="md" radius="md"
className="right-rail-icon" className="right-rail-icon"
onClick={() => (window as any).embedPdfPan?.togglePan()} onClick={() => {
(window as any).embedPdfPan?.togglePan();
setIsPanning(!isPanning);
}}
disabled={currentView !== 'viewer'} disabled={currentView !== 'viewer'}
> >
<LocalIcon icon="pan-tool-rounded" width="1.5rem" height="1.5rem" /> <LocalIcon icon="pan-tool-rounded" width="1.5rem" height="1.5rem" />
@ -326,7 +343,7 @@ export default function RightRail() {
</div> </div>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
<div style={{ minWidth: 280 }}> <div style={{ minWidth: '17.5rem' }}>
<BulkSelectionPanel <BulkSelectionPanel
csvInput={csvInput} csvInput={csvInput}
setCsvInput={setCsvInput} setCsvInput={setCsvInput}

View File

@ -8,7 +8,6 @@ import { useFileState } from "../../contexts/FileContext";
import { useFileWithUrl } from "../../hooks/useFileWithUrl"; import { useFileWithUrl } from "../../hooks/useFileWithUrl";
import { LocalEmbedPDF } from './LocalEmbedPDF'; import { LocalEmbedPDF } from './LocalEmbedPDF';
import { PdfViewerToolbar } from './PdfViewerToolbar'; import { PdfViewerToolbar } from './PdfViewerToolbar';
import { SearchInterface } from './SearchInterface';
import { ThumbnailSidebar } from './ThumbnailSidebar'; import { ThumbnailSidebar } from './ThumbnailSidebar';
export interface EmbedPdfViewerProps { export interface EmbedPdfViewerProps {
@ -29,7 +28,6 @@ const EmbedPdfViewer = ({
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const viewerRef = React.useRef<HTMLDivElement>(null); const viewerRef = React.useRef<HTMLDivElement>(null);
const [isViewerHovered, setIsViewerHovered] = React.useState(false); const [isViewerHovered, setIsViewerHovered] = React.useState(false);
const [isSearchVisible, setIsSearchVisible] = React.useState(false);
const [isThumbnailSidebarVisible, setIsThumbnailSidebarVisible] = React.useState(false); const [isThumbnailSidebarVisible, setIsThumbnailSidebarVisible] = React.useState(false);
// Get current file from FileContext // Get current file from FileContext
@ -122,16 +120,11 @@ const EmbedPdfViewer = ({
// Expose toggle functions globally for right rail buttons // Expose toggle functions globally for right rail buttons
React.useEffect(() => { React.useEffect(() => {
(window as any).togglePdfSearch = () => {
setIsSearchVisible(prev => !prev);
};
(window as any).toggleThumbnailSidebar = () => { (window as any).toggleThumbnailSidebar = () => {
setIsThumbnailSidebarVisible(prev => !prev); setIsThumbnailSidebarVisible(prev => !prev);
}; };
return () => { return () => {
delete (window as any).togglePdfSearch;
delete (window as any).toggleThumbnailSidebar; delete (window as any).toggleThumbnailSidebar;
}; };
}, []); }, []);
@ -227,11 +220,6 @@ const EmbedPdfViewer = ({
</div> </div>
)} )}
{/* Search Interface Overlay */}
<SearchInterface
visible={isSearchVisible}
onClose={() => setIsSearchVisible(false)}
/>
{/* Thumbnail Sidebar */} {/* Thumbnail Sidebar */}
<ThumbnailSidebar <ThumbnailSidebar

View File

@ -17,13 +17,13 @@ import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
import { SearchPluginPackage } from '@embedpdf/plugin-search/react'; import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react'; import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
import { CustomSearchLayer } from './CustomSearchLayer'; import { CustomSearchLayer } from './CustomSearchLayer';
import { ZoomControlsExporter } from './ZoomControlsExporter'; import { ZoomAPIBridge } from './ZoomAPIBridge';
import { ScrollControlsExporter } from './ScrollControlsExporter'; import { ScrollAPIBridge } from './ScrollAPIBridge';
import { SelectionControlsExporter } from './SelectionControlsExporter'; import { SelectionAPIBridge } from './SelectionAPIBridge';
import { PanControlsExporter } from './PanControlsExporter'; import { PanAPIBridge } from './PanAPIBridge';
import { SpreadControlsExporter } from './SpreadControlsExporter'; import { SpreadAPIBridge } from './SpreadAPIBridge';
import { SearchControlsExporter } from './SearchControlsExporter'; import { SearchAPIBridge } from './SearchAPIBridge';
import { ThumbnailControlsExporter } from './ThumbnailControlsExporter'; import { ThumbnailAPIBridge } from './ThumbnailAPIBridge';
interface LocalEmbedPDFProps { interface LocalEmbedPDFProps {
file?: File | Blob; file?: File | Blob;
@ -180,13 +180,13 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
minWidth: 0 minWidth: 0
}}> }}>
<EmbedPDF engine={engine} plugins={plugins}> <EmbedPDF engine={engine} plugins={plugins}>
<ZoomControlsExporter /> <ZoomAPIBridge />
<ScrollControlsExporter /> <ScrollAPIBridge />
<SelectionControlsExporter /> <SelectionAPIBridge />
<PanControlsExporter /> <PanAPIBridge />
<SpreadControlsExporter /> <SpreadAPIBridge />
<SearchControlsExporter /> <SearchAPIBridge />
<ThumbnailControlsExporter /> <ThumbnailAPIBridge />
<GlobalPointerProvider> <GlobalPointerProvider>
<Viewport <Viewport
style={{ style={{

View File

@ -2,9 +2,9 @@ import { useEffect, useState } from 'react';
import { usePan } from '@embedpdf/plugin-pan/react'; import { usePan } from '@embedpdf/plugin-pan/react';
/** /**
* Component that runs inside EmbedPDF context and exports pan controls globally * Component that runs inside EmbedPDF context and bridges pan controls to global window
*/ */
export function PanControlsExporter() { export function PanAPIBridge() {
const { provides: pan, isPanning } = usePan(); const { provides: pan, isPanning } = usePan();
const [panStateListeners, setPanStateListeners] = useState<Array<(isPanning: boolean) => void>>([]); const [panStateListeners, setPanStateListeners] = useState<Array<(isPanning: boolean) => void>>([]);
@ -21,11 +21,10 @@ export function PanControlsExporter() {
pan.disablePan(); pan.disablePan();
}, },
togglePan: () => { togglePan: () => {
console.log('EmbedPDF: Toggling pan mode, current isPanning:', isPanning);
pan.togglePan(); pan.togglePan();
}, },
makePanDefault: () => pan.makePanDefault(), makePanDefault: () => pan.makePanDefault(),
isPanning: isPanning, get isPanning() { return isPanning; }, // Use getter to always return current value
// Subscribe to pan state changes for reactive UI // Subscribe to pan state changes for reactive UI
onPanStateChange: (callback: (isPanning: boolean) => void) => { onPanStateChange: (callback: (isPanning: boolean) => void) => {
setPanStateListeners(prev => [...prev, callback]); setPanStateListeners(prev => [...prev, callback]);
@ -36,11 +35,6 @@ export function PanControlsExporter() {
}, },
}; };
console.log('EmbedPDF pan controls exported to window.embedPdfPan', {
isPanning,
panAPI: pan,
availableMethods: Object.keys(pan)
});
} else { } else {
console.warn('EmbedPDF pan API not available yet'); console.warn('EmbedPDF pan API not available yet');
} }
@ -51,5 +45,5 @@ export function PanControlsExporter() {
panStateListeners.forEach(callback => callback(isPanning)); panStateListeners.forEach(callback => callback(isPanning));
}, [isPanning, panStateListeners]); }, [isPanning, panStateListeners]);
return null; // This component doesn't render anything return null;
} }

View File

@ -35,6 +35,7 @@ export function PdfViewerToolbar({
const [dynamicZoom, setDynamicZoom] = useState(currentZoom); const [dynamicZoom, setDynamicZoom] = useState(currentZoom);
const [dynamicPage, setDynamicPage] = useState(currentPage); const [dynamicPage, setDynamicPage] = useState(currentPage);
const [dynamicTotalPages, setDynamicTotalPages] = useState(totalPages); const [dynamicTotalPages, setDynamicTotalPages] = useState(totalPages);
const [isPanning, setIsPanning] = useState(false);
// Update zoom and scroll state from EmbedPDF APIs // Update zoom and scroll state from EmbedPDF APIs
useEffect(() => { useEffect(() => {
@ -53,6 +54,12 @@ export function PdfViewerToolbar({
setDynamicTotalPages(totalPagesNum); setDynamicTotalPages(totalPagesNum);
setPageInput(currentPageNum); setPageInput(currentPageNum);
} }
// Update pan mode state
if ((window as any).embedPdfPan) {
const panState = (window as any).embedPdfPan.isPanning || false;
setIsPanning(panState);
}
}; };
// Update state immediately // Update state immediately
@ -133,7 +140,7 @@ export function PdfViewerToolbar({
borderBottomRightRadius: 0, borderBottomRightRadius: 0,
boxShadow: "0 -2px 8px rgba(0,0,0,0.04)", boxShadow: "0 -2px 8px rgba(0,0,0,0.04)",
pointerEvents: "auto", pointerEvents: "auto",
minWidth: 420, minWidth: '26.5rem',
}} }}
> >
{/* First Page Button */} {/* First Page Button */}
@ -145,7 +152,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handleFirstPage} onClick={handleFirstPage}
disabled={dynamicPage === 1} disabled={dynamicPage === 1}
style={{ minWidth: 36 }} style={{ minWidth: '2.5rem' }}
title={t("viewer.firstPage", "First Page")} title={t("viewer.firstPage", "First Page")}
> >
<FirstPageIcon fontSize="small" /> <FirstPageIcon fontSize="small" />
@ -160,7 +167,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handlePreviousPage} onClick={handlePreviousPage}
disabled={dynamicPage === 1} disabled={dynamicPage === 1}
style={{ minWidth: 36 }} style={{ minWidth: '2.5rem' }}
title={t("viewer.previousPage", "Previous Page")} title={t("viewer.previousPage", "Previous Page")}
> >
<ArrowBackIosIcon fontSize="small" /> <ArrowBackIosIcon fontSize="small" />
@ -197,7 +204,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handleNextPage} onClick={handleNextPage}
disabled={dynamicPage === dynamicTotalPages} disabled={dynamicPage === dynamicTotalPages}
style={{ minWidth: 36 }} style={{ minWidth: '2.5rem' }}
title={t("viewer.nextPage", "Next Page")} title={t("viewer.nextPage", "Next Page")}
> >
<ArrowForwardIosIcon fontSize="small" /> <ArrowForwardIosIcon fontSize="small" />
@ -212,7 +219,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handleLastPage} onClick={handleLastPage}
disabled={dynamicPage === dynamicTotalPages} disabled={dynamicPage === dynamicTotalPages}
style={{ minWidth: 36 }} style={{ minWidth: '2.5rem' }}
title={t("viewer.lastPage", "Last Page")} title={t("viewer.lastPage", "Last Page")}
> >
<LastPageIcon fontSize="small" /> <LastPageIcon fontSize="small" />
@ -225,7 +232,7 @@ export function PdfViewerToolbar({
size="md" size="md"
radius="xl" radius="xl"
onClick={onDualPageToggle} onClick={onDualPageToggle}
style={{ minWidth: 36 }} style={{ minWidth: '2.5rem' }}
title={dualPage ? t("viewer.singlePageView", "Single Page View") : t("viewer.dualPageView", "Dual Page View")} title={dualPage ? t("viewer.singlePageView", "Single Page View") : t("viewer.dualPageView", "Dual Page View")}
> >
{dualPage ? <DescriptionIcon fontSize="small" /> : <ViewWeekIcon fontSize="small" />} {dualPage ? <DescriptionIcon fontSize="small" /> : <ViewWeekIcon fontSize="small" />}
@ -239,12 +246,12 @@ export function PdfViewerToolbar({
size="md" size="md"
radius="xl" radius="xl"
onClick={handleZoomOut} onClick={handleZoomOut}
style={{ minWidth: 32, padding: 0 }} style={{ minWidth: '2rem', padding: 0 }}
title={t("viewer.zoomOut", "Zoom out")} title={t("viewer.zoomOut", "Zoom out")}
> >
</Button> </Button>
<span style={{ minWidth: 40, textAlign: "center" }}> <span style={{ minWidth: '2.5rem', textAlign: "center" }}>
{dynamicZoom}% {dynamicZoom}%
</span> </span>
<Button <Button
@ -253,7 +260,7 @@ export function PdfViewerToolbar({
size="md" size="md"
radius="xl" radius="xl"
onClick={handleZoomIn} onClick={handleZoomIn}
style={{ minWidth: 32, padding: 0 }} style={{ minWidth: '2rem', padding: 0 }}
title={t("viewer.zoomIn", "Zoom in")} title={t("viewer.zoomIn", "Zoom in")}
> >
+ +

View File

@ -4,7 +4,7 @@ import { useScroll } from '@embedpdf/plugin-scroll/react';
/** /**
* Component that runs inside EmbedPDF context and exports scroll controls globally * Component that runs inside EmbedPDF context and exports scroll controls globally
*/ */
export function ScrollControlsExporter() { export function ScrollAPIBridge() {
const { provides: scroll, state: scrollState } = useScroll(); const { provides: scroll, state: scrollState } = useScroll();
useEffect(() => { useEffect(() => {
@ -23,5 +23,5 @@ export function ScrollControlsExporter() {
} }
}, [scroll, scrollState]); }, [scroll, scrollState]);
return null; // This component doesn't render anything return null;
} }

View File

@ -2,9 +2,9 @@ import { useEffect } from 'react';
import { useSearch } from '@embedpdf/plugin-search/react'; import { useSearch } from '@embedpdf/plugin-search/react';
/** /**
* Component that runs inside EmbedPDF context and exports search controls globally * Component that runs inside EmbedPDF context and bridges search controls to global window
*/ */
export function SearchControlsExporter() { export function SearchAPIBridge() {
const { provides: search, state } = useSearch(); const { provides: search, state } = useSearch();
useEffect(() => { useEffect(() => {
@ -48,5 +48,5 @@ export function SearchControlsExporter() {
} }
}, [search, state]); }, [search, state]);
return null; // This component doesn't render anything return null;
} }

View File

@ -11,6 +11,7 @@ interface SearchInterfaceProps {
export function SearchInterface({ visible, onClose }: SearchInterfaceProps) { export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [jumpToValue, setJumpToValue] = useState('');
const [resultInfo, setResultInfo] = useState<{ const [resultInfo, setResultInfo] = useState<{
currentIndex: number; currentIndex: number;
totalResults: number; totalResults: number;
@ -54,7 +55,11 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
}, [visible]); }, [visible]);
const handleSearch = async (query: string) => { const handleSearch = async (query: string) => {
if (!query.trim()) return; if (!query.trim()) {
// If query is empty, clear the search
handleClearSearch();
return;
}
const searchAPI = (window as any).embedPdfSearch; const searchAPI = (window as any).embedPdfSearch;
if (searchAPI) { if (searchAPI) {
@ -95,47 +100,61 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
const searchAPI = (window as any).embedPdfSearch; const searchAPI = (window as any).embedPdfSearch;
if (searchAPI) { if (searchAPI) {
searchAPI.clearSearch(); searchAPI.clearSearch();
// Also try to explicitly clear highlights if available
if (searchAPI.searchAPI && searchAPI.searchAPI.clearHighlights) {
searchAPI.searchAPI.clearHighlights();
}
} }
setSearchQuery(''); setSearchQuery('');
setResultInfo(null); setResultInfo(null);
}; };
// Sync search query with API state on mount
useEffect(() => {
const searchAPI = (window as any).embedPdfSearch;
if (searchAPI && searchAPI.state && searchAPI.state.query) {
setSearchQuery(searchAPI.state.query);
}
}, []);
const handleJumpToResult = (index: number) => {
const searchAPI = (window as any).embedPdfSearch;
if (searchAPI && resultInfo && index >= 1 && index <= resultInfo.totalResults) {
// Convert 1-based user input to 0-based API index
searchAPI.goToResult(index - 1);
}
};
const handleJumpToSubmit = () => {
const index = parseInt(jumpToValue);
if (index && resultInfo && index >= 1 && index <= resultInfo.totalResults) {
handleJumpToResult(index);
}
};
const handleJumpToKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleJumpToSubmit();
}
};
const handleClose = () => { const handleClose = () => {
handleClearSearch(); handleClearSearch();
onClose(); onClose();
}; };
if (!visible) return null;
return ( return (
<Box <Box
style={{ style={{
position: 'fixed', padding: '0px'
top: '20px',
right: '20px',
zIndex: 1000,
backgroundColor: 'var(--mantine-color-body)',
border: '1px solid var(--mantine-color-gray-3)',
borderRadius: '8px',
padding: '16px',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
minWidth: '320px',
maxWidth: '400px'
}} }}
> >
{/* Header with close button */} {/* Header */}
<Group justify="space-between" mb="md"> <Group mb="md">
<Text size="sm" fw={600}> <Text size="sm" fw={600}>
{t('search.title', 'Search PDF')} {t('search.title', 'Search PDF')}
</Text> </Text>
<ActionIcon
variant="subtle"
size="sm"
onClick={handleClose}
aria-label="Close search"
>
<LocalIcon icon="close" width="1rem" height="1rem" />
</ActionIcon>
</Group> </Group>
{/* Search input */} {/* Search input */}
@ -143,7 +162,14 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
<TextInput <TextInput
placeholder={t('search.placeholder', 'Enter search term...')} placeholder={t('search.placeholder', 'Enter search term...')}
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.currentTarget.value)} onChange={(e) => {
const newValue = e.currentTarget.value;
setSearchQuery(newValue);
// If user clears the input, clear the search highlights
if (!newValue.trim()) {
handleClearSearch();
}
}}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
style={{ flex: 1 }} style={{ flex: 1 }}
rightSection={ rightSection={
@ -162,15 +188,29 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
{/* Results info and navigation */} {/* Results info and navigation */}
{resultInfo && ( {resultInfo && (
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
{resultInfo.totalResults === 0 ? (
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
{resultInfo.totalResults === 0 {t('search.noResults', 'No results found')}
? t('search.noResults', 'No results found')
: t('search.resultCount', '{{current}} of {{total}}', {
current: resultInfo.currentIndex,
total: resultInfo.totalResults
})
}
</Text> </Text>
) : (
<Group gap="xs" align="center">
<TextInput
size="xs"
value={jumpToValue}
onChange={(e) => setJumpToValue(e.currentTarget.value)}
onKeyDown={handleJumpToKeyDown}
onBlur={handleJumpToSubmit}
placeholder={resultInfo.currentIndex.toString()}
style={{ width: '3rem' }}
type="number"
min="1"
max={resultInfo.totalResults}
/>
<Text size="sm" c="dimmed">
of {resultInfo.totalResults}
</Text>
</Group>
)}
{resultInfo.totalResults > 0 && ( {resultInfo.totalResults > 0 && (
<Group gap="xs"> <Group gap="xs">

View File

@ -4,7 +4,7 @@ import { useSelectionCapability, SelectionRangeX } from '@embedpdf/plugin-select
/** /**
* Component that runs inside EmbedPDF context and exports selection controls globally * Component that runs inside EmbedPDF context and exports selection controls globally
*/ */
export function SelectionControlsExporter() { export function SelectionAPIBridge() {
const { provides: selection } = useSelectionCapability(); const { provides: selection } = useSelectionCapability();
const [hasSelection, setHasSelection] = useState(false); const [hasSelection, setHasSelection] = useState(false);
@ -47,5 +47,5 @@ export function SelectionControlsExporter() {
} }
}, [selection, hasSelection]); }, [selection, hasSelection]);
return null; // This component doesn't render anything return null;
} }

View File

@ -4,7 +4,7 @@ import { useSpread, SpreadMode } from '@embedpdf/plugin-spread/react';
/** /**
* Component that runs inside EmbedPDF context and exports spread controls globally * Component that runs inside EmbedPDF context and exports spread controls globally
*/ */
export function SpreadControlsExporter() { export function SpreadAPIBridge() {
const { provides: spread, spreadMode } = useSpread(); const { provides: spread, spreadMode } = useSpread();
useEffect(() => { useEffect(() => {
@ -38,5 +38,5 @@ export function SpreadControlsExporter() {
} }
}, [spread, spreadMode]); }, [spread, spreadMode]);
return null; // This component doesn't render anything return null;
} }

View File

@ -4,11 +4,11 @@ import { useThumbnailCapability } from '@embedpdf/plugin-thumbnail/react';
/** /**
* Component that runs inside EmbedPDF context and exports thumbnail controls globally * Component that runs inside EmbedPDF context and exports thumbnail controls globally
*/ */
export function ThumbnailControlsExporter() { export function ThumbnailAPIBridge() {
const { provides: thumbnail } = useThumbnailCapability(); const { provides: thumbnail } = useThumbnailCapability();
useEffect(() => { useEffect(() => {
console.log('📄 ThumbnailControlsExporter useEffect:', { thumbnail: !!thumbnail }); console.log('📄 ThumbnailAPIBridge useEffect:', { thumbnail: !!thumbnail });
if (thumbnail) { if (thumbnail) {
console.log('📄 Exporting thumbnail controls to window:', { console.log('📄 Exporting thumbnail controls to window:', {
availableMethods: Object.keys(thumbnail), availableMethods: Object.keys(thumbnail),
@ -22,5 +22,5 @@ export function ThumbnailControlsExporter() {
} }
}, [thumbnail]); }, [thumbnail]);
return null; // This component doesn't render anything return null;
} }

View File

@ -111,7 +111,7 @@ export function ThumbnailSidebar({ visible, onToggle, colorScheme }: ThumbnailSi
right: 0, right: 0,
top: 0, top: 0,
bottom: 0, bottom: 0,
width: '240px', width: '15rem',
backgroundColor: actualColorScheme === 'dark' ? '#1a1b1e' : '#f8f9fa', backgroundColor: actualColorScheme === 'dark' ? '#1a1b1e' : '#f8f9fa',
borderLeft: `1px solid ${actualColorScheme === 'dark' ? '#373A40' : '#e9ecef'}`, borderLeft: `1px solid ${actualColorScheme === 'dark' ? '#373A40' : '#e9ecef'}`,
zIndex: 998, zIndex: 998,
@ -174,8 +174,8 @@ export function ThumbnailSidebar({ visible, onToggle, colorScheme }: ThumbnailSi
/> />
) : thumbnails[pageIndex] === 'error' ? ( ) : thumbnails[pageIndex] === 'error' ? (
<div style={{ <div style={{
width: '180px', width: '11.5rem',
height: '240px', height: '15rem',
backgroundColor: actualColorScheme === 'dark' ? '#2d1b1b' : '#ffebee', backgroundColor: actualColorScheme === 'dark' ? '#2d1b1b' : '#ffebee',
border: `1px solid ${actualColorScheme === 'dark' ? '#5d3737' : '#ffcdd2'}`, border: `1px solid ${actualColorScheme === 'dark' ? '#5d3737' : '#ffcdd2'}`,
borderRadius: '4px', borderRadius: '4px',
@ -189,8 +189,8 @@ export function ThumbnailSidebar({ visible, onToggle, colorScheme }: ThumbnailSi
</div> </div>
) : ( ) : (
<div style={{ <div style={{
width: '180px', width: '11.5rem',
height: '240px', height: '15rem',
backgroundColor: actualColorScheme === 'dark' ? '#25262b' : '#f8f9fa', backgroundColor: actualColorScheme === 'dark' ? '#25262b' : '#f8f9fa',
border: `1px solid ${actualColorScheme === 'dark' ? '#373A40' : '#e9ecef'}`, border: `1px solid ${actualColorScheme === 'dark' ? '#373A40' : '#e9ecef'}`,
borderRadius: '4px', borderRadius: '4px',

View File

@ -4,7 +4,7 @@ import { useZoom } from '@embedpdf/plugin-zoom/react';
/** /**
* Component that runs inside EmbedPDF context and exports zoom controls globally * Component that runs inside EmbedPDF context and exports zoom controls globally
*/ */
export function ZoomControlsExporter() { export function ZoomAPIBridge() {
const { provides: zoom, state: zoomState } = useZoom(); const { provides: zoom, state: zoomState } = useZoom();
useEffect(() => { useEffect(() => {
@ -22,5 +22,5 @@ export function ZoomControlsExporter() {
} }
}, [zoom, zoomState]); }, [zoom, zoomState]);
return null; // This component doesn't render anything return null;
} }

View File

@ -1,24 +0,0 @@
import { useState, useEffect } from 'react';
/**
* Hook to track EmbedPDF pan state for reactive UI components
*/
export function usePanState() {
const [isPanning, setIsPanning] = useState(false);
useEffect(() => {
// Subscribe to pan state changes
const unsubscribe = (window as any).embedPdfPan?.onPanStateChange?.((newIsPanning: boolean) => {
setIsPanning(newIsPanning);
});
// Get initial state
if ((window as any).embedPdfPan?.isPanning !== undefined) {
setIsPanning((window as any).embedPdfPan.isPanning);
}
return unsubscribe || (() => {});
}, []);
return isPanning;
}