Compare commits

..

No commits in common. "1709ca904925e2c6f672fcbeeabe806eb48d79f6" and "423617db52203f4de20c20188aae317b83fa851f" have entirely different histories.

14 changed files with 146 additions and 168 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, setIsPanning] = useState(false); const isPanning = usePanState();
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,29 +215,15 @@ 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}> <ActionIcon
<Popover.Target> variant="subtle"
<div style={{ display: 'inline-flex' }}> radius="md"
<ActionIcon className="right-rail-icon"
variant="subtle" onClick={() => (window as any).togglePdfSearch?.()}
radius="md" disabled={currentView !== 'viewer'}
className="right-rail-icon" >
disabled={currentView !== 'viewer'} <LocalIcon icon="search" width="1.5rem" height="1.5rem" />
aria-label={typeof t === 'function' ? t('rightRail.search', 'Search PDF') : 'Search PDF'} </ActionIcon>
>
<LocalIcon icon="search" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</Popover.Target>
<Popover.Dropdown>
<div style={{ minWidth: '20rem' }}>
<SearchInterface
visible={true}
onClose={() => {}}
/>
</div>
</Popover.Dropdown>
</Popover>
</Tooltip> </Tooltip>
@ -248,10 +234,7 @@ 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={() => { onClick={() => (window as any).embedPdfPan?.togglePan()}
(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" />
@ -343,7 +326,7 @@ export default function RightRail() {
</div> </div>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
<div style={{ minWidth: '17.5rem' }}> <div style={{ minWidth: 280 }}>
<BulkSelectionPanel <BulkSelectionPanel
csvInput={csvInput} csvInput={csvInput}
setCsvInput={setCsvInput} setCsvInput={setCsvInput}

View File

@ -8,6 +8,7 @@ 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 {
@ -28,6 +29,7 @@ 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
@ -120,11 +122,16 @@ 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;
}; };
}, []); }, []);
@ -220,6 +227,11 @@ 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 { ZoomAPIBridge } from './ZoomAPIBridge'; import { ZoomControlsExporter } from './ZoomControlsExporter';
import { ScrollAPIBridge } from './ScrollAPIBridge'; import { ScrollControlsExporter } from './ScrollControlsExporter';
import { SelectionAPIBridge } from './SelectionAPIBridge'; import { SelectionControlsExporter } from './SelectionControlsExporter';
import { PanAPIBridge } from './PanAPIBridge'; import { PanControlsExporter } from './PanControlsExporter';
import { SpreadAPIBridge } from './SpreadAPIBridge'; import { SpreadControlsExporter } from './SpreadControlsExporter';
import { SearchAPIBridge } from './SearchAPIBridge'; import { SearchControlsExporter } from './SearchControlsExporter';
import { ThumbnailAPIBridge } from './ThumbnailAPIBridge'; import { ThumbnailControlsExporter } from './ThumbnailControlsExporter';
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}>
<ZoomAPIBridge /> <ZoomControlsExporter />
<ScrollAPIBridge /> <ScrollControlsExporter />
<SelectionAPIBridge /> <SelectionControlsExporter />
<PanAPIBridge /> <PanControlsExporter />
<SpreadAPIBridge /> <SpreadControlsExporter />
<SearchAPIBridge /> <SearchControlsExporter />
<ThumbnailAPIBridge /> <ThumbnailControlsExporter />
<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 bridges pan controls to global window * Component that runs inside EmbedPDF context and exports pan controls globally
*/ */
export function PanAPIBridge() { export function PanControlsExporter() {
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,10 +21,11 @@ export function PanAPIBridge() {
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(),
get isPanning() { return isPanning; }, // Use getter to always return current value isPanning: isPanning,
// 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]);
@ -35,6 +36,11 @@ export function PanAPIBridge() {
}, },
}; };
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');
} }
@ -45,5 +51,5 @@ export function PanAPIBridge() {
panStateListeners.forEach(callback => callback(isPanning)); panStateListeners.forEach(callback => callback(isPanning));
}, [isPanning, panStateListeners]); }, [isPanning, panStateListeners]);
return null; return null; // This component doesn't render anything
} }

View File

