mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-26 07:22:34 +00:00
[image file view] add a new version of 'image zoomer'(zoom in/out icons, and a scale menu) (#8059)
This commit is contained in:
@@ -82,7 +82,7 @@ class FileContent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="file-view-content flex-1 image-file-view">
|
<div className="file-view-content flex-1 image-file-view d-flex align-items-center justify-content-center">
|
||||||
{previousImage && (
|
{previousImage && (
|
||||||
<a href={previousImageUrl} id="img-prev" title={gettext('you can also press ← ')}><span className="sf3-font sf3-font-down rotate-90 d-inline-block"></span></a>
|
<a href={previousImageUrl} id="img-prev" title={gettext('you can also press ← ')}><span className="sf3-font sf3-font-down rotate-90 d-inline-block"></span></a>
|
||||||
)}
|
)}
|
||||||
|
@@ -1,57 +1,167 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Input } from 'reactstrap';
|
import { Input, Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||||
import Icon from '../../components/icon';
|
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.15, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4];
|
||||||
|
|
||||||
const SCALE_OPTIONS = [0.25, 0.5, 1, 1.5, 2];
|
|
||||||
const SCALE_MIN = SCALE_OPTIONS[0];
|
const SCALE_MIN = SCALE_OPTIONS[0];
|
||||||
const SCALE_MAX = SCALE_OPTIONS[SCALE_OPTIONS.length - 1];
|
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 ImageZoomer = ({ setImageScale }) => {
|
||||||
|
|
||||||
const [curScale, setScale] = useState(1);
|
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) => {
|
const scaleImage = useCallback((scale) => {
|
||||||
setImageScale(scale);
|
setImageScale(scale);
|
||||||
}, [setImageScale]);
|
}, [setImageScale]);
|
||||||
|
|
||||||
const changeScale = useCallback((e) => {
|
const zoomInOrOut = useCallback((scale) => {
|
||||||
const scale = Number(e.target.value);
|
|
||||||
setScale(scale);
|
setScale(scale);
|
||||||
scaleImage(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]);
|
}, [scaleImage]);
|
||||||
|
|
||||||
const zoomIn = useCallback(() => {
|
const zoomIn = useCallback(() => {
|
||||||
const scale = SCALE_OPTIONS[SCALE_OPTIONS.indexOf(curScale) + 1];
|
const offset = Math.ceil(curScale) * 0.1; // round up
|
||||||
setScale(scale);
|
const normalizedScale = Number((curScale + offset).toFixed(2)); // handle the result of floating point arithmetic
|
||||||
scaleImage(scale);
|
const scale = Math.min(normalizedScale, SCALE_MAX);
|
||||||
}, [curScale, scaleImage]);
|
zoomInOrOut(scale);
|
||||||
|
}, [curScale, zoomInOrOut]);
|
||||||
|
|
||||||
const zoomOut = useCallback(() => {
|
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);
|
setScale(scale);
|
||||||
scaleImage(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 (
|
return (
|
||||||
<div className='metadata-slider-container image-zoomer ml-0'>
|
<div className='d-flex align-items-center image-zoomer'>
|
||||||
<Button className="metadata-slider-icon-button" onClick={zoomOut} disabled={curScale == SCALE_MIN}>
|
<IconButton
|
||||||
<Icon symbol='minus_sign' className='metadata-slider-icon' />
|
id="zoom-out-image"
|
||||||
</Button>
|
icon="minus_sign"
|
||||||
<Input
|
text={gettext('Zoom out')}
|
||||||
type="range"
|
onClick={zoomOut}
|
||||||
min={SCALE_MIN}
|
disabled={curScale == SCALE_MIN}
|
||||||
max={SCALE_MAX}
|
/>
|
||||||
step="any"
|
<Dropdown
|
||||||
value={curScale}
|
isOpen={isScaleMenuOpen}
|
||||||
onChange={changeScale}
|
toggle={toggleMenu}
|
||||||
className="metadata-slider"
|
className="vam"
|
||||||
|
direction='down'
|
||||||
|
>
|
||||||
|
<DropdownToggle
|
||||||
|
tag='div'
|
||||||
|
className="position-relative"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={isScaleMenuOpen}
|
||||||
|
>
|
||||||
|
<Input id="cur-scale-input" type="text" value={curScaleText} readOnly={true} />
|
||||||
|
<i id="scale-menu-caret" className="sf3-font-down sf3-font"></i>
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu id="scale-menu">
|
||||||
|
{SCALE_OPTIONS.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<DropdownItem
|
||||||
|
key={index}
|
||||||
|
className="position-relative pl-5"
|
||||||
|
onClick={() => {onMenuItemClick(item);}}
|
||||||
|
onKeyDown={(e) => {onMenuItemKeyDown(e, item);}}
|
||||||
|
>
|
||||||
|
{selectedScale == item && (
|
||||||
|
<i className="dropdown-item-tick sf2-icon-tick"></i>
|
||||||
|
)}
|
||||||
|
<span>{`${item * 100}%`}</span>
|
||||||
|
</DropdownItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{SCALE_OPTIONS_2.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<DropdownItem
|
||||||
|
key={index}
|
||||||
|
className="position-relative pl-5"
|
||||||
|
onClick={() => {onMenuItemClick(item.value);}}
|
||||||
|
onKeyDown={(e) => {onMenuItemKeyDown(e, item.value);}}
|
||||||
|
>
|
||||||
|
{selectedScale == item.value && (
|
||||||
|
<i className="dropdown-item-tick sf2-icon-tick"></i>
|
||||||
|
)}
|
||||||
|
<span>{item.text}</span>
|
||||||
|
</DropdownItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
<IconButton
|
||||||
|
id="zoom-in-image"
|
||||||
|
icon="plus_sign"
|
||||||
|
text={gettext('Zoom in')}
|
||||||
|
onClick={zoomIn}
|
||||||
|
disabled={curScale == SCALE_MAX}
|
||||||
/>
|
/>
|
||||||
<Button className="metadata-slider-icon-button" onClick={zoomIn} disabled={curScale == SCALE_MAX}>
|
|
||||||
<Icon symbol='plus_sign' className='metadata-slider-icon' />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -18,8 +18,6 @@
|
|||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-width: calc(100% - 4px);
|
|
||||||
max-height: calc(100% - 4px);
|
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
@@ -51,6 +49,32 @@
|
|||||||
color: var(--bs-body-color);
|
color: var(--bs-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-zoomer {
|
.image-zoomer #zoom-out-image {
|
||||||
height: 24px;
|
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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user