From 41fdf48ad357ab499986c498292c255525026253 Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Mon, 21 Oct 2024 00:30:45 -0500 Subject: [PATCH 1/9] Setup Intersection Observers & more... Bad commit here with too much stuff. I apologize. This sets up two Intersection Observers: the first captures every page that is at least 30% visible inside the `.pages` container, and the second captures every page that has at least one pixel on the horizontal center line of `.pages`. Both can be arrays of integers (page index). The "visiblePages" array is duplicated and formatted into a "formattedPages" state, which gets displayed in the toolbar. The toolbar displays that, unless the user clicks into the page input and enters their own integer (only a single integer, no range), which can then jump the preview to that page on Enter or blur(). The Arrow 'change page' buttons jump the preview back and forth by a 'full set'. If one page is viewed at a time, this is moved on page a time, and if 10 pages are viewed at a time it jumps the pages by 10. Left to do: adapt the "jump editor to match preview" divider button to work with new "centerPage". --- client/homebrew/brewRenderer/brewRenderer.jsx | 133 ++++++++++++++---- .../homebrew/brewRenderer/toolBar/toolBar.jsx | 24 +++- .../brewRenderer/toolBar/toolBar.less | 2 +- 3 files changed, 126 insertions(+), 33 deletions(-) diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 48f155820..1b30c0ae5 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -1,7 +1,7 @@ /*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ require('./brewRenderer.less'); const React = require('react'); -const { useState, useRef, useCallback } = React; +const { useState, useRef, useCallback, useEffect } = React; const _ = require('lodash'); const MarkdownLegacy = require('naturalcrit/markdownLegacy.js'); @@ -30,14 +30,47 @@ const INITIAL_CONTENT = dedent`
`; //v=====----------------------< Brew Page Component >---------------------=====v// -const BrewPage = (props)=>{ - props = { - contents : '', - index : 0, - ...props - }; - const cleanText = props.contents; //DOMPurify.sanitize(props.contents, purifyConfig); - return
+const BrewPage = ({contents = '', index = 0, onVisibilityChange, onCenterPageChange, ...props})=>{ + const pageRef = useRef(null); + const cleanText = contents; //DOMPurify.sanitize(props.contents, purifyConfig); + + useEffect(()=>{ + if(!pageRef.current) return; + const observer = new IntersectionObserver( + (entries)=>{ + entries.forEach((entry)=>{ + if(entry.isIntersecting){ + onVisibilityChange(index + 1, true); + } else { + onVisibilityChange(index + 1, false); + } + }); + }, + { threshold: .3, rootMargin: '0px 0px 0px 0px' } + ); + + // Observer for tracking the page at the center of the iframe. + const centerObserver = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + onCenterPageChange(index + 1); // Set this page as the center page + } + }); + }, + { threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center + ); + + observer.observe(pageRef.current); + centerObserver.observe(pageRef.current); + + return ()=>{ + observer.disconnect(); + centerObserver.disconnect(); + }; + }, [index, onVisibilityChange, onCenterPageChange]); + + return
; }; @@ -64,11 +97,14 @@ const BrewRenderer = (props)=>{ }; const [state, setState] = useState({ - isMounted : false, - visibility : 'hidden', - zoom : 100 + isMounted : false, + visibility : 'hidden', + zoom : 100, + visiblePages : [], + formattedPages : '', + centerPage : 1 }); - + const iframeRef = useRef(null); const mainRef = useRef(null); if(props.renderer == 'legacy') { @@ -77,13 +113,54 @@ const BrewRenderer = (props)=>{ rawPages = props.text.split(/^\\page$/gm); } - const updateCurrentPage = useCallback(_.throttle((e)=>{ - const { scrollTop, clientHeight, scrollHeight } = e.target; - const totalScrollableHeight = scrollHeight - clientHeight; - const currentPageNumber = Math.max(Math.ceil((scrollTop / totalScrollableHeight) * rawPages.length), 1); + useEffect(() => { + props.onPageChange(formatVisiblePages(state.visiblePages)); + }, [state.visiblePages]); - props.onPageChange(currentPageNumber); - }, 200), []); + const handlePageVisibilityChange = useCallback((pageNum, isVisible) => { + setState((prevState) => { + let updatedVisiblePages = new Set(prevState.visiblePages); + if(isVisible){ + updatedVisiblePages.add(pageNum) + } else { + updatedVisiblePages.delete(pageNum) + } + const pages = Array.from(updatedVisiblePages); + + return { ...prevState, + visiblePages : _.sortBy(pages), + formattedPages : formatVisiblePages(pages) + }; + }); + }, []); + + const formatVisiblePages = (pages) => { + if (pages.length === 0) return ''; + + const sortedPages = [...pages].sort((a, b) => a - b); // Copy and sort the array + let ranges = []; + let start = sortedPages[0]; + + for (let i = 1; i <= sortedPages.length; i++) { + // If the current page is not consecutive or it's the end of the list + if (i === sortedPages.length || sortedPages[i] !== sortedPages[i - 1] + 1) { + // Push the range to the list + ranges.push( + start === sortedPages[i - 1] ? `${start}` : `${start} - ${sortedPages[i - 1]}` + ); + start = sortedPages[i]; // Start a new range + } + } + + return ranges.join(', '); + }; + + const handleCenterPageChange = useCallback((pageNum) => { + setState((prevState) => ({ + ...prevState, + centerPage : pageNum, + })); + }, []); const isInView = (index)=>{ if(!state.isMounted) @@ -113,11 +190,11 @@ const BrewRenderer = (props)=>{ const renderPage = (pageText, index)=>{ if(props.renderer == 'legacy') { const html = MarkdownLegacy.render(pageText); - return ; + return ; } else { pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear) const html = Markdown.render(pageText, index); - return ; + return ; } }; @@ -183,7 +260,9 @@ const BrewRenderer = (props)=>{ <> {/*render dummy page while iFrame is mounting.*/} {!state.isMounted - ?
+ ?
{renderDummyPage(1)}
@@ -196,7 +275,7 @@ const BrewRenderer = (props)=>{
- + {/*render in iFrame so broken code doesn't crash the site.*/} { onClick={()=>{emitClick();}} >
+ style={ styleObject } + > {/* Apply CSS from Style tab and render pages from Markdown tab */} {state.isMounted && <> {renderStyle()} -
+
{renderPages()}
diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.jsx b/client/homebrew/brewRenderer/toolBar/toolBar.jsx index 73b48d778..f3ee3a11a 100644 --- a/client/homebrew/brewRenderer/toolBar/toolBar.jsx +++ b/client/homebrew/brewRenderer/toolBar/toolBar.jsx @@ -7,12 +7,20 @@ const _ = require('lodash'); const MAX_ZOOM = 300; const MIN_ZOOM = 10; -const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{ +const ToolBar = ({ onZoomChange, currentPage, visiblePages, formattedPages, centerPage, totalPages })=>{ const [zoomLevel, setZoomLevel] = useState(100); - const [pageNum, setPageNum] = useState(currentPage); + const [pageNum, setPageNum] = useState(null); const [toolsVisible, setToolsVisible] = useState(true); + useEffect(()=>{ + setPageNum(visiblePages[0]); + }, []); + + useEffect(()=>{ + setPageNum(formattedPages); + }, [visiblePages]); + useEffect(()=>{ onZoomChange(zoomLevel); }, [zoomLevel]); @@ -26,17 +34,21 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{ }; const handlePageInput = (pageInput)=>{ + console.log(pageInput); if(/[0-9]/.test(pageInput)) setPageNum(parseInt(pageInput)); // input type is 'text', so `page` comes in as a string, not number. }; const scrollToPage = (pageNumber)=>{ + console.log('visiblePages:', visiblePages); + console.log('centerPage:', centerPage); + console.log('pageNumber:', pageNumber); + if(typeof pageNumber !== 'number') return; pageNumber = _.clamp(pageNumber, 1, totalPages); const iframe = document.getElementById('BrewRenderer'); const brewRenderer = iframe?.contentWindow?.document.querySelector('.brewRenderer'); const page = brewRenderer?.querySelector(`#p${pageNumber}`); page?.scrollIntoView({ block: 'start' }); - setPageNum(pageNumber); }; @@ -125,7 +137,7 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{ From 26050e21342b2371c748cf1c58dbff3843b3dbe7 Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Mon, 21 Oct 2024 22:20:52 -0500 Subject: [PATCH 7/9] add comment --- client/homebrew/brewRenderer/toolBar/toolBar.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.jsx b/client/homebrew/brewRenderer/toolBar/toolBar.jsx index 61183a083..1fcdceecb 100644 --- a/client/homebrew/brewRenderer/toolBar/toolBar.jsx +++ b/client/homebrew/brewRenderer/toolBar/toolBar.jsx @@ -71,6 +71,7 @@ const ToolBar = ({ onZoomChange, visiblePages, totalPages })=>{ return deltaZoom; }; + // format the visible pages to work with ranges, including separate ranges ("2-7, 10-15") const formatVisiblePages = (pages)=>{ if(pages.length === 0) return ''; From 4126188df12b5c0d76fe8108927567c3f1309ee9 Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Mon, 21 Oct 2024 22:29:58 -0500 Subject: [PATCH 8/9] linting --- client/homebrew/brewRenderer/brewRenderer.jsx | 10 +++--- .../brewRenderer/toolBar/toolBar.less | 34 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index c846d4d63..2177e224e 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -99,11 +99,11 @@ const BrewRenderer = (props)=>{ }; const [state, setState] = useState({ - isMounted : false, - visibility : 'hidden', - zoom : 100, - visiblePages : [], - centerPage : 1 + isMounted : false, + visibility : 'hidden', + zoom : 100, + visiblePages : [], + centerPage : 1 }); const iframeRef = useRef(null); const mainRef = useRef(null); diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.less b/client/homebrew/brewRenderer/toolBar/toolBar.less index 4cc125aad..86ea769f6 100644 --- a/client/homebrew/brewRenderer/toolBar/toolBar.less +++ b/client/homebrew/brewRenderer/toolBar/toolBar.less @@ -16,8 +16,8 @@ color : #CCCCCC; background-color : #555555; & > *:not(.toggleButton) { - opacity: 1; - transition: all .2s ease; + opacity : 1; + transition : all 0.2s ease; } .group { @@ -100,29 +100,27 @@ color : #777777; background-color : unset !important; } - i { - font-size:1.2em; - } + i { font-size : 1.2em; } } &.hidden { - width: 32px; - transition: all .3s ease; - flex-wrap:nowrap; - overflow: hidden; - background-color: unset; - opacity: .5; + flex-wrap : nowrap; + width : 32px; + overflow : hidden; + background-color : unset; + opacity : 0.5; + transition : all 0.3s ease; & > *:not(.toggleButton) { - opacity: 0; - transition: all .2s ease; + opacity : 0; + transition : all 0.2s ease; } } } button.toggleButton { - z-index : 5; - position:absolute; - left: 0; - width: 32px; - min-width: unset; + position : absolute; + left : 0; + z-index : 5; + width : 32px; + min-width : unset; } \ No newline at end of file From 5ab867f21efd2ecd85e70b0ceabc97ade360c7c8 Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Tue, 22 Oct 2024 22:36:13 -0500 Subject: [PATCH 9/9] adjust prev/next page buttons to meet expectations i hope --- client/homebrew/brewRenderer/toolBar/toolBar.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.jsx b/client/homebrew/brewRenderer/toolBar/toolBar.jsx index 1fcdceecb..291b33352 100644 --- a/client/homebrew/brewRenderer/toolBar/toolBar.jsx +++ b/client/homebrew/brewRenderer/toolBar/toolBar.jsx @@ -43,7 +43,6 @@ const ToolBar = ({ onZoomChange, visiblePages, totalPages })=>{ page?.scrollIntoView({ block: 'start' }); }; - const calculateChange = (mode)=>{ const iframe = document.getElementById('BrewRenderer'); const iframeWidth = iframe.getBoundingClientRect().width; @@ -151,7 +150,10 @@ const ToolBar = ({ onZoomChange, visiblePages, totalPages })=>{