diff --git a/res/icons/more-horizontal.png b/res/icons/more-horizontal.png new file mode 100644 index 00000000..c8839ecb Binary files /dev/null and b/res/icons/more-horizontal.png differ diff --git a/res/icons/more-horizontal@2x.png b/res/icons/more-horizontal@2x.png new file mode 100644 index 00000000..5f8b65dd Binary files /dev/null and b/res/icons/more-horizontal@2x.png differ diff --git a/src/common/components/sidebar/thumbnails-view.js b/src/common/components/sidebar/thumbnails-view.js index 7173eeb1..74a4bd6c 100644 --- a/src/common/components/sidebar/thumbnails-view.js +++ b/src/common/components/sidebar/thumbnails-view.js @@ -1,10 +1,10 @@ -import React, { Fragment, useState, useCallback, useContext, useEffect, useRef, useImperativeHandle } from 'react'; -import { useIntl } from 'react-intl'; +import React, { Fragment, useState, useCallback, useContext, useEffect, useRef, useImperativeHandle, memo } from 'react'; +import { useIntl, FormattedMessage } from 'react-intl'; import cx from 'classnames'; import { pressedNextKey, pressedPreviousKey } from '../../lib/utilities'; import { ReaderContext } from '../../reader'; -function Thumbnail({ thumbnail, selected, pageLabel, onContextMenu }) { +const Thumbnail = memo(({ thumbnail, selected, pageLabel, onContextMenu }) => { return (
{thumbnail.image - ? - :
+ ? + :
}
{pageLabel}
); -} +}); + +Thumbnail.displayName = 'Thumbnail'; + + function ThumbnailsView(props) { const intl = useIntl(); const [selected, setSelected] = useState([0]); const containerRef = useRef(); + const { onOpenThumbnailContextMenu } = props; const { platform } = useContext(ReaderContext); useEffect(() => { @@ -42,7 +47,7 @@ function ThumbnailsView(props) { useEffect (() => { let options = { - root: containerRef.current.parentNode, + root: containerRef.current, rootMargin: "200px", threshold: 1.0 }; @@ -172,39 +177,63 @@ function ThumbnailsView(props) { } } - function handleContextMenu(event) { + const handleContextMenu = useCallback((event) => { if (platform === 'web') { return; } event.preventDefault(); - props.onOpenThumbnailContextMenu({ + onOpenThumbnailContextMenu({ x: event.clientX, y: event.clientY, pageIndexes: selected }); - } + }, [onOpenThumbnailContextMenu, platform, selected]); + + const handleMoreClick = useCallback((event) => { + event.preventDefault(); + const { x, bottom: y } = event.target.getBoundingClientRect(); + onOpenThumbnailContextMenu({ + x, y, + pageIndexes: selected, + }); + }, [onOpenThumbnailContextMenu, selected]); return ( -
- {props.thumbnails.map((thumbnail, index) => { - let pageLabel = props.pageLabels[index] || (index + 1).toString(); - return ( - + {platform === 'web' && ( +
+ +
+ )} +
+ {props.thumbnails.map((thumbnail, index) => { + let pageLabel = props.pageLabels[index] || (index + 1).toString(); + return ( + + ); + })} +
); } diff --git a/src/common/stylesheets/components/_thumbnails-view.scss b/src/common/stylesheets/components/_thumbnails-view.scss index 6867351f..1ba7fc2e 100644 --- a/src/common/stylesheets/components/_thumbnails-view.scss +++ b/src/common/stylesheets/components/_thumbnails-view.scss @@ -1,13 +1,34 @@ .thumbnails-view { - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 10px; - padding: 10px 30px 0; - cursor: default; + width: 100%; + height: 100%; user-select: none; - outline: none; + display: flex; + flex-direction: column; + + .thumbnails-header { + display: flex; + position: relative; + margin: $thumbnail-header-padding; + justify-content: space-between; + align-items: center; + + .toolbarButton { + @include icon("more-horizontal", 12px); + } + } + + .thumbnails { + cursor: default; + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + outline: none; + overflow: auto; + padding: 10px 30px 0; + user-select: none; + } .thumbnail { .image { @@ -26,6 +47,26 @@ } } + @if $platform =="web" { + position: relative; + .more { + @include icon("more", 12px); + position: absolute; + top: 12px; + right: 12px; + display: none; + cursor: pointer; + + @include state(".thumbnail:hover") { + display: block; + } + + &.active { + display: block; + } + } + } + .label { display: flex; justify-content: center; diff --git a/src/common/stylesheets/themes/_light-darwin.scss b/src/common/stylesheets/themes/_light-darwin.scss index b461e6ad..f38a58d2 100644 --- a/src/common/stylesheets/themes/_light-darwin.scss +++ b/src/common/stylesheets/themes/_light-darwin.scss @@ -258,6 +258,7 @@ $attach-icon-toggled: "darwin/attach-linear-white"; $thumbnail-selection-ring-focus-bg: rgba(0, 0, 0, 0.2); $thumbnail-selection-ring-selected-bg: rgba(0, 0, 0, 0.1); $thumbnail-image-border-color: transparent; +$thumbnail-header-padding: 6px 13px; // Outline & attachment view $sidebar-item-link-btn-color: $text-color; diff --git a/src/en-us.strings.js b/src/en-us.strings.js index d27eab76..5113f65d 100644 --- a/src/en-us.strings.js +++ b/src/en-us.strings.js @@ -145,5 +145,7 @@ export default { 'pdfReader.enterPassword': 'Enter the password to open this PDF file.', 'pdfReader.includeAnnotations': 'Include annotations', 'pdfReader.preparingDocumentForPrinting': 'Preparing document for printing…', - 'pdfReader.phraseNotFound': 'Phrase not found' + 'pdfReader.phraseNotFound': 'Phrase not found', + 'pdfReader.selectedPages': '{count, plural, one {# page} other {# pages}} selected', + 'pdfReader.pageOptions': 'Page Options' };