diff --git a/frontend/src/components/file-content-view/image.js b/frontend/src/components/file-content-view/image.js index ca9ae41026..b772a37902 100644 --- a/frontend/src/components/file-content-view/image.js +++ b/frontend/src/components/file-content-view/image.js @@ -82,7 +82,7 @@ class FileContent extends React.Component { } return ( -
+
{previousImage && ( )} diff --git a/frontend/src/components/file-view/image-zoomer.js b/frontend/src/components/file-view/image-zoomer.js index bbf43e4819..662135658e 100644 --- a/frontend/src/components/file-view/image-zoomer.js +++ b/frontend/src/components/file-view/image-zoomer.js @@ -1,57 +1,167 @@ import React, { useState, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { Button, Input } from 'reactstrap'; -import Icon from '../../components/icon'; +import { Input, Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; +import IconButton from '../../components/icon-button'; +import { gettext } from '../../utils/constants'; -import '../../metadata/components/data-process-setter/gallery-slider-setter/index.css'; - -const SCALE_OPTIONS = [0.25, 0.5, 1, 1.5, 2]; +const SCALE_OPTIONS = [0.15, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4]; const SCALE_MIN = SCALE_OPTIONS[0]; const SCALE_MAX = SCALE_OPTIONS[SCALE_OPTIONS.length - 1]; +const SCALE_OPTIONS_2 = [ + { value: 'page-fit', text: gettext('Page Fit') }, + { value: 'actual-size', text: gettext('Actual Size') } +]; const ImageZoomer = ({ setImageScale }) => { const [curScale, setScale] = useState(1); + const [curScaleText, setScaleText] = useState(`${curScale * 100}%`); // for the text shown in the input + const [selectedScale, setSelectedScale] = useState(1); // for the scale menu + const [isScaleMenuOpen, setScaleMenuOpen] = useState(false); + + const toggleMenu = useCallback(() => { + setScaleMenuOpen(!isScaleMenuOpen); + }, [isScaleMenuOpen, setScaleMenuOpen]); const scaleImage = useCallback((scale) => { setImageScale(scale); }, [setImageScale]); - const changeScale = useCallback((e) => { - const scale = Number(e.target.value); + const zoomInOrOut = useCallback((scale) => { setScale(scale); scaleImage(scale); + setScaleText(`${Math.round(scale * 100)}%`); // handle the result of floating point arithmetic + if (SCALE_OPTIONS.indexOf(scale) == -1) { + setSelectedScale(null); + } else { + setSelectedScale(scale); + } }, [scaleImage]); const zoomIn = useCallback(() => { - const scale = SCALE_OPTIONS[SCALE_OPTIONS.indexOf(curScale) + 1]; - setScale(scale); - scaleImage(scale); - }, [curScale, scaleImage]); + const offset = Math.ceil(curScale) * 0.1; // round up + const normalizedScale = Number((curScale + offset).toFixed(2)); // handle the result of floating point arithmetic + const scale = Math.min(normalizedScale, SCALE_MAX); + zoomInOrOut(scale); + }, [curScale, zoomInOrOut]); const zoomOut = useCallback(() => { - const scale = SCALE_OPTIONS[SCALE_OPTIONS.indexOf(curScale) - 1]; + const offset = Math.ceil(curScale) * 0.1; // round up + const normalizedScale = Number((curScale - offset).toFixed(2)); // handle the result of floating point arithmetic + const scale = Math.max(normalizedScale, SCALE_MIN); + zoomInOrOut(scale); + }, [curScale, zoomInOrOut]); + + const scaleImageToPageFit = useCallback(() => { + const imageElement = document.getElementById('image-view'); + const borderWidth = 1; + const width = imageElement.clientWidth + borderWidth * 2; + const height = imageElement.clientHeight + borderWidth * 2; + + const imageContainer = imageElement.parentNode; + const hPadding = 0; // horizontal padding + const vPadding = 30; // vertical padding + const maxWidth = imageContainer.clientWidth - hPadding * 2; + const maxHeight = imageContainer.clientHeight - vPadding * 2; + + const hScale = maxWidth / width; + const vScale = maxHeight / height; + const scale = Math.min(hScale, vScale); setScale(scale); scaleImage(scale); - }, [curScale, scaleImage]); + }, [setScale, scaleImage]); + + const onMenuItemClick = useCallback((value) => { + setSelectedScale(value); + if (SCALE_OPTIONS.indexOf(value) != -1) { + const scale = value; + setScale(scale); + scaleImage(scale); + setScaleText(`${scale * 100}%`); + } else { + if (value == 'actual-size') { + const scale = 1; + setScale(scale); + scaleImage(scale); + } else { + // 'page-fit' + scaleImageToPageFit(); + } + setScaleText(SCALE_OPTIONS_2.filter(item => item.value == value)[0].text); + } + setScaleMenuOpen(false); + }, [scaleImage, setScaleMenuOpen, scaleImageToPageFit]); + + const onMenuItemKeyDown = useCallback((e, value) => { + if (e.key == 'Enter' || e.key == 'Space') { + onMenuItemClick(value); + } + }, [onMenuItemClick]); return ( -
- - + + + + + + + + {SCALE_OPTIONS.map((item, index) => { + return ( + {onMenuItemClick(item);}} + onKeyDown={(e) => {onMenuItemKeyDown(e, item);}} + > + {selectedScale == item && ( + + )} + {`${item * 100}%`} + + ); + })} + {SCALE_OPTIONS_2.map((item, index) => { + return ( + {onMenuItemClick(item.value);}} + onKeyDown={(e) => {onMenuItemKeyDown(e, item.value);}} + > + {selectedScale == item.value && ( + + )} + {item.text} + + ); + })} + + + -
); }; diff --git a/frontend/src/css/image-file-view.css b/frontend/src/css/image-file-view.css index 69784fb177..b735a0db2c 100644 --- a/frontend/src/css/image-file-view.css +++ b/frontend/src/css/image-file-view.css @@ -18,8 +18,6 @@ border: 1px solid #eee; width: auto; height: auto; - max-width: calc(100% - 4px); - max-height: calc(100% - 4px); font-size: 0; line-height: 0; } @@ -51,6 +49,32 @@ color: var(--bs-body-color); } -.image-zoomer { - height: 24px; +.image-zoomer #zoom-out-image { + margin-right: 2px; + font-size: 18px; /* the actual icon is smaller than the others */ +} + +.image-zoomer #zoom-in-image { + margin-left: 2px; + font-size: 18px; /* the actual icon is smaller than the others */ +} + +.image-zoomer #cur-scale-input { + height: 28px; + width: 120px; + user-select: none; +} + +.image-zoomer #scale-menu { + min-width: 120px; /* overwrite css from seahub_react.css */ + width: 120px; +} + +.image-zoomer #scale-menu-caret { + position: absolute; + right: 12px; + top: 9px; + font-size: 9px; + line-height: 1; + color: #666; }