Merge branch 'master' into master

This commit is contained in:
feng zhi hao 2018-05-23 09:52:43 +08:00 committed by GitHub
commit 4c58cabe54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 6602 additions and 225 deletions

View File

@ -46,20 +46,44 @@ class App extends React.Component<any, any> {
## Props
| props | type | default | description | required |
|--------------|--------------|---------|-----------------------------|----------|
| visible | string | false | Viewer visible | true |
| onClose | function | - | Specify a function that will be called when Visible close | true |
| images | [ImageDecorator](#imagedecorator)[] | [] | image source array | true |
| activeIndex | number | 0 | active image index | false |
| zIndex | number | 1000 | Viewer css z-index | false |
| container | HTMLElement | null | set parent node(inline mode) | false |
| drag | boolean | true | whether to drag image | false |
| attribute | boolean | true | whether to show image attribute | false |
| zoomable | boolean | true | whether to show 'zoom' button | false |
| rotatable | boolean | true | whether to show 'rotate' button | false |
| scalable | boolean | true | whether to show 'scale' button | false |
| onMaskClick | (e) => void | - | callback function when mask is clicked | false |
| downloadable | boolean | false | whether to show 'download' | false |
| noClose | boolean | false | to not render close button | false |
| noNavbar | boolean | false | to not render the navbar | false |
| noToolbar | boolean | false | to not render the toolbar | false |
| noImgDetails | boolean | false | to not render image detail (WxH) | false |
| noFooter | boolean | false | to not render the entire footer | false |
| changeable | boolean | true | wheather to show change button | false |
| customToolbar | (defaultToolbarConfigs: [ToolbarConfig](#toolbarconfig)[]) => ToolbarConfig[] | - | customer toolbar | false |
### ImageDecorator
| props | type | default | description | required |
|-------------|--------------|---------|-----------------------------|----------|
| visible | string | false | Viewer visible | true |
| onClose | function | - | Specify a function that will be called when Visible close | true |
| images | {src: string, alt: string}[] | [] | image source array | true |
| activeIndex | number | 0 | active image index | false |
| zIndex | number | 1000 | Viewer css z-index | false |
| container | HTMLElement | null | set parent node(inline mode) | false |
| drag | boolean | true | whether to drag image | false |
| attribute | boolean | true | whether to show image attribute | false |
| zoomable | boolean | true | whether to show 'zoom' buttom | false |
| rotatable | boolean | true | whether to show 'rotate' button | false |
| scalable | boolean | true | whether to show 'scale' button | false |
| onMaskClick | (e) => void | - | callback function when mask is clicked |
| src | string | - | image source | true |
| alt | string | - | image description | false |
| downloadUrl | string | - | image downlaod url | false |
### ToolbarConfig
| props | type | default | description | required |
|-------------|--------------|---------|-----------------------------|----------|
| key | string | - | tool key | true |
| render | React.ReactNode | - | tool render | false |
| onClick | function | - | callback function when action is clicked | false |
## Keyboard support

View File

@ -47,15 +47,19 @@ class App extends React.Component<any, Partial<State>> {
let images = [{
src: img,
alt: 'lake',
downloadUrl: '',
}, {
src: img2,
alt: 'mountain',
downloadUrl: '',
}, {
src: img3,
alt: '',
downloadUrl: '',
}, {
src: img4,
alt: '',
downloadUrl: '',
}];
let inline = this.state.mode === 'inline';
@ -125,8 +129,17 @@ class App extends React.Component<any, Partial<State>> {
onClose={() => { this.setState({ visible: false }); } }
images={images}
activeIndex={this.state.activeIndex}
attribute={false}
container={inline ? this.container : null}
downloadable
customToolbar={(toolbars) => {
return toolbars.concat([{
key: 'test',
render: <div>C</div>,
onClick: (activeImage) => {
console.log(activeImage);
},
}]);
}}
/>
</div>
<div className="footer">

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html> <html> <head> <title>react-viewer</title> <meta charset="utf-8"> <link href="/react-viewer/index.css?55d05ece8bc68335253e" rel="stylesheet"></head> <body> <div id="root"></div> <script type="text/javascript" src="/react-viewer/index.js?55d05ece8bc68335253e"></script></body> </html>
<!DOCTYPE html> <html> <head> <title>react-viewer</title> <meta charset="utf-8"> <link href="/react-viewer/index.css?e520ae9314a8c80503cc" rel="stylesheet"></head> <body> <div id="root"></div> <script type="text/javascript" src="/react-viewer/index.js?e520ae9314a8c80503cc"></script></body> </html>

File diff suppressed because one or more lines are too long

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "react-viewer",
"version": "2.3.4",
"version": "2.4.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "react-viewer",
"version": "2.3.4",
"version": "2.4.0",
"description": "react image viewer",
"main": "dist/index",
"scripts": {

View File

@ -11,6 +11,7 @@ export enum ActionType {
close = 8,
scaleX = 9,
scaleY = 10,
download = 11,
}
export interface IconProps {

View File

@ -51,12 +51,10 @@ export default class Viewer extends React.Component<ViewerProps, any> {
}
componentWillUnmount() {
if (this.props.visible) {
if (this.props.visible && this.props.onClose) {
this.props.onClose();
this.removeViewer();
} else {
this.removeViewer();
}
this.removeViewer();
}
componentWillReceiveProps(nextProps: ViewerProps) {

View File

@ -38,12 +38,6 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
mouseX: 0,
mouseY: 0,
};
this.handleMouseScroll = this.handleMouseScroll.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.bindEvent = this.bindEvent.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
}
componentDidMount() {
@ -61,7 +55,7 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
this.handleMouseDown(e);
}
handleMouseDown(e) {
handleMouseDown = (e) => {
if (!this.props.visible || !this.props.drag) {
return;
}
@ -74,7 +68,7 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
});
}
handleMouseMove(e) {
handleMouseMove = (e) => {
if (this.state.isMouseDown) {
let diffX = e.clientX - this.state.mouseX;
let diffY = e.clientY - this.state.mouseY;
@ -86,13 +80,14 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
}
}
handleMouseUp(e) {
handleMouseUp = (e) => {
this.setState({
isMouseDown: false,
});
}
handleMouseScroll(e) {
handleMouseScroll = (e) => {
e.preventDefault();
let direct: 0 | 1 | -1 = 0;
if (e.wheelDelta) {
direct = e.wheelDelta > 0 ? 1 : -1;
@ -102,11 +97,16 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
if (direct !== 0) {
let x = e.clientX;
let y = e.clientY;
if (this.props.container) {
const containerRect = this.props.container.getBoundingClientRect();
x -= containerRect.left;
y -= containerRect.top;
}
this.props.onZoom(x, y, direct, .05);
}
}
bindEvent(remove?: boolean) {
bindEvent = (remove?: boolean) => {
let funcName = 'addEventListener';
if (remove) {
funcName = 'removeEventListener';

View File

@ -2,8 +2,8 @@ import * as React from 'react';
import './style/index.less';
import ViewerCanvas from './ViewerCanvas';
import ViewerNav from './ViewerNav';
import ViewerToolbar from './ViewerToolbar';
import ViewerProps, { ImageDecorator } from './ViewerProps';
import ViewerToolbar, { defaultToolbars } from './ViewerToolbar';
import ViewerProps, { ImageDecorator, ToolbarConfig } from './ViewerProps';
import Icon, { ActionType } from './Icon';
function noop() {}
@ -40,6 +40,8 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
rotatable: true,
scalable: true,
onMaskClick: noop,
changeable: true,
customToolbar: (toolbars) => toolbars,
};
private prefixCls: string;
@ -69,17 +71,6 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
loading: false,
};
this.handleChangeImg = this.handleChangeImg.bind(this);
this.handleChangeImgState = this.handleChangeImgState.bind(this);
this.handleAction = this.handleAction.bind(this);
this.handleResize = this.handleResize.bind(this);
this.handleZoom = this.handleZoom.bind(this);
this.handleRotate = this.handleRotate.bind(this);
this.handleKeydown = this.handleKeydown.bind(this);
this.handleScaleX = this.handleScaleX.bind(this);
this.handleScaleY = this.handleScaleY.bind(this);
this.getImageCenterXY = this.getImageCenterXY.bind(this);
this.setContainerWidthHeight();
this.footerHeight = 84;
}
@ -93,7 +84,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
}
}
handleClose(e) {
handleClose = (e) => {
this.props.onClose();
}
@ -119,20 +110,24 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
}
componentDidMount() {
(this.refs['viewerCore'] as HTMLDivElement).addEventListener('transitionend', this.handleTransitionEnd, false);
(this.refs['viewerCore'] as HTMLDivElement).addEventListener(
'transitionend',
this.handleTransitionEnd,
false
);
this.startVisible(this.state.activeIndex);
}
getImgWidthHeight(imgWidth, imgHeight) {
let width = 0;
let height = 0;
let maxWidth = this.containerWidth * .8;
let maxHeight = (this.containerHeight - this.footerHeight) * .8;
let maxWidth = this.containerWidth * 0.8;
let maxHeight = (this.containerHeight - this.footerHeight) * 0.8;
width = Math.min(maxWidth, imgWidth);
height = (width / imgWidth) * imgHeight;
height = width / imgWidth * imgHeight;
if (height > maxHeight) {
height = maxHeight;
width = (height / imgHeight) * imgWidth;
width = height / imgHeight * imgWidth;
}
return [width, height];
}
@ -151,13 +146,13 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
width: 0,
height: 0,
left: 0,
top: 0,
top: 0,
rotate: 0,
scaleX: 1,
scaleY: 1,
loading: true,
});
}else {
} else {
this.setState({
activeIndex: activeIndex,
loading: true,
@ -176,16 +171,16 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
let imgCenterXY = this.getImageCenterXY();
this.handleZoom(imgCenterXY.x, imgCenterXY.y, 1, 1);
}, 50);
}else {
const [ width, height ] = this.getImgWidthHeight(imgWidth, imgHeight);
let left = ( this.containerWidth - width ) / 2;
} else {
const [width, height] = this.getImgWidthHeight(imgWidth, imgHeight);
let left = (this.containerWidth - width) / 2;
let top = (this.containerHeight - height - this.footerHeight) / 2;
this.setState({
activeIndex: activeIndex,
width: width,
height: height,
left: left,
top: top,
top: top,
imageWidth: imgWidth,
imageHeight: imgHeight,
loading: false,
@ -205,7 +200,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
};
}
handleChangeImg(newIndex: number) {
handleChangeImg = (newIndex: number) => {
// let imgCenterXY2 = this.getImageCenterXY();
// this.handleZoom(imgCenterXY2.x, imgCenterXY2.y, -1, 1);
// setTimeout(() => {
@ -214,7 +209,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
this.loadImg(newIndex);
}
handleChangeImgState(width, height, top, left) {
handleChangeImgState = (width, height, top, left) => {
this.setState({
width: width,
height: height,
@ -223,7 +218,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
});
}
handleAction(type: ActionType) {
handleDefaultAction = (type: ActionType) => {
switch (type) {
case ActionType.prev:
if (this.state.activeIndex - 1 >= 0) {
@ -237,11 +232,11 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
break;
case ActionType.zoomIn:
let imgCenterXY = this.getImageCenterXY();
this.handleZoom(imgCenterXY.x, imgCenterXY.y, 1, .05);
this.handleZoom(imgCenterXY.x, imgCenterXY.y, 1, 0.05);
break;
case ActionType.zoomOut:
let imgCenterXY2 = this.getImageCenterXY();
this.handleZoom(imgCenterXY2.x, imgCenterXY2.y, -1, .05);
this.handleZoom(imgCenterXY2.x, imgCenterXY2.y, -1, 0.05);
break;
case ActionType.rotateLeft:
this.handleRotate();
@ -258,24 +253,43 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
case ActionType.scaleY:
this.handleScaleY(-1);
break;
case ActionType.download:
this.handleDownload();
break;
default:
break;
}
}
handleScaleX(newScale: 1 | -1) {
handleAction = (config: ToolbarConfig) => {
this.handleDefaultAction(config.actionType);
if (config.onClick) {
const activeImage = this.getActiveImage();
config.onClick(activeImage);
}
}
handleDownload = () => {
const activeImage = this.getActiveImage();
if (activeImage.downloadUrl) {
location.href = activeImage.downloadUrl;
}
};
handleScaleX = (newScale: 1 | -1) => {
this.setState({
scaleX: this.state.scaleX * newScale,
});
}
handleScaleY(newScale: 1 | -1) {
handleScaleY = (newScale: 1 | -1) => {
this.setState({
scaleY: this.state.scaleY * newScale,
});
}
handleZoom(targetX, targetY, direct, scale) {
handleZoom = (targetX, targetY, direct, scale) => {
let imgCenterXY = this.getImageCenterXY();
let diffX = targetX - imgCenterXY.x;
let diffY = targetY - imgCenterXY.y;
@ -288,14 +302,17 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
let scaleX = 0;
let scaleY = 0;
if (this.state.width === 0) {
const [ imgWidth, imgHeight ] = this.getImgWidthHeight(this.state.imageWidth, this.state.imageHeight);
const [imgWidth, imgHeight] = this.getImgWidthHeight(
this.state.imageWidth,
this.state.imageHeight
);
reset = true;
left = (this.containerWidth - imgWidth) / 2;
top = (this.containerHeight - this.footerHeight - imgHeight) / 2;
width = this.state.width + imgWidth;
height = this.state.height + imgHeight;
scaleX = scaleY = 1;
}else {
} else {
let directX = this.state.scaleX > 0 ? 1 : -1;
let directY = this.state.scaleY > 0 ? 1 : -1;
scaleX = this.state.scaleX + scale * direct * directX;
@ -319,30 +336,30 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
});
}
getImageCenterXY() {
getImageCenterXY = () => {
return {
x: (this.state.left + this.state.width / 2),
y: (this.state.top + this.state.height / 2),
x: this.state.left + this.state.width / 2,
y: this.state.top + this.state.height / 2,
};
}
handleRotate(isRight: boolean = false) {
handleRotate = (isRight: boolean = false) => {
this.setState({
rotate: this.state.rotate + 90 * (isRight ? 1 : -1),
});
}
handleResize() {
handleResize = () => {
this.setContainerWidthHeight();
if (this.props.visible) {
const [ width, height ] = this.getImgWidthHeight(this.state.imageWidth, this.state.imageHeight);
let left = ( this.containerWidth - width ) / 2;
const [width, height] = this.getImgWidthHeight(this.state.imageWidth, this.state.imageHeight);
let left = (this.containerWidth - width) / 2;
let top = (this.containerHeight - height - this.footerHeight) / 2;
this.setState({
width: width,
height: height,
left: left,
top: top,
top: top,
rotate: 0,
scaleX: 1,
scaleY: 1,
@ -350,7 +367,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
}
}
handleKeydown(e) {
handleKeydown = (e) => {
let keyCode = e.keyCode || e.which || e.charCode;
let isFeatrue = false;
switch (keyCode) {
@ -362,29 +379,29 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
// key: ←
case 37:
if (e.ctrlKey) {
this.handleAction(ActionType.rotateLeft);
}else {
this.handleAction(ActionType.prev);
this.handleDefaultAction(ActionType.rotateLeft);
} else {
this.handleDefaultAction(ActionType.prev);
}
isFeatrue = true;
break;
// key: →
case 39:
if (e.ctrlKey) {
this.handleAction(ActionType.rotateRight);
}else {
this.handleAction(ActionType.next);
this.handleDefaultAction(ActionType.rotateRight);
} else {
this.handleDefaultAction(ActionType.next);
}
isFeatrue = true;
break;
// key: ↑
case 38:
this.handleAction(ActionType.zoomIn);
this.handleDefaultAction(ActionType.zoomIn);
isFeatrue = true;
break;
// key: ↓
case 40:
this.handleAction(ActionType.zoomOut);
this.handleDefaultAction(ActionType.zoomOut);
isFeatrue = true;
break;
// key: Ctrl + 1
@ -402,14 +419,14 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
}
}
handleTransitionEnd = (e) => {
handleTransitionEnd = e => {
if (!this.state.transitionEnd || this.state.visibleStart) {
this.setState({
visibleStart: false,
transitionEnd: true,
});
}
}
};
bindEvent(remove: boolean = false) {
let funcName = 'addEventListener';
@ -421,7 +438,11 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
componentWillUnmount() {
this.bindEvent(true);
(this.refs['viewerCore'] as HTMLDivElement).removeEventListener('transitionend', this.handleTransitionEnd, false);
(this.refs['viewerCore'] as HTMLDivElement).removeEventListener(
'transitionend',
this.handleTransitionEnd,
false
);
}
componentWillReceiveProps(nextProps: ViewerProps) {
@ -435,7 +456,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
this.containerWidth / 2,
(this.containerHeight - this.footerHeight) / 2,
-1,
(this.state.scaleX > 0 ? 1 : -1) * this.state.scaleX - 0.11,
(this.state.scaleX > 0 ? 1 : -1) * this.state.scaleX - 0.11
);
setTimeout(() => {
document.body.style.overflow = '';
@ -457,7 +478,22 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
handleCanvasMouseDown = e => {
this.props.onMaskClick(e);
}
};
getActiveImage = () => {
let activeImg: ImageDecorator = {
src: '',
alt: '',
downloadUrl: '',
};
let images = this.props.images || [];
if (images.length > 0 && this.state.activeIndex >= 0) {
activeImg = images[this.state.activeIndex];
}
return activeImg;
};
render() {
let activeImg: ImageDecorator = {
@ -482,10 +518,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
viewerStryle.display = 'block';
}
if (this.state.visible && this.state.transitionEnd) {
let images = this.props.images || [];
if (images.length > 0 && this.state.activeIndex >= 0) {
activeImg = images[this.state.activeIndex];
}
activeImg = this.getActiveImage();
}
let className = `${this.prefixCls} ${this.prefixCls}-transition`;
@ -494,59 +527,66 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
}
return (
<div
ref="viewerCore"
className={className}
style={viewerStryle}
>
<div className={`${this.prefixCls}-mask`} style={{zIndex: zIndex}}></div>
<div
className={`${this.prefixCls}-close ${this.prefixCls}-btn`}
onClick={this.handleClose.bind(this)}
style={{zIndex: zIndex + 10}}
>
<Icon type={ActionType.close}/>
</div>
<div ref="viewerCore" className={className} style={viewerStryle}>
<div className={`${this.prefixCls}-mask`} style={{ zIndex: zIndex }} />
{this.props.noClose || (
<div
className={`${this.prefixCls}-close ${this.prefixCls}-btn`}
onClick={this.handleClose}
style={{ zIndex: zIndex + 10 }}
>
<Icon type={ActionType.close} />
</div>
)}
<ViewerCanvas
prefixCls={this.prefixCls}
imgSrc={activeImg.src}
visible={this.props.visible}
width={this.state.width}
height={this.state.height}
top={this.state.top}
left={this.state.left}
rotate={this.state.rotate}
onChangeImgState={this.handleChangeImgState}
onResize={this.handleResize}
onZoom={this.handleZoom}
zIndex={zIndex + 5}
scaleX={this.state.scaleX}
scaleY={this.state.scaleY}
loading={this.state.loading}
drag={this.props.drag}
container={this.props.container}
onCanvasMouseDown={this.handleCanvasMouseDown}
prefixCls={this.prefixCls}
imgSrc={activeImg.src}
visible={this.props.visible}
width={this.state.width}
height={this.state.height}
top={this.state.top}
left={this.state.left}
rotate={this.state.rotate}
onChangeImgState={this.handleChangeImgState}
onResize={this.handleResize}
onZoom={this.handleZoom}
zIndex={zIndex + 5}
scaleX={this.state.scaleX}
scaleY={this.state.scaleY}
loading={this.state.loading}
drag={this.props.drag}
container={this.props.container}
onCanvasMouseDown={this.handleCanvasMouseDown}
/>
<div className={`${this.prefixCls}-footer`} style={{zIndex: zIndex + 5}}>
<ViewerToolbar
prefixCls={this.prefixCls}
onAction={this.handleAction}
alt={activeImg.alt}
width={this.state.imageWidth}
height={this.state.imageHeight}
attribute={this.props.attribute}
zoomable={this.props.zoomable}
rotatable={this.props.rotatable}
scalable={this.props.scalable}
changeable={true}
/>
<ViewerNav
prefixCls={this.prefixCls}
images={this.props.images}
activeIndex={this.state.activeIndex}
onChangeImg={this.handleChangeImg}
/>
</div>
{this.props.noFooter || (
<div className={`${this.prefixCls}-footer`} style={{ zIndex: zIndex + 5 }}>
{this.props.noToolbar || (
<ViewerToolbar
prefixCls={this.prefixCls}
onAction={this.handleAction}
alt={activeImg.alt}
width={this.state.imageWidth}
height={this.state.imageHeight}
attribute={this.props.attribute}
zoomable={this.props.zoomable}
rotatable={this.props.rotatable}
scalable={this.props.scalable}
changeable={this.props.changeable}
downloadable={this.props.downloadable}
noImgDetails={this.props.noImgDetails}
toolbars={this.props.customToolbar(defaultToolbars)}
/>
)}
{this.props.noNavbar || (
<ViewerNav
prefixCls={this.prefixCls}
images={this.props.images}
activeIndex={this.state.activeIndex}
onChangeImg={this.handleChangeImg}
/>
)}
</div>
)}
</div>
);
}

View File

@ -13,7 +13,7 @@ export default class ViewerNav extends React.Component<ViewerNavProps, any> {
activeIndex: 0,
};
handleChangeImg(newIndex) {
handleChangeImg = (newIndex) => {
if (this.props.activeIndex === newIndex) {
return;
}
@ -33,7 +33,7 @@ export default class ViewerNav extends React.Component<ViewerNavProps, any> {
<li
key={index}
className={index === this.props.activeIndex ? 'active' : ''}
onClick={this.handleChangeImg.bind(this, index)}
onClick={() => { this.handleChangeImg(index); }}
>
<img src={item.src} alt={item.alt} />
</li>

View File

@ -1,6 +1,14 @@
export interface ImageDecorator {
src: string;
alt?: string;
downloadUrl?: string;
}
export interface ToolbarConfig {
key: string;
actionType?: number;
render?: React.ReactNode;
onClick?: (activeImage: ImageDecorator) => void;
}
interface ViewerProps {
@ -28,6 +36,29 @@ interface ViewerProps {
scalable?: boolean;
/** callback function when mask is clicked */
onMaskClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
/** 是否显示下载按钮 */
downloadable?: boolean;
// no render close button
noClose?: boolean;
// no render image details
noImgDetails?: boolean;
// no render navbar
noNavbar?: boolean;
// no render toolbar
noToolbar?: boolean;
// no render footer
noFooter?: boolean;
// wheather to show change button
changeable?: boolean;
// custom toolbar
customToolbar?: (toolbars: ToolbarConfig[]) => ToolbarConfig[];
}
export default ViewerProps;

View File

@ -1,9 +1,10 @@
import * as React from 'react';
import Icon, { ActionType } from './Icon';
import { ToolbarConfig } from './ViewerProps';
export interface ViewerToolbarProps {
prefixCls: string;
onAction: (type: ActionType) => void;
onAction: (config: ToolbarConfig) => void;
alt: string;
width: number;
height: number;
@ -12,6 +13,58 @@ export interface ViewerToolbarProps {
rotatable: boolean;
scalable: boolean;
changeable: boolean;
downloadable: boolean;
noImgDetails: boolean;
toolbars: ToolbarConfig[];
}
export const defaultToolbars: ToolbarConfig[] = [
{
key: 'zoomIn',
actionType: ActionType.zoomIn,
},
{
key: 'zoomOut',
actionType: ActionType.zoomOut,
},
{
key: 'prev',
actionType: ActionType.prev,
},
{
key: 'reset',
actionType: ActionType.reset,
},
{
key: 'next',
actionType: ActionType.next,
},
{
key: 'rotateLeft',
actionType: ActionType.rotateLeft,
},
{
key: 'rotateRight',
actionType: ActionType.rotateRight,
},
{
key: 'scaleX',
actionType: ActionType.scaleX,
},
{
key: 'scaleY',
actionType: ActionType.scaleY,
},
{
key: 'download',
actionType: ActionType.download,
},
];
function deleteToolbarFromKey(toolbars: ToolbarConfig[], keys: string[]) {
const targetToolbar = toolbars.filter(item => keys.indexOf(item.key) < 0);
return targetToolbar;
}
export default class ViewerToolbar extends React.Component<ViewerToolbarProps, any> {
@ -20,89 +73,61 @@ export default class ViewerToolbar extends React.Component<ViewerToolbarProps, a
super();
}
handleAction(type: ActionType) {
this.props.onAction(type);
handleAction(config: ToolbarConfig) {
this.props.onAction(config);
}
renderAction = (config: ToolbarConfig) => {
let content = null;
// default toolbar
if (typeof ActionType[config.actionType] !== 'undefined') {
content = <Icon type={config.actionType}/>;
}
// extra toolbar
if (config.render) {
content = config.render;
}
return (
<li
key={config.key}
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(config);}}
>
{content}
</li>
);
}
render() {
let attributeNode = this.props.attribute ? (
<p className={`${this.props.prefixCls}-attribute`}>
{`${this.props.alt}(${this.props.width} x ${this.props.height})`}
{this.props.alt && `${this.props.alt}`}
{this.props.noImgDetails || `(${this.props.width} x ${this.props.height})`}
</p>
) : null;
let featureNodeArr = [];
if (this.props.zoomable) {
featureNodeArr = featureNodeArr.concat([
<li
key="zoomIn"
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(ActionType.zoomIn);}}>
<Icon type={ActionType.zoomIn}/>
</li>,
<li
key="zoomOut"
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(ActionType.zoomOut);}}>
<Icon type={ActionType.zoomOut}/>
</li>,
]);
let toolbars = this.props.toolbars;
if (!this.props.zoomable) {
toolbars = deleteToolbarFromKey(toolbars, ['zoomIn', 'zoomOut']);
}
if (this.props.changeable) {
featureNodeArr = featureNodeArr.concat([
<li
key="prev"
className={`${this.props.prefixCls}-btn`} onClick={() => {this.handleAction(ActionType.prev);}}>
<Icon type={ActionType.prev}/>
</li>,
<li
key="reset"
className={`${this.props.prefixCls}-btn`} onClick={() => {this.handleAction(ActionType.reset);}}>
<Icon type={ActionType.reset}/>
</li>,
<li
key="next"
className={`${this.props.prefixCls}-btn`} onClick={() => {this.handleAction(ActionType.next);}}>
<Icon type={ActionType.next}/>
</li>,
]);
if (!this.props.changeable) {
toolbars = deleteToolbarFromKey(toolbars, ['prev', 'next']);
}
if (this.props.rotatable) {
featureNodeArr = featureNodeArr.concat([
<li
key="rotateLeft"
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(ActionType.rotateLeft);}}>
<Icon type={ActionType.rotateLeft}/>
</li>,
<li
key="rotateRight"
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(ActionType.rotateRight);}}>
<Icon type={ActionType.rotateRight}/>
</li>,
]);
if (!this.props.rotatable) {
toolbars = deleteToolbarFromKey(toolbars, ['rotateLeft', 'rotateRight']);
}
if (this.props.scalable) {
featureNodeArr = featureNodeArr.concat([
<li
key="scaleX"
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(ActionType.scaleX);}}>
<Icon type={ActionType.scaleX}/>
</li>,
<li
key="scaleY"
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(ActionType.scaleY);}}>
<Icon type={ActionType.scaleY}/>
</li>,
]);
if (!this.props.scalable) {
toolbars = deleteToolbarFromKey(toolbars, ['scaleX', 'scaleY']);
}
if (!this.props.downloadable) {
toolbars = deleteToolbarFromKey(toolbars, ['download']);
}
return (
<div>
{attributeNode}
<ul className={`${this.props.prefixCls}-toolbar`}>
{featureNodeArr}
{toolbars.map(item => {
return this.renderAction(item);
})}
</ul>
</div>
);

BIN
src/style/fonts/icomoon.eot Normal file → Executable file

Binary file not shown.

1
src/style/fonts/icomoon.svg Normal file → Executable file
View File

@ -10,6 +10,7 @@
<glyph unicode="&#xe967;" glyph-name="rotate-left" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z" />
<glyph unicode="&#xe968;" glyph-name="rotate-right" d="M576 711.628v248.372l384-384-384-384v253.824c-446.75 10.482-427.588-303.792-313.86-509.824-280.712 303.414-221.1 789.57 313.86 775.628z" />
<glyph unicode="&#xe984;" glyph-name="reset" d="M1024 576h-384l143.53 143.53c-72.53 72.526-168.96 112.47-271.53 112.47s-199-39.944-271.53-112.47c-72.526-72.53-112.47-168.96-112.47-271.53s39.944-199 112.47-271.53c72.53-72.526 168.96-112.47 271.53-112.47s199 39.944 271.528 112.472c6.056 6.054 11.86 12.292 17.456 18.668l96.32-84.282c-93.846-107.166-231.664-174.858-385.304-174.858-282.77 0-512 229.23-512 512s229.23 512 512 512c141.386 0 269.368-57.326 362.016-149.984l149.984 149.984v-384z" />
<glyph unicode="&#xe9c7;" glyph-name="download" d="M736 512l-256-256-256 256h160v384h192v-384zM480 256h-480v-256h960v256h-480zM896 128h-128v64h128v-64z" />
<glyph unicode="&#xea0a;" glyph-name="zoom-in" d="M992 576h-352v352c0 17.672-14.328 32-32 32h-192c-17.672 0-32-14.328-32-32v-352h-352c-17.672 0-32-14.328-32-32v-192c0-17.672 14.328-32 32-32h352v-352c0-17.672 14.328-32 32-32h192c17.672 0 32 14.328 32 32v352h352c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32z" />
<glyph unicode="&#xea0b;" glyph-name="zoom-out" d="M0 544v-192c0-17.672 14.328-32 32-32h960c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32h-960c-17.672 0-32-14.328-32-32z" />
<glyph unicode="&#xea0f;" glyph-name="close" d="M1014.662 137.34c-0.004 0.004-0.008 0.008-0.012 0.010l-310.644 310.65 310.644 310.65c0.004 0.004 0.008 0.006 0.012 0.010 3.344 3.346 5.762 7.254 7.312 11.416 4.246 11.376 1.824 24.682-7.324 33.83l-146.746 146.746c-9.148 9.146-22.45 11.566-33.828 7.32-4.16-1.55-8.070-3.968-11.418-7.31 0-0.004-0.004-0.006-0.008-0.010l-310.648-310.652-310.648 310.65c-0.004 0.004-0.006 0.006-0.010 0.010-3.346 3.342-7.254 5.76-11.414 7.31-11.38 4.248-24.682 1.826-33.83-7.32l-146.748-146.748c-9.148-9.148-11.568-22.452-7.322-33.828 1.552-4.16 3.97-8.072 7.312-11.416 0.004-0.002 0.006-0.006 0.010-0.010l310.65-310.648-310.65-310.652c-0.002-0.004-0.006-0.006-0.008-0.010-3.342-3.346-5.76-7.254-7.314-11.414-4.248-11.376-1.826-24.682 7.322-33.83l146.748-146.746c9.15-9.148 22.452-11.568 33.83-7.322 4.16 1.552 8.070 3.97 11.416 7.312 0.002 0.004 0.006 0.006 0.010 0.010l310.648 310.65 310.648-310.65c0.004-0.002 0.008-0.006 0.012-0.008 3.348-3.344 7.254-5.762 11.414-7.314 11.378-4.246 24.684-1.826 33.828 7.322l146.746 146.748c9.148 9.148 11.57 22.454 7.324 33.83-1.552 4.16-3.97 8.068-7.314 11.414z" />

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
src/style/fonts/icomoon.ttf Normal file → Executable file

Binary file not shown.

BIN
src/style/fonts/icomoon.woff Normal file → Executable file

Binary file not shown.

View File

@ -52,6 +52,7 @@
&-btn {
background-color: @btn-background-color;
color: white;
}
&-btn:hover{
@ -132,7 +133,7 @@
border-radius: @toolbarHeight;
margin-right: 3px;
cursor: pointer;
line-height: 29px;
line-height: 28px;
}
&-toolbar li:hover {
@ -254,7 +255,11 @@
&-scaleY:before {
content: '\ea5f';
}
}
&-download:before {
content: '\e9c7';
}
}
.spin {

6239
yarn.lock Normal file

File diff suppressed because it is too large Load Diff