import React, { useEffect, useImperativeHandle, useState } from 'react'; import type { CSSProperties } from 'react'; import { View, StyleSheet } from 'react-native'; import { useNavigator, usePreferencesObserver, useDecorationsObserver, } from '../../web/hooks'; import { convertToNavigatorLocator } from '../../web/utils/locationNormalizer'; import type { ReadiumProps as BaseReadiumProps, ReadiumViewRef as BaseReadiumViewRef } from './ReadiumView.types'; export type ReadiumProps = BaseReadiumProps & { height?: number; width?: number; }; export type ReadiumViewRef = BaseReadiumViewRef & { /** @deprecated Use goForward() */ nextPage: () => void; /** @deprecated Use goBackward() */ prevPage: () => void; }; export const ReadiumView = React.forwardRef( ( { file, preferences, decorations, onLocationChange, onPublicationReady, onDecorationActivated, style = {}, height, width, }, ref ) => { const [container, setContainer] = useState(null); const [currentPosition, setCurrentPosition] = useState(null); // Convert DecorationGroup[] to DecorationGroups record for web hooks const decorationsRecord = decorations ? Object.fromEntries(decorations.map((g) => [g.name, g.decorations])) : undefined; const { navigator, positions } = useNavigator({ file, onLocationChange, onPublicationReady, container, onPositionChange: setCurrentPosition, }); useImperativeHandle( ref, () => ({ goTo: (locator) => { if (!navigator) return; const navLocator = convertToNavigatorLocator(locator); if (navLocator) { // @ts-ignore navigator.go(navLocator, true, () => {}); } }, goForward: () => { navigator?.goForward(true, () => {}); }, goBackward: () => { navigator?.goBackward(true, () => {}); }, /** @deprecated Use goForward() */ nextPage: () => { navigator?.goForward(true, () => {}); }, /** @deprecated Use goBackward() */ prevPage: () => { navigator?.goBackward(true, () => {}); }, }), [navigator] ); usePreferencesObserver(navigator, preferences); useDecorationsObserver(navigator, decorationsRecord, onDecorationActivated); // Generate position label text const positionLabel = currentPosition && positions.length > 0 ? `${currentPosition} / ${positions.length}` : null; // Manage position label as a child of the readium-container useEffect(() => { if (!container) return; const existingLabel = container.querySelector('.position-label'); if (positionLabel) { if (!existingLabel) { const label = document.createElement('div'); label.className = 'position-label'; label.setAttribute('aria-live', 'polite'); label.textContent = positionLabel; container.appendChild(label); } else { existingLabel.textContent = positionLabel; } } else if (existingLabel) { existingLabel.remove(); } // Cleanup function return () => { const label = container.querySelector('.position-label'); if (label) { label.remove(); } }; }, [container, positionLabel, currentPosition, positions.length]); const mainStyle = { ...styles.maximize, ...(style as CSSProperties), }; if (height) mainStyle.height = height; if (width) mainStyle.width = width; // Determine theme colors based on preferences const getThemeColors = () => { const theme = preferences?.theme; switch (theme) { case 'dark': return { background: '#000000', text: '#ffffff' }; case 'sepia': return { background: '#f4ecd8', text: '#5f4b32' }; case 'light': default: return { background: '#ffffff', text: '#000000' }; } }; const themeColors = getThemeColors(); return ( {!navigator &&
Loading reader...
}
); } ); const loaderStyle: React.CSSProperties = { width: '100%', height: '100%', textAlign: 'center', position: 'relative', top: 'calc(50% - 10px)', }; const styles = StyleSheet.create({ container: { width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', }, readiumContainer: { // @ts-ignore contain: 'content', width: '100%', height: '100%', }, maximize: { width: '100%', height: '100%', display: 'flex', }, });