@ -35,7 +35,6 @@ 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(() => {
@ -54,12 +53,6 @@ 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
@ -140,7 +133,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: '26.5rem', minWidth: 420,
}} }}
> >
{/* First Page Button */} {/* First Page Button */}
@ -152,7 +145,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handleFirstPage} onClick={handleFirstPage}
disabled={dynamicPage === 1} disabled={dynamicPage === 1}
style={{ minWidth: '2.5rem' }} style={{ minWidth: 36 }}
title={t("viewer.firstPage", "First Page")} title={t("viewer.firstPage", "First Page")}
> >
<FirstPageIcon fontSize="small" /> <FirstPageIcon fontSize="small" />
@ -167,7 +160,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handlePreviousPage} onClick={handlePreviousPage}
disabled={dynamicPage === 1} disabled={dynamicPage === 1}
style={{ minWidth: '2.5rem' }} style={{ minWidth: 36 }}
title={t("viewer.previousPage", "Previous Page")} title={t("viewer.previousPage", "Previous Page")}
> >
<ArrowBackIosIcon fontSize="small" /> <ArrowBackIosIcon fontSize="small" />
@ -204,7 +197,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handleNextPage} onClick={handleNextPage}
disabled={dynamicPage === dynamicTotalPages} disabled={dynamicPage === dynamicTotalPages}
style={{ minWidth: '2.5rem' }} style={{ minWidth: 36 }}
title={t("viewer.nextPage", "Next Page")} title={t("viewer.nextPage", "Next Page")}
> >
<ArrowForwardIosIcon fontSize="small" /> <ArrowForwardIosIcon fontSize="small" />
@ -219,7 +212,7 @@ export function PdfViewerToolbar({
radius="xl" radius="xl"
onClick={handleLastPage} onClick={handleLastPage}
disabled={dynamicPage === dynamicTotalPages} disabled={dynamicPage === dynamicTotalPages}
style={{ minWidth: '2.5rem' }} style={{ minWidth: 36 }}
title={t("viewer.lastPage", "Last Page")} title={t("viewer.lastPage", "Last Page")}
> >
<LastPageIcon fontSize="small" /> <LastPageIcon fontSize="small" />
@ -232,7 +225,7 @@ export function PdfViewerToolbar({
size="md" size="md"
radius="xl" radius="xl"
onClick={onDualPageToggle} onClick={onDualPageToggle}
style={{ minWidth: '2.5rem' }} style={{ minWidth: 36 }}
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" />}
@ -246,12 +239,12 @@ export function PdfViewerToolbar({
size="md" size="md"
radius="xl" radius="xl"
onClick={handleZoomOut} onClick={handleZoomOut}
style={{ minWidth: '2rem', padding: 0 }} style={{ minWidth: 32, padding: 0 }}
title={t("viewer.zoomOut", "Zoom out")} title={t("viewer.zoomOut", "Zoom out")}
> >
</Button> </Button>
<span style={{ minWidth: '2.5rem', textAlign: "center" }}> <span style={{ minWidth: 40, textAlign: "center" }}>
{dynamicZoom}% {dynamicZoom}%
</span> </span>
<Button <Button
@ -260,7 +253,7 @@ export function PdfViewerToolbar({
size="md" size="md"
radius="xl" radius="xl"
onClick={handleZoomIn} onClick={handleZoomIn}
style={{ minWidth: '2rem', padding: 0 }} style={{ minWidth: 32, 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 ScrollAPIBridge() { export function ScrollControlsExporter() {
const { provides: scroll, state: scrollState } = useScroll(); const { provides: scroll, state: scrollState } = useScroll();
useEffect(() => { useEffect(() => {
@ -23,5 +23,5 @@ export function ScrollAPIBridge() {
} }
}, [scroll, scrollState]); }, [scroll, scrollState]);
return null; return null; // This component doesn't render anything
} }

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 bridges search controls to global window * Component that runs inside EmbedPDF context and exports search controls globally
*/ */
export function SearchAPIBridge() { export function SearchControlsExporter() {
const { provides: search, state } = useSearch(); const { provides: search, state } = useSearch();
useEffect(() => { useEffect(() => {
@ -48,5 +48,5 @@ export function SearchAPIBridge() {
} }
}, [search, state]); }, [search, state]);
return null; return null; // This component doesn't render anything
} }

View File

@ -11,7 +11,6 @@ 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;
@ -55,11 +54,7 @@ export function SearchInterface({ visible, onClose }: SearchInterfaceProps) {
}, [visible]); }, [visible]);
const handleSearch = async (query: string) => { const handleSearch = async (query: string) => {
if (!query.trim()) { if (!query.trim()) return;
// 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) {
@ -100,61 +95,47 @@ 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={{
padding: '0px' position: 'fixed',
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 */} {/* Header with close button */}
<Group mb="md"> <Group justify="space-between" 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 */}
@ -162,14 +143,7 @@ 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) => { onChange={(e) => setSearchQuery(e.currentTarget.value)}
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={
@ -188,29 +162,15 @@ 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')
</Text> : t('search.resultCount', '{{current}} of {{total}}', {
) : ( current: resultInfo.currentIndex,
<Group gap="xs" align="center"> total: resultInfo.totalResults
<TextInput })
size="xs" }
value={jumpToValue} </Text>
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 SelectionAPIBridge() { export function SelectionControlsExporter() {
const { provides: selection } = useSelectionCapability(); const { provides: selection } = useSelectionCapability();
const [hasSelection, setHasSelection] = useState(false); const [hasSelection, setHasSelection] = useState(false);
@ -47,5 +47,5 @@ export function SelectionAPIBridge() {
} }
}, [selection, hasSelection]); }, [selection, hasSelection]);
return null; return null; // This component doesn't render anything
} }

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 SpreadAPIBridge() { export function SpreadControlsExporter() {
const { provides: spread, spreadMode } = useSpread(); const { provides: spread, spreadMode } = useSpread();
useEffect(() => { useEffect(() => {
@ -38,5 +38,5 @@ export function SpreadAPIBridge() {
} }
}, [spread, spreadMode]); }, [spread, spreadMode]);
return null; return null; // This component doesn't render anything
} }

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 ThumbnailAPIBridge() { export function ThumbnailControlsExporter() {
const { provides: thumbnail } = useThumbnailCapability(); const { provides: thumbnail } = useThumbnailCapability();
useEffect(() => { useEffect(() => {
console.log('📄 ThumbnailAPIBridge useEffect:', { thumbnail: !!thumbnail }); console.log('📄 ThumbnailControlsExporter 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 ThumbnailAPIBridge() {
} }
}, [thumbnail]); }, [thumbnail]);
return null; return null; // This component doesn't render anything
} }

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: '15rem', width: '240px',
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: '11.5rem', width: '180px',
height: '15rem', height: '240px',
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: '11.5rem', width: '180px',
height: '15rem', height: '240px',
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 ZoomAPIBridge() { export function ZoomControlsExporter() {
const { provides: zoom, state: zoomState } = useZoom(); const { provides: zoom, state: zoomState } = useZoom();
useEffect(() => { useEffect(() => {
@ -22,5 +22,5 @@ export function ZoomAPIBridge() {
} }
}, [zoom, zoomState]); }, [zoom, zoomState]);
return null; return null; // This component doesn't render anything
} }

View File

@ -0,0 +1,24 @@
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;
}