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'
};