mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-01 19:32:42 +00:00
Prior to fix, the "next page" button in the toolbar wouldn't work well if there were multiple pages in view that were in a single 'row'. This is because the logic is to take the pages that are "visible", take the max of those pages, and then scroll to that page. But the issue is that if the 'max' page is in the same row as other pages, the range of visible pages doesn't change....the max will always be the same. So the change here basically runs the scroll function twice-- if the first run results in the same 'max' page as before the scroll, it runs it again but with the target page being "max + 1", which will bump the target to the next row.
267 lines
9.5 KiB
JavaScript
267 lines
9.5 KiB
JavaScript
/* eslint-disable max-lines */
|
|
require('./toolBar.less');
|
|
const React = require('react');
|
|
const { useState, useEffect } = React;
|
|
const _ = require('lodash');
|
|
|
|
import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anchored.jsx';
|
|
|
|
const MAX_ZOOM = 300;
|
|
const MIN_ZOOM = 10;
|
|
|
|
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages })=>{
|
|
|
|
const [pageNum, setPageNum] = useState(1);
|
|
const [toolsVisible, setToolsVisible] = useState(true);
|
|
|
|
useEffect(()=>{
|
|
if(visiblePages.length !== 0){ // If zoomed in enough, it's possible that no page fits the intersection criteria, so don't update.
|
|
setPageNum(formatVisiblePages(visiblePages));
|
|
}
|
|
}, [visiblePages]);
|
|
|
|
const handleZoomButton = (zoom)=>{
|
|
handleOptionChange('zoomLevel', _.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
|
|
};
|
|
|
|
const handleOptionChange = (optionKey, newValue)=>{
|
|
//setDisplayOptions(prevOptions => ({ ...prevOptions, [optionKey]: newValue }));
|
|
onDisplayOptionsChange({ ...displayOptions, [optionKey]: newValue });
|
|
};
|
|
|
|
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.
|
|
};
|
|
|
|
// scroll to a page, used in the Prev/Next Page buttons.
|
|
const scrollToPage = (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' });
|
|
};
|
|
|
|
const calculateChange = (mode)=>{
|
|
const iframe = document.getElementById('BrewRenderer');
|
|
const iframeWidth = iframe.getBoundingClientRect().width;
|
|
const iframeHeight = iframe.getBoundingClientRect().height;
|
|
const pages = iframe.contentWindow.document.getElementsByClassName('page');
|
|
|
|
let desiredZoom = 0;
|
|
|
|
if(mode == 'fill'){
|
|
// find widest page, in case pages are different widths, so that the zoom is adapted to not cut the widest page off screen.
|
|
const widestPage = _.maxBy([...pages], 'offsetWidth').offsetWidth;
|
|
|
|
desiredZoom = (iframeWidth / widestPage) * 100;
|
|
|
|
} else if(mode == 'fit'){
|
|
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
|
|
const minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
|
|
|
|
desiredZoom = minDimRatio * 100;
|
|
}
|
|
|
|
const margin = 5; // extra space so page isn't edge to edge (not truly "to fill")
|
|
|
|
const deltaZoom = (desiredZoom - displayOptions.zoomLevel) - margin;
|
|
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 '';
|
|
|
|
const sortedPages = [...pages].sort((a, b)=>a - b); // Copy and sort the array
|
|
const 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(', ');
|
|
};
|
|
|
|
return (
|
|
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
|
|
<button className='toggleButton' title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
|
|
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
|
|
<div className='group' role='group' aria-label='Zoom' aria-hidden={!toolsVisible}>
|
|
<button
|
|
id='fill-width'
|
|
className='tool'
|
|
title='Set zoom to fill preview with one page'
|
|
onClick={()=>handleZoomButton(displayOptions.zoomLevel + calculateChange('fill'))}
|
|
>
|
|
<i className='fac fit-width' />
|
|
</button>
|
|
<button
|
|
id='zoom-to-fit'
|
|
className='tool'
|
|
title='Set zoom to fit entire page in preview'
|
|
onClick={()=>handleZoomButton(displayOptions.zoomLevel + calculateChange('fit'))}
|
|
>
|
|
<i className='fac zoom-to-fit' />
|
|
</button>
|
|
<button
|
|
id='zoom-out'
|
|
className='tool'
|
|
onClick={()=>handleZoomButton(displayOptions.zoomLevel - 20)}
|
|
disabled={displayOptions.zoomLevel <= MIN_ZOOM}
|
|
title='Zoom Out'
|
|
>
|
|
<i className='fas fa-magnifying-glass-minus' />
|
|
</button>
|
|
<input
|
|
id='zoom-slider'
|
|
className='range-input tool hover-tooltip'
|
|
type='range'
|
|
name='zoom'
|
|
title='Set Zoom'
|
|
list='zoomLevels'
|
|
min={MIN_ZOOM}
|
|
max={MAX_ZOOM}
|
|
step='1'
|
|
value={displayOptions.zoomLevel}
|
|
onChange={(e)=>handleZoomButton(parseInt(e.target.value))}
|
|
/>
|
|
<datalist id='zoomLevels'>
|
|
<option value='100' />
|
|
</datalist>
|
|
|
|
<button
|
|
id='zoom-in'
|
|
className='tool'
|
|
onClick={()=>handleZoomButton(displayOptions.zoomLevel + 20)}
|
|
disabled={displayOptions.zoomLevel >= MAX_ZOOM}
|
|
title='Zoom In'
|
|
>
|
|
<i className='fas fa-magnifying-glass-plus' />
|
|
</button>
|
|
</div>
|
|
|
|
{/*v=====----------------------< Spread Controls >---------------------=====v*/}
|
|
<div className='group' role='group' aria-label='Spread' aria-hidden={!toolsVisible}>
|
|
<div className='radio-group' role='radiogroup'>
|
|
<button role='radio'
|
|
id='single-spread'
|
|
className='tool'
|
|
title='Single Page'
|
|
onClick={()=>{handleOptionChange('spread', 'active');}}
|
|
aria-checked={displayOptions.spread === 'single'}
|
|
><i className='fac single-spread' /></button>
|
|
<button role='radio'
|
|
id='facing-spread'
|
|
className='tool'
|
|
title='Facing Pages'
|
|
onClick={()=>{handleOptionChange('spread', 'facing');}}
|
|
aria-checked={displayOptions.spread === 'facing'}
|
|
><i className='fac facing-spread' /></button>
|
|
<button role='radio'
|
|
id='flow-spread'
|
|
className='tool'
|
|
title='Flow Pages'
|
|
onClick={()=>{handleOptionChange('spread', 'flow');}}
|
|
aria-checked={displayOptions.spread === 'flow'}
|
|
><i className='fac flow-spread' /></button>
|
|
|
|
</div>
|
|
<Anchored>
|
|
<AnchoredTrigger id='spread-settings' className='tool' title='Spread options'><i className='fas fa-gear' /></AnchoredTrigger>
|
|
<AnchoredBox title='Options'>
|
|
<h1>Options</h1>
|
|
<label title='Modify the horizontal space between pages.'>
|
|
Column gap
|
|
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('columnGap', evt.target.value)} />
|
|
</label>
|
|
<label title='Modify the vertical space between rows of pages.'>
|
|
Row gap
|
|
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('rowGap', evt.target.value)} />
|
|
</label>
|
|
<label title='Start 1st page on the right side, such as if you have cover page.'>
|
|
Start on right
|
|
<input type='checkbox' checked={displayOptions.startOnRight} onChange={()=>{handleOptionChange('startOnRight', !displayOptions.startOnRight);}}
|
|
title={displayOptions.spread !== 'facing' ? 'Switch to Facing to enable toggle.' : null} />
|
|
</label>
|
|
<label title='Toggle the page shadow on every page.'>
|
|
Page shadows
|
|
<input type='checkbox' checked={displayOptions.pageShadows} onChange={()=>{handleOptionChange('pageShadows', !displayOptions.pageShadows);}} />
|
|
</label>
|
|
</AnchoredBox>
|
|
</Anchored>
|
|
</div>
|
|
|
|
{/*v=====----------------------< Page Controls >---------------------=====v*/}
|
|
<div className='group' role='group' aria-label='Pages' aria-hidden={!toolsVisible}>
|
|
<button
|
|
id='previous-page'
|
|
className='previousPage tool'
|
|
type='button'
|
|
title='Previous Page(s)'
|
|
onClick={()=>{
|
|
const rangeOffset = visiblePages.length > 1 ? 1 : 0;
|
|
scrollToPage(_.min(visiblePages) - visiblePages.length + rangeOffset);
|
|
}}
|
|
disabled={pageNum <= 1}
|
|
>
|
|
<i className='fas fa-arrow-left'></i>
|
|
</button>
|
|
|
|
<div className='tool'>
|
|
<input
|
|
id='page-input'
|
|
className='text-input'
|
|
type='text'
|
|
name='page'
|
|
title='Current page(s) in view'
|
|
inputMode='numeric'
|
|
pattern='[0-9]'
|
|
value={`${pageNum}`}
|
|
onClick={(e)=>e.target.select()}
|
|
onChange={(e)=>handlePageInput(e.target.value)}
|
|
onBlur={()=>scrollToPage(pageNum)}
|
|
onKeyDown={(e)=>e.key == 'Enter' && scrollToPage(pageNum)}
|
|
/>
|
|
<span id='page-count' title='Total Page Count'>/ {totalPages}</span>
|
|
</div>
|
|
|
|
<button
|
|
id='next-page'
|
|
className='tool'
|
|
type='button'
|
|
title='Next Page(s)'
|
|
onClick={()=>{
|
|
// if there are multiple pages in a 'row' and they are in 'view',
|
|
// then the 'max'/last page in view will always be the same, and
|
|
// the other pages will always be the same (since the viewport doesn't change).
|
|
// So this needs to scroll to the 'max', then see what is newly in view,
|
|
// and if the same pages are visible, do it again but +1.
|
|
const start = _.max(visiblePages);
|
|
scrollToPage(start);
|
|
if(start === _.max(visiblePages)){
|
|
scrollToPage(start + 1)
|
|
};
|
|
}}
|
|
disabled={pageNum >= totalPages}
|
|
>
|
|
<i className='fas fa-arrow-right'></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
module.exports = ToolBar;
|