Last Meeting Changes - Document page change according to scroll
This commit is contained in:
parent
c8bba1d34f
commit
78f895bd2c
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md';
|
import { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md';
|
||||||
import { DocumentType } from './SignDocument';
|
import { DocumentType } from './SignDocument';
|
||||||
import { DroppedItem } from '../../utlity/types';
|
import { DroppedItem } from '../../utlity/types';
|
||||||
|
|
@ -9,25 +9,35 @@ interface DocumentPagesInterface {
|
||||||
setDropDownTogglePage: React.Dispatch<React.SetStateAction<string>>;
|
setDropDownTogglePage: React.Dispatch<React.SetStateAction<string>>;
|
||||||
dropDownTogglePage: string;
|
dropDownTogglePage: string;
|
||||||
droppedItems: Record<string, DroppedItem[]> | null;
|
droppedItems: Record<string, DroppedItem[]> | null;
|
||||||
|
isPages: boolean;
|
||||||
|
setIsPages: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
activePage: string | null; // NEW
|
||||||
|
onPageSelect: (key: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage, droppedItems }: DocumentPagesInterface) => {
|
const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage, droppedItems, isPages, setIsPages, activePage, onPageSelect }: DocumentPagesInterface) => {
|
||||||
|
|
||||||
const [activePage, setActivePage] = useState<string | null>(
|
// const [activePage, setActivePage] = useState<string | null>(
|
||||||
selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
|
// selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
|
||||||
)
|
// )
|
||||||
|
|
||||||
const [documentPagesTagsMapping, setDocumentPagesTagsMapping] = useState<Record<string, Record<number, [number, string]>>>({})
|
const [documentPagesTagsMapping, setDocumentPagesTagsMapping] = useState<Record<string, Record<number, [number, string]>>>({})
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const documentId = selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
|
// const documentId = selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
|
||||||
if (documentId === null) return;
|
// if (documentId === null) return;
|
||||||
|
|
||||||
const key = 'pageDropDown' + 0;
|
// const key = 'pageDropDown' + 0;
|
||||||
setDropDownTogglePage(dropDownTogglePage === key ? '' : key);
|
// setDropDownTogglePage(dropDownTogglePage === key ? '' : key);
|
||||||
setActivePage(`${documentId}_0`);
|
// setActivePage(`${documentId}_0`);
|
||||||
window.location.hash = documentId + "0";
|
// window.location.hash = documentId + "0";
|
||||||
}, [selectedDocuments])
|
// }, [selectedDocuments])
|
||||||
|
|
||||||
|
|
||||||
|
// const goToPage = (docId: string, pageIndex: number) => {
|
||||||
|
// const key = `${docId}_${pageIndex}`;
|
||||||
|
// onPageSelect(key);
|
||||||
|
// };
|
||||||
|
|
||||||
const [openDocs, setOpenDocs] = useState<Record<string, boolean>>(
|
const [openDocs, setOpenDocs] = useState<Record<string, boolean>>(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -54,11 +64,18 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
}, [selectedDocuments])
|
}, [selectedDocuments])
|
||||||
|
|
||||||
const goToPage = (documentId: string, pageIndex: number) => {
|
const goToPage = (documentId: string, pageIndex: number) => {
|
||||||
|
console.log("boom Active Page:", activePage);
|
||||||
const key = `${documentId}_${pageIndex}`;
|
const key = `${documentId}_${pageIndex}`;
|
||||||
setActivePage(key);
|
onPageSelect(key);
|
||||||
window.location.hash = documentId + "" + pageIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const thumbnailRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // NEW
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!activePage) return;
|
||||||
|
const el = thumbnailRefs.current[activePage];
|
||||||
|
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}, [activePage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -87,7 +104,9 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
// const isOpen = dropDownTogglePage === 'pageDropDown' + indx;
|
// const isOpen = dropDownTogglePage === 'pageDropDown' + indx;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={indx} className="border-b border-slate-200">
|
<div
|
||||||
|
key={indx}
|
||||||
|
className="border-b border-slate-200">
|
||||||
|
|
||||||
{/* Document Header */}
|
{/* Document Header */}
|
||||||
<div
|
<div
|
||||||
|
|
@ -118,6 +137,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={pageIndex}
|
key={pageIndex}
|
||||||
|
ref={(el) => { thumbnailRefs.current[key] = el; }} // NEW
|
||||||
onClick={() => goToPage(document.documentId, pageIndex)}
|
onClick={() => goToPage(document.documentId, pageIndex)}
|
||||||
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
|
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,8 @@ enum RightSideNav {
|
||||||
PAGES = "pages"
|
PAGES = "pages"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selectedDoc: { [key: string]: string | null } = { key: null }
|
||||||
|
|
||||||
const SignDocument = () => {
|
const SignDocument = () => {
|
||||||
|
|
||||||
const windowWidth = window.innerWidth;
|
const windowWidth = window.innerWidth;
|
||||||
|
|
@ -179,6 +181,7 @@ const SignDocument = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
|
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
|
||||||
|
const pageRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
|
||||||
|
|
||||||
// Signer data - START
|
// Signer data - START
|
||||||
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
|
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
|
||||||
|
|
@ -187,6 +190,8 @@ const SignDocument = () => {
|
||||||
|
|
||||||
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
|
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
|
||||||
const [isPages, setIsPages] = useState<boolean>(true);
|
const [isPages, setIsPages] = useState<boolean>(true);
|
||||||
|
const [activePage, setActivePage] = useState<string | null>(null);
|
||||||
|
const isProgrammaticScrollRef = useRef(false);
|
||||||
|
|
||||||
const [recipient, setRecipient] = useState<RecipientType | null>(null);
|
const [recipient, setRecipient] = useState<RecipientType | null>(null);
|
||||||
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
|
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
|
||||||
|
|
@ -252,6 +257,56 @@ const SignDocument = () => {
|
||||||
};
|
};
|
||||||
// Handle Confirm Sign Modal - END
|
// Handle Confirm Sign Modal - END
|
||||||
|
|
||||||
|
// Sync activePage with scroll position via IntersectionObserver
|
||||||
|
useEffect(() => {
|
||||||
|
const scrollRoot = document.getElementById('doc-con');
|
||||||
|
if (!scrollRoot) return;
|
||||||
|
|
||||||
|
// Track intersection ratio per page so we can pick the most-visible one
|
||||||
|
// when multiple pages are partially visible at once.
|
||||||
|
const ratios: Record<string, number> = {};
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
// Ignore intersection changes caused by our own programmatic
|
||||||
|
// scroll (e.g. clicking a thumbnail) to avoid feedback loops.
|
||||||
|
if (isProgrammaticScrollRef.current) return;
|
||||||
|
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const key = entry.target.getAttribute('data-page-key');
|
||||||
|
if (!key) return;
|
||||||
|
ratios[key] = entry.isIntersecting ? entry.intersectionRatio : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pick the key with the highest intersection ratio
|
||||||
|
let bestKey: string | null = null;
|
||||||
|
let bestRatio = 0;
|
||||||
|
for (const [key, ratio] of Object.entries(ratios)) {
|
||||||
|
if (ratio > bestRatio) {
|
||||||
|
bestRatio = ratio;
|
||||||
|
bestKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestKey && bestRatio > 0) {
|
||||||
|
setActivePage((prev) => (prev === bestKey ? prev : bestKey)); // dedupe, avoid re-render
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: scrollRoot,
|
||||||
|
// Multiple thresholds so ratio updates fire smoothly while
|
||||||
|
// scrolling, without needing scroll-event listeners at all.
|
||||||
|
threshold: [0, 0.25, 0.5, 0.75, 1],
|
||||||
|
rootMargin: '0px',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.values(pageRefs.current).forEach((el) => {
|
||||||
|
if (el) observer.observe(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [selectedDocuments]);
|
||||||
|
|
||||||
// To center the container on every zoom scale change - START
|
// To center the container on every zoom scale change - START
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -272,30 +327,30 @@ const SignDocument = () => {
|
||||||
// To center the container on every zoom scale change - END
|
// To center the container on every zoom scale change - END
|
||||||
|
|
||||||
// Handle Tags and Image Size - START
|
// Handle Tags and Image Size - START
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const observers: ResizeObserver[] = [];
|
// const observers: ResizeObserver[] = [];
|
||||||
|
|
||||||
Object.keys(imageRefs.current).forEach((key) => {
|
// Object.keys(imageRefs.current).forEach((key) => {
|
||||||
const el = imageRefs.current[key];
|
// const el = imageRefs.current[key];
|
||||||
if (!el) return;
|
// if (!el) return;
|
||||||
|
|
||||||
const observer = new ResizeObserver((entries) => {
|
// const observer = new ResizeObserver((entries) => {
|
||||||
for (const entry of entries) {
|
// for (const entry of entries) {
|
||||||
const { width, height } = entry.contentRect;
|
// const { width, height } = entry.contentRect;
|
||||||
setImageSizes((prev) => ({
|
// setImageSizes((prev) => ({
|
||||||
...prev,
|
// ...prev,
|
||||||
[key]: { width, height },
|
// [key]: { width, height },
|
||||||
}));
|
// }));
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
observer.observe(el);
|
// observer.observe(el);
|
||||||
observers.push(observer);
|
// observers.push(observer);
|
||||||
});
|
// });
|
||||||
|
|
||||||
return () => observers.forEach((o) => o.disconnect());
|
// return () => observers.forEach((o) => o.disconnect());
|
||||||
|
|
||||||
}, [selectedDocuments]); // re-run when documents load
|
// }, [selectedDocuments]); // re-run when documents load
|
||||||
// Handle Tags anf Image Size = END
|
// Handle Tags anf Image Size = END
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -345,6 +400,16 @@ const SignDocument = () => {
|
||||||
setSelectedTag({ key, index })
|
setSelectedTag({ key, index })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedDocuments && selectedDocuments.length > 0 && selectedDocuments[0].pages?.length > 0) {
|
||||||
|
const firstDoc = selectedDocuments[0];
|
||||||
|
const firstPage = firstDoc.pages[0];
|
||||||
|
if (!selectedDoc.key) {
|
||||||
|
selectedDoc.key = `${firstDoc.documentId}_${firstPage.index}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedDocuments]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(showLoader("Loading ..."));
|
dispatch(showLoader("Loading ..."));
|
||||||
|
|
@ -958,6 +1023,10 @@ const SignDocument = () => {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const handleDocumentFocus = (key: string) => {
|
||||||
|
selectedDoc.key = key;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`w-full overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>
|
<div className={`w-full overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>
|
||||||
|
|
@ -1110,7 +1179,7 @@ const SignDocument = () => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/* Right Side - ENDS */}
|
{/* Right Side - ENDS */}
|
||||||
|
|
@ -1200,7 +1269,10 @@ const SignDocument = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={indx}
|
key={indx}
|
||||||
|
onFocus={() => handleDocumentFocus(key)}
|
||||||
id={doc.documentId + indx}
|
id={doc.documentId + indx}
|
||||||
|
data-page-key={key}
|
||||||
|
ref={(el) => { pageRefs.current[key] = el; }}
|
||||||
onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
|
onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
|
||||||
onDragOver={(e) => handleDragOver(e)}
|
onDragOver={(e) => handleDragOver(e)}
|
||||||
className='drop-page-in shadow-md relative overflow-hidden'
|
className='drop-page-in shadow-md relative overflow-hidden'
|
||||||
|
|
@ -1383,10 +1455,24 @@ const SignDocument = () => {
|
||||||
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
|
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
|
||||||
|
|
||||||
<DocumentPages
|
<DocumentPages
|
||||||
|
isPages={isPages}
|
||||||
|
setIsPages={setIsPages}
|
||||||
|
|
||||||
|
droppedItems={droppedItems}
|
||||||
|
|
||||||
selectedDocuments={selectedDocuments}
|
selectedDocuments={selectedDocuments}
|
||||||
setDropDownTogglePage={setDropDownTogglePage}
|
setDropDownTogglePage={setDropDownTogglePage}
|
||||||
dropDownTogglePage={dropDownTogglePage}
|
dropDownTogglePage={dropDownTogglePage}
|
||||||
droppedItems={droppedItems}
|
|
||||||
|
activePage={activePage}
|
||||||
|
onPageSelect={(key: string) => { // NEW — replaces hash-based nav
|
||||||
|
isProgrammaticScrollRef.current = true;
|
||||||
|
setActivePage(key);
|
||||||
|
pageRefs.current[key]?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
// Release the guard after the smooth scroll has had time to settle,
|
||||||
|
// so the IntersectionObserver resumes driving activePage afterward.
|
||||||
|
window.setTimeout(() => { isProgrammaticScrollRef.current = false; }, 600);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ const DocumentPages: React.FC<{
|
||||||
|
|
||||||
const thumbnailRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // NEW
|
const thumbnailRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); // NEW
|
||||||
|
|
||||||
useEffect(() => { // NEW
|
useEffect(() => {
|
||||||
if (!activePage) return;
|
if (!activePage) return;
|
||||||
const el = thumbnailRefs.current[activePage];
|
const el = thumbnailRefs.current[activePage];
|
||||||
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
@ -101,9 +101,6 @@ const DocumentPages: React.FC<{
|
||||||
// // }, [droppedItems, selectedRecipientIndx])
|
// // }, [droppedItems, selectedRecipientIndx])
|
||||||
// }, [droppedItems])
|
// }, [droppedItems])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue