import { DialogFooter, Radio, RadioGroup, Tag } from '@blueprintjs/core'; import type { PageSizeName, PrintPageOptions } from '@zakodium/nmrium-core'; import type { CSSProperties, ReactNode } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Controller, useForm } from 'react-hook-form'; import ActionButtons from '../ActionButtons.js'; import type { LabelStyle } from '../Label.js'; import Label from '../Label.js'; import { NumberInput2Controller } from '../NumberInput2Controller.js'; import { Select2Controller } from '../Select2Controller.js'; import { StandardDialog } from '../StandardDialog.tsx'; import { StyledDialogBody } from '../StyledDialogBody.js'; import { PrintProvider } from './PrintProvider.js'; import { getSizesList, pageSizes } from './pageSize.js'; const isFirefox = navigator.userAgent.toLowerCase().includes('firefox'); type Layout = 'portrait' | 'landscape'; interface BasePrintProps { onPrint: (options: PrintPageOptions) => void; defaultPrintPageOptions: Partial; } interface InnerPrintFrameProps { children: ReactNode; onAfterPrint?: () => void; onBeforePrint?: () => void; printPageOptions?: Partial; } interface PrintFrameProps extends InnerPrintFrameProps, Partial {} export function PrintContent(props: PrintFrameProps) { const [isPageOptionModalOpened, togglePageOptionDialog] = useState(false); const [pageOptions, setPageOptions] = useState | null>(); const { onBeforePrint, onAfterPrint, children, printPageOptions, defaultPrintPageOptions, onPrint, } = props; useEffect(() => { function handleKeyDow(event: KeyboardEvent) { if ((event.ctrlKey || event.metaKey) && event.key === 'p') { event.preventDefault(); if (!printPageOptions) { togglePageOptionDialog(true); } else { setPageOptions(printPageOptions); } } } globalThis.addEventListener('keydown', handleKeyDow); return () => { globalThis.removeEventListener('keydown', handleKeyDow); }; }, [printPageOptions]); if (!pageOptions) { return ( { togglePageOptionDialog(false); }} onPrint={(options) => { togglePageOptionDialog(false); onPrint?.(options); setPageOptions(options); }} defaultPrintPageOptions={defaultPrintPageOptions || {}} /> ); } return ( <>
{ setPageOptions(null); onAfterPrint?.(); }} onBeforePrint={() => { onBeforePrint?.(); }} > {children} ); } function InnerPrintFrame(props: InnerPrintFrameProps) { const { children, onAfterPrint, onBeforePrint, printPageOptions = {}, } = props; const { size = 'A4', margin = 0, layout = 'landscape', } = printPageOptions || {}; const frameRef = useRef(null); const [content, setContent] = useState(); const { width = 0, height = 0 } = pageSizes.find((pageItem) => pageItem.name === size)?.[layout] || {}; const handleAfterPrint = useCallback(() => { onAfterPrint?.(); }, [onAfterPrint]); const handleBeforePrint = useCallback(() => { onBeforePrint?.(); }, [onBeforePrint]); const load = useCallback(() => { const contentWindow = frameRef.current?.contentWindow; if (!contentWindow) return; const document = contentWindow.document; setContent(document.body); transferStyles(document); appendPrintPageStyle(document, { size, layout, margin }); contentWindow.addEventListener('afterprint', handleAfterPrint); contentWindow.addEventListener('beforeprint', handleBeforePrint); return contentWindow; }, [handleAfterPrint, handleBeforePrint, layout, margin, size]); useEffect(() => { const contentWindow = frameRef.current?.contentWindow; if (!isFirefox) { load(); } return () => { if (!contentWindow) return; contentWindow.removeEventListener('afterprint', handleAfterPrint); contentWindow.removeEventListener('beforeprint', handleBeforePrint); }; }, [ onBeforePrint, onAfterPrint, handleBeforePrint, handleAfterPrint, size, layout, margin, load, ]); return ( ); } function RenderContainer(props: { onRenderComplete: () => void; children: ReactNode; style?: CSSProperties; }) { const { onRenderComplete, style, children } = props; useEffect(() => { const handleRenderComplete = () => { setTimeout(() => { onRenderComplete(); }, 250); }; const animationFrameId = requestAnimationFrame(handleRenderComplete); return () => { cancelAnimationFrame(animationFrameId); }; }, [onRenderComplete]); return
{children}
; } interface Style extends Pick { layout?: 'portrait' | 'landscape'; size?: string; } function appendPrintPageStyle(document: Document, style: Style = {}) { const { layout = 'landscape', size = 'A4' } = style; const styleElement = document.createElement('style'); styleElement.textContent = ` @media print { @page { size: ${size} ${layout}; padding:0; margin:0; } } `; document.head.append(styleElement); } function transferStyles(targetDocument: Document) { // Copy the style from the main page and inject it inside the iframe const styleSheets = Array.from(document.styleSheets); const targetHead = targetDocument.head; for (const styleSheet of styleSheets) { try { if (styleSheet.cssRules) { const newStyleEl = document.createElement('style'); const cssRules = Array.from(styleSheet.cssRules); const cssText = cssRules.map((rule) => rule.cssText).join('\n'); newStyleEl.append(document.createTextNode(cssText)); targetHead.append(newStyleEl); } } catch (error) { // eslint-disable-next-line no-console console.error('Error transferring styles:', error); } } } interface InnerPrintOptionsModalProps extends BasePrintProps { onCloseDialog: () => void; } interface PrintOptionsModalProps extends InnerPrintOptionsModalProps { isOpen: boolean; } function PrintPageOptionsModal(props: PrintOptionsModalProps) { const { isOpen, ...otherProps } = props; if (!isOpen) return; return ; } const labelStyle: LabelStyle = { label: { flex: 4, color: '#232323', }, wrapper: { flex: 8, display: 'flex', justifyContent: 'flex-start', }, container: { margin: '5px 0' }, }; interface PrintPagOptions { size: PageSizeName; layout: Layout; margin: number; } const INITIAL_VALUE: PrintPagOptions = { size: 'A4', layout: 'landscape', margin: 0, }; function InnerPrintOptionsModal(props: InnerPrintOptionsModalProps) { const { onCloseDialog, onPrint, defaultPrintPageOptions } = props; function submitHandler(values: any) { onPrint(values); onCloseDialog?.(); } const { handleSubmit, control, watch } = useForm({ defaultValues: { ...INITIAL_VALUE, ...defaultPrintPageOptions }, }); const layout = watch('layout'); const sizesList = getSizesList(layout); return ( { void handleSubmit(submitHandler)(); }} doneLabel="Print" onCancel={() => onCloseDialog?.()} /> ); }