cyntler / react-doc-viewer

File viewer for React.
https://cyntler.github.io/react-doc-viewer
Apache License 2.0
383 stars 127 forks source link

Scroll to page #172

Open laaragm opened 1 year ago

laaragm commented 1 year ago

Hi! Is it possible to scroll to a particular page given a certain document? Is it something you'd consider for the future? Thanks.

zhongshuai-cao commented 1 year ago

it could be very helpful to include this function!

karam-khanna commented 1 year ago

+1 on this

VAJRESH commented 1 year ago

@cyntler can you assign this to me, I can set default page for pdf

mathieugruson commented 8 months ago

Hello @VAJRESH Do you know when will be included that feature ? Thanks

For anybody coming here, this is a way I have found to solve this problem by selecting the html tag of the good page and scrolling to there and adding some wait time to let the doc to load

      useEffect(() => {

        const handleSetDocs = (() => {

               // Delay scrolling until the element is likely to be loaded
                const timer = setTimeout(() => {

                    const element = document.getElementById("lolo");
                    console.log('element\n', element);
                    const elementsArray = document.querySelectorAll('.sc-bjfHPd.kQleml');
                    if (elementsArray.length > 0) {
                        console.log('elementsArray\n', elementsArray);
                        const ElementPage = elementsArray[(props.pageToDisplay - 1)].getBoundingClientRect();
                        if (element) {
                          console.log('c2: ', ElementPage.top);
                            element.scrollTo(0, ElementPage.top )
                          }
                    }

                }, 2500); // Delay for X milliseconds (1 second)

                return () => clearTimeout(timer); // Cleanup the timer

            })

        handleSetDocs();

    }, [props.pageToDisplay])
angeal-chw commented 3 months ago

Hi everyone, just checking if anyone have a better solution to this issue? The above useEffect code is buggy for me as it requires the document to be loaded before the scrolling happens, occasionally a bug occurs where the scrolling causes the document to scroll to a random page. Thanks in advance!

jooskesters commented 2 months ago

I'm not getting this code to work. When I log what's inside const element it says null. What Id should I pick there ?

When i pick react-doc-viewer as id, I get the following console output, but no scrolling is taking place ...

image

Support for scroll to page would be a huge improvement, please implement it in the package...

mathieugruson commented 1 month ago

Hi, sorry for my late answer. Actually, I ended not using this library as it was buggy as you said. I finally used react-pdf library and calculating the page size with js and hmlt elelemt

'use client';

import { useCallback, useState, useEffect, useRef } from 'react';
import { useResizeObserver } from '@wojtekmaj/react-hooks';
import { pdfjs, Document, Page } from 'react-pdf';
import { useSession } from 'next-auth/react';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';

// pdfjs.GlobalWorkerOptions.workerSrc = new URL(
//   'pdfjs-dist/build/pdf.worker.min.js',
//   import.meta.url,
// ).toString();

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;

const options = {
  cMapUrl: '/cmaps/',
  standardFontDataUrl: '/standard_fonts/',
};

const resizeObserverOptions = {};

const maxWidth = 800;

type PDFFile = string | File | null;

function PdfDisplay({ fileToDisplay, docViewerWidth, numPages, setNumPages, zoomLevel, setCurrentPage } : any) {

  const documentRef = useRef<HTMLDivElement>(null); // Create a ref for the document component
  const [fileToDisplayChecked, setFileToDisplayChecked] = useState('')
  const [urlFileToDisplay, setUrlFileToDisplay] = useState<any>('')
  const { data: session, status } = useSession();

  function getFileName(filePath : string) {
    // Get the last portion of the path (in case it's a path)
    const baseName = filePath.split('/').pop();
    // Find the last dot where the extension starts
    const dotIndex = baseName?.lastIndexOf('.');
    // If there is no dot, return the whole name; otherwise, return the part before the dot
    if (!dotIndex)
        return baseName
    return dotIndex === -1 ? baseName : baseName?.substring(0, dotIndex);
}

function getFileExtension(filePath : string) {
    // Get the last portion of the path (in case it's a path)
    const baseName = filePath.split('/').pop();
    // Find the last dot where the extension starts
    const dotIndex = baseName?.lastIndexOf('.');
    // If there is no dot, return an empty string; otherwise, return the part after the dot
    if (!dotIndex)
        return baseName
    return dotIndex === -1 ? '' : baseName?.substring(dotIndex + 1);
}

useEffect(() => {
  const fetchFileToDisplay = async () => {
    console.log('fileToDisplay', fileToDisplay);

    try {
      const response = await fetch(`back_api/folder-creation/serve`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          authorization: `Bearer ${session?.backendTokens.accessToken}`,
        },
        body: JSON.stringify({
          filePath: `${fileToDisplay}`,
        }),
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }

      const blob = await response.blob();
      const url = URL.createObjectURL(blob);
      console.log('url\n', url);

      setUrlFileToDisplay(url);

    } catch (error) {
      console.error('There has been a problem with your fetch operation:', error);
    }
  };

  if (fileToDisplay !== '') {
    fetchFileToDisplay();
  }

  // Cleanup or other actions to be taken on component unmount or before re-running the effect
  return () => {
    // second - e.g., cleanup actions
  };
}, [fileToDisplay]); // third - Assuming `path` is the dependency that triggers this effect

  useEffect(() => {
    const REGEX_FOR_FILENAME = /[^\/]+\.(pdf|txt|png|jpg|jpeg|docx|doc)/g

    const REGEX_FOR_PATH = /^.*\//g

    const pathFromRegex = fileToDisplay.match(REGEX_FOR_PATH)
    console.log('pthFromRegex\n', pathFromRegex);

    const filenameFromRegex = fileToDisplay.match(REGEX_FOR_FILENAME)

    const extension = getFileExtension(fileToDisplay)
    const fileName = getFileName(fileToDisplay)

    if (extension === 'docx') {
      const fileDisplay = `${pathFromRegex[0]}/.${fileName}.pdf`
      console.log('fileDisplay\n', fileDisplay);
      setFileToDisplayChecked(fileDisplay)
    } else {
      setFileToDisplayChecked(fileToDisplay)
    }

  }, [fileToDisplay])

  const handleScroll = useCallback(() => {
    // Query the document for elements with the class 'react-pdf__Page'
    const pages = documentRef.current?.getElementsByClassName('react-pdf__Page');
    if (pages && pages.length > 1) {
      // Use type assertion to treat elements as HTMLElements
      const firstPage = pages[0] as HTMLElement;
      const secondPage = pages[1] as HTMLElement;

      // Calculate the height of a page by finding the distance between two consecutive pages
      const pageHeight = secondPage.offsetTop - firstPage.offsetTop;
      // console.log('pageHeight\n', pageHeight);

      var element = document.getElementById('lolo');

      if (!element) return;

      const { scrollTop } = element;      

      const newCurrentPage = Math.floor((scrollTop - (pageHeight * 0.75)) / pageHeight) + 2;
      // console.log('Math.floor((scrollTop - (pageHeight * 0.75)) / pageHeight)\n', Math.floor((scrollTop - (pageHeight * 0.75)) / pageHeight));
      // console.log(`newCurrentPage\n`, newCurrentPage);

      setCurrentPage(newCurrentPage <= numPages ? newCurrentPage : numPages); // Ensure the page number is within bounds
    }
  }, [numPages, setCurrentPage]);

  useEffect(() => {
    const div = documentRef.current;
    if (div) {
      div.addEventListener('scroll', handleScroll, { passive: true });
    }
    return () => {
      if (div) {
        div.removeEventListener('scroll', handleScroll);
      }
    };
  }, [handleScroll]);

  const onDocumentLoadSuccess = useCallback(({ numPages }) => {
    setNumPages(numPages);
  }, []);

    return (
      <div
      id="lolo"
      onScroll={() => {handleScroll()}}
      style={{ width: `${docViewerWidth}px`, overflow: `overlay` }}
      className='relative h-[calc(100%-32px)] mt-[2px] border-1 border-black bg-[#202124] flex justify-center overflow-auto overflow-y-scroll scroll-p-0 scrollbar-thin scrollbar-rounded-lg scrollbar-thumb-[#858585] scrollbar-track-[#202124]'>    
      <div ref={documentRef}>
        <Document
          file={urlFileToDisplay}
          onLoadSuccess={onDocumentLoadSuccess}>
          {Array.from(
            new Array(numPages),
            (el, index) => (
              <div>
                <Page
                  key={`page_${index + 1}`}
                  pageNumber={index + 1}
                  renderTextLayer={false}
                  scale={zoomLevel}
                  />
                <br/>
                </div>
              ),
              )}
        </Document>
          </div>
      </div>
      );
}

export default PdfDisplay

You can ask an LLM to explain more in detail how it is done here. I put all my code if you want to see how i handle all the stuff. Sorry if it is noisy