refactor: hooks

This commit is contained in:
infeng 2019-08-29 14:38:35 +08:00
parent 266d59bf2b
commit 09ecbc2be6
11 changed files with 1552 additions and 812 deletions

View File

@ -20,8 +20,8 @@ interface State {
class App extends React.Component<any, Partial<State>> {
container: HTMLDivElement;
constructor() {
super();
constructor(props) {
super(props);
this.state = {
visible: false,
@ -121,12 +121,14 @@ class App extends React.Component<any, Partial<State>> {
);
})}
</div>
<div className={inlineContainerClass} ref={ref => {this.container = ref;}}></div>
<div className={inlineContainerClass} ref={ref => {this.container = ref; }}></div>
</Col>
</Row>
<Viewer
visible={this.state.visible}
onClose={() => { this.setState({ visible: false }); } }
onClose={() => {
this.setState({ visible: false });
}}
images={images}
activeIndex={this.state.activeIndex}
container={inline ? this.container : null}
@ -154,5 +156,5 @@ class App extends React.Component<any, Partial<State>> {
ReactDOM.render(
<App />,
document.getElementById('root')
document.getElementById('root'),
);

59
package-lock.json generated
View File

@ -123,16 +123,26 @@
"integrity": "sha512-vToa8YEeulfyYg1gSOeHjvvIRqrokng62VMSj2hoZrwZNcYrp2h3AWo6KeBVuymIklQUaY5zgVJvVsC4KiiLkQ==",
"dev": true
},
"@types/react": {
"version": "0.14.57",
"resolved": "https://registry.npmjs.org/@types/react/-/react-0.14.57.tgz",
"integrity": "sha1-GHioZU+v3R04G4RXKStkM0mMW2I=",
"@types/prop-types": {
"version": "15.7.1",
"resolved": "https://registry.npm.taobao.org/@types/prop-types/download/@types/prop-types-15.7.1.tgz",
"integrity": "sha1-8aEee6uww8rWgQC+OB0eBkxo8fY=",
"dev": true
},
"@types/react": {
"version": "16.9.1",
"resolved": "https://registry.npm.taobao.org/@types/react/download/@types/react-16.9.1.tgz?cache=0&sync_timestamp=1565360509473&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Freact%2Fdownload%2F%40types%2Freact-16.9.1.tgz",
"integrity": "sha1-hiyDtMnVzRFuQv2aTzaUhDzSwFE=",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-dom": {
"version": "0.14.23",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-0.14.23.tgz",
"integrity": "sha1-zs/PrXVLTCdl/l0puBswGImtbC4=",
"version": "16.8.5",
"resolved": "https://registry.npm.taobao.org/@types/react-dom/download/@types/react-dom-16.8.5.tgz",
"integrity": "sha1-Pj9NmRmTkaf7QKo6FVyN2ZuJnL0=",
"dev": true,
"requires": {
"@types/react": "*"
@ -2840,6 +2850,12 @@
"cssom": "0.3.x"
}
},
"csstype": {
"version": "2.6.6",
"resolved": "https://registry.npm.taobao.org/csstype/download/csstype-2.6.6.tgz",
"integrity": "sha1-w0+CJqlLuxDDLMDXFK/flCKR/EE=",
"dev": true
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@ -11119,27 +11135,26 @@
}
},
"react": {
"version": "16.4.2",
"resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz",
"integrity": "sha512-dMv7YrbxO4y2aqnvA7f/ik9ibeLSHQJTI6TrYAenPSaQ6OXfb+Oti+oJiy8WBxgRzlKatYqtCjphTgDSCEiWFg==",
"version": "16.9.0",
"resolved": "https://registry.npm.taobao.org/react/download/react-16.9.0.tgz?cache=0&sync_timestamp=1565317868688&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact%2Fdownload%2Freact-16.9.0.tgz",
"integrity": "sha1-QLovmvE7waONddvy9DWaUYXE96o=",
"dev": true,
"requires": {
"fbjs": "^0.8.16",
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.0"
"prop-types": "^15.6.2"
}
},
"react-dom": {
"version": "16.4.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.2.tgz",
"integrity": "sha512-Usl73nQqzvmJN+89r97zmeUpQDKDlh58eX6Hbs/ERdDHzeBzWy+ENk7fsGQ+5KxArV1iOFPT46/VneklK9zoWw==",
"version": "16.9.0",
"resolved": "https://registry.npm.taobao.org/react-dom/download/react-dom-16.9.0.tgz",
"integrity": "sha1-XmVSel4m8irjcBExvMyu6fsNOWI=",
"dev": true,
"requires": {
"fbjs": "^0.8.16",
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.0"
"prop-types": "^15.6.2",
"scheduler": "^0.15.0"
}
},
"react-is": {
@ -12143,6 +12158,16 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
},
"scheduler": {
"version": "0.15.0",
"resolved": "https://registry.npm.taobao.org/scheduler/download/scheduler-0.15.0.tgz?cache=0&sync_timestamp=1565317903688&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fscheduler%2Fdownload%2Fscheduler-0.15.0.tgz",
"integrity": "sha1-a/z4D/hQsoD+1K7sxlE7wLTxf44=",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",

View File

@ -34,8 +34,8 @@
"devDependencies": {
"@types/jest": "^23.3.1",
"@types/node": "^6.0.45",
"@types/react": "^0.14.39",
"@types/react-dom": "^0.14.17",
"@types/react": "^16.9.1",
"@types/react-dom": "^16.8.5",
"antd": "^3.16.2",
"atool-build": "^1.0.8",
"babel-jest": "^23.4.2",
@ -61,8 +61,8 @@
"jest-static-stubs": "0.0.1",
"merge2": "^1.0.2",
"pre-commit": "^1.1.3",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-test-render": "^1.1.1",
"through2": "^2.0.1",
"ts-jest": "^23.1.3",

View File

@ -4,18 +4,12 @@ export interface LoadingProps {
style?: React.CSSProperties;
}
export default class Loading extends React.Component<LoadingProps, any> {
constructor() {
super();
}
render() {
let cls = 'circle-loading';
return (
<div className="loading-wrap" style={this.props.style}>
<div className={cls}>
</div>
export default function Loading(props: LoadingProps) {
let cls = 'circle-loading';
return (
<div className="loading-wrap" style={props.style}>
<div className={cls}>
</div>
);
}
</div>
);
}

96
src/Viewer copy.tsx Normal file
View File

@ -0,0 +1,96 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ViewerCore from './ViewerCore';
import ViewerProps from './ViewerProps';
export default class Viewer extends React.Component<ViewerProps, any> {
private defaultContainer: HTMLElement;
private container: HTMLElement;
private component: React.ReactNode;
constructor(props) {
super(props);
this.container = null;
this.defaultContainer = null;
if (typeof document !== 'undefined') {
this.setDefaultContainer();
}
this.component = null;
}
setDefaultContainer() {
this.defaultContainer = document.createElement('div');
}
renderViewer() {
if (this.props.visible || this.component) {
if (!this.container) {
if (this.props.container) {
this.container = this.props.container;
} else {
if (!this.defaultContainer) {
this.setDefaultContainer();
}
this.container = this.defaultContainer;
document.body.appendChild(this.container);
}
}
let instance = this;
ReactDOM.unstable_renderSubtreeIntoContainer(
this,
<ViewerCore
{...this.props}
/>,
this.container,
function () {
instance.component = this;
},
);
}
}
removeViewer() {
if (this.container) {
const container = this.container;
ReactDOM.unmountComponentAtNode(container);
container.parentNode.removeChild(container);
this.container = null;
this.component = null;
}
}
componentWillUnmount() {
if (this.props.visible && this.props.onClose) {
this.props.onClose();
}
this.removeViewer();
}
componentWillReceiveProps(nextProps: ViewerProps) {
if (this.props.container !== nextProps.container) {
this.component = null;
if (nextProps.container) {
if (this.container) {
document.body.removeChild(this.container);
}
this.container = nextProps.container;
} else {
this.container = this.defaultContainer;
document.body.appendChild(this.container);
}
}
}
componentDidMount() {
this.renderViewer();
}
componentDidUpdate() {
this.renderViewer();
}
render() {
return null;
}
}

View File

@ -3,94 +3,35 @@ import * as ReactDOM from 'react-dom';
import ViewerCore from './ViewerCore';
import ViewerProps from './ViewerProps';
export default class Viewer extends React.Component<ViewerProps, any> {
private defaultContainer: HTMLElement;
private container: HTMLElement;
private component: React.ReactNode;
export default React.forwardRef((props: ViewerProps, ref) => {
const defaultContainer = React.useRef(document.createElement('div'));
const [ container, setContainer ] = React.useState(props.container);
const [ init, setInit ] = React.useState(false);
constructor() {
super();
React.useEffect(() => {
document.body.appendChild(defaultContainer.current);
}, []);
this.container = null;
this.defaultContainer = null;
if (typeof document !== 'undefined') {
this.setDefaultContainer();
React.useEffect(() => {
if (props.visible && !init) {
setInit(true);
}
this.component = null;
}
}, [props.visible, init]);
setDefaultContainer() {
this.defaultContainer = document.createElement('div');
}
renderViewer() {
if (this.props.visible || this.component) {
if (!this.container) {
if (this.props.container) {
this.container = this.props.container;
} else {
if (!this.defaultContainer) {
this.setDefaultContainer();
}
this.container = this.defaultContainer;
document.body.appendChild(this.container);
}
}
let instance = this;
ReactDOM.unstable_renderSubtreeIntoContainer(
this,
<ViewerCore
{...this.props}
/>,
this.container,
function () {
instance.component = this;
},
);
React.useEffect(() => {
if (props.container) {
setContainer(props.container);
} else {
setContainer(defaultContainer.current);
}
}
}, [props.container]);
removeViewer() {
if (this.container) {
const container = this.container;
ReactDOM.unmountComponentAtNode(container);
container.parentNode.removeChild(container);
this.container = null;
this.component = null;
}
}
componentWillUnmount() {
if (this.props.visible && this.props.onClose) {
this.props.onClose();
}
this.removeViewer();
}
componentWillReceiveProps(nextProps: ViewerProps) {
if (this.props.container !== nextProps.container) {
this.component = null;
if (nextProps.container) {
if (this.container) {
document.body.removeChild(this.container);
}
this.container = nextProps.container;
} else {
this.container = this.defaultContainer;
document.body.appendChild(this.container);
}
}
}
componentDidMount() {
this.renderViewer();
}
componentDidUpdate() {
this.renderViewer();
}
render() {
if (!init) {
return null;
}
}
return ReactDOM.createPortal((
<ViewerCore
{...props}
/>
), container);
});

View File

@ -28,154 +28,154 @@ export interface ViewerCanvasState {
mouseY?: number;
}
export default class ViewerCanvas extends React.Component<ViewerCanvasProps, ViewerCanvasState> {
export default function ViewerCanvas(props: ViewerCanvasProps) {
const isMouseDown = React.useRef(false);
const prePosition = React.useRef({
x: 0,
y: 0,
});
const [ position, setPosition ] = React.useState({
x: 0,
y: 0,
});
constructor() {
super();
this.state = {
isMouseDown: false,
mouseX: 0,
mouseY: 0,
React.useEffect(() => {
return () => {
bindEvent(true);
bindWindowResizeEvent(true);
};
}
}, []);
componentDidMount() {
if (this.props.drag) {
this.bindEvent();
React.useEffect(() => {
bindWindowResizeEvent(!props.visible);
}, [props.visible]);
React.useEffect(() => {
if (props.visible && props.drag) {
bindEvent();
}
if (!props.visible && props.drag) {
handleMouseUp({});
}
return () => {
bindEvent(true);
};
}, [props.drag, props.visible]);
React.useEffect(() => {
let diffX = position.x - prePosition.current.x;
let diffY = position.y - prePosition.current.y;
prePosition.current = {
x: position.x,
y: position.y,
};
props.onChangeImgState(props.width, props.height, props.top + diffY, props.left + diffX);
}, [position]);
function handleResize(e) {
props.onResize();
}
handleResize = (e) => {
this.props.onResize();
function handleCanvasMouseDown(e) {
props.onCanvasMouseDown(e);
handleMouseDown(e);
}
handleCanvasMouseDown = (e) => {
this.props.onCanvasMouseDown(e);
this.handleMouseDown(e);
}
handleMouseDown = (e) => {
function handleMouseDown(e) {
if (e.button !== 0) {
return;
}
if (!this.props.visible || !this.props.drag) {
if (!props.visible || !props.drag) {
return;
}
e.preventDefault();
e.stopPropagation();
this.setState({
isMouseDown: true,
mouseX: e.nativeEvent.clientX,
mouseY: e.nativeEvent.clientY,
});
isMouseDown.current = true;
prePosition.current = {
x: e.nativeEvent.clientX,
y: e.nativeEvent.clientY,
};
}
handleMouseMove = (e) => {
if (this.state.isMouseDown) {
let diffX = e.clientX - this.state.mouseX;
let diffY = e.clientY - this.state.mouseY;
this.setState({
mouseX: e.clientX,
mouseY: e.clientY,
const handleMouseMove = (e) => {
if (isMouseDown.current) {
setPosition({
x: e.clientX,
y: e.clientY,
});
this.props.onChangeImgState(this.props.width, this.props.height, this.props.top + diffY, this.props.left + diffX);
}
};
function handleMouseUp(e) {
isMouseDown.current = false;
}
handleMouseUp = (e) => {
this.setState({
isMouseDown: false,
});
function bindWindowResizeEvent(remove?: boolean) {
let funcName = 'addEventListener';
if (remove) {
funcName = 'removeEventListener';
}
window[funcName]('resize', handleResize, false);
}
bindEvent = (remove?: boolean) => {
function bindEvent(remove?: boolean) {
let funcName = 'addEventListener';
if (remove) {
funcName = 'removeEventListener';
}
document[funcName]('click', this.handleMouseUp, false);
document[funcName]('mousemove', this.handleMouseMove, false);
window[funcName]('resize', this.handleResize, false);
document[funcName]('click', handleMouseUp, false);
document[funcName]('mousemove', handleMouseMove, false);
}
componentWillReceiveProps(nextProps: ViewerCanvasProps) {
if (!this.props.visible && nextProps.visible) {
if (nextProps.drag) {
return this.bindEvent();
}
}
if (this.props.visible && !nextProps.visible) {
this.handleMouseUp({});
if (nextProps.drag) {
return this.bindEvent(true);
}
}
if (this.props.drag && !nextProps.drag) {
return this.bindEvent(true);
}
if (!this.props.drag && nextProps.drag) {
if (nextProps.visible) {
return this.bindEvent(true);
}
}
let imgStyle: React.CSSProperties = {
width: `${props.width}px`,
height: `${props.height}px`,
transform: `
translateX(${props.left !== null ? props.left + 'px' : 'aoto'}) translateY(${props.top}px)
rotate(${props.rotate}deg) scaleX(${props.scaleX}) scaleY(${props.scaleY})`,
};
const imgClass = classnames(`${props.prefixCls}-image`, {
drag: props.drag,
[`${props.prefixCls}-image-transition`]: !isMouseDown.current,
});
let style = {
zIndex: props.zIndex,
};
let imgNode = null;
if (props.imgSrc !== '') {
imgNode = <img
className={imgClass}
src={props.imgSrc}
style={imgStyle}
onMouseDown={handleMouseDown}
/>;
}
componentWillUnmount() {
this.bindEvent(true);
}
render() {
let imgStyle: React.CSSProperties = {
width: `${this.props.width}px`,
height: `${this.props.height}px`,
transform: `
translateX(${this.props.left !== null ? this.props.left + 'px' : 'aoto'}) translateY(${this.props.top}px)
rotate(${this.props.rotate}deg) scaleX(${this.props.scaleX}) scaleY(${this.props.scaleY})`,
};
const imgClass = classnames(`${this.props.prefixCls}-image`, {
drag: this.props.drag,
[`${this.props.prefixCls}-image-transition`]: !this.state.isMouseDown,
});
let style = {
zIndex: this.props.zIndex,
};
let imgNode = null;
if (this.props.imgSrc !== '') {
imgNode = <img
className={imgClass}
src={this.props.imgSrc}
style={imgStyle}
onMouseDown={this.handleMouseDown}
/>;
}
if (this.props.loading) {
imgNode = (
<div
style={{
display: 'flex',
height: `${window.innerHeight - 84}px`,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Loading/>
</div>
);
}
return (
if (props.loading) {
imgNode = (
<div
className={`${this.props.prefixCls}-canvas`}
onMouseDown={this.handleCanvasMouseDown}
style={style}
style={{
display: 'flex',
height: `${window.innerHeight - 84}px`,
justifyContent: 'center',
alignItems: 'center',
}}
>
{imgNode}
<Loading/>
</div>
);
}
return (
<div
className={`${props.prefixCls}-canvas`}
onMouseDown={handleCanvasMouseDown}
style={style}
>
{imgNode}
</div>
);
}

679
src/ViewerCore copy.tsx Normal file
View File

@ -0,0 +1,679 @@
import * as React from 'react';
import './style/index.less';
import ViewerCanvas from './ViewerCanvas';
import ViewerNav from './ViewerNav';
import ViewerToolbar, { defaultToolbars } from './ViewerToolbar';
import ViewerProps, { ImageDecorator, ToolbarConfig } from './ViewerProps';
import Icon, { ActionType } from './Icon';
import * as constants from './constants';
function noop() { }
const transitionDuration = 300;
export interface ViewerCoreState {
visible?: boolean;
visibleStart?: boolean;
transitionEnd?: boolean;
activeIndex?: number;
width?: number;
height?: number;
top?: number;
left?: number;
rotate?: number;
imageWidth?: number;
imageHeight?: number;
scaleX?: number;
scaleY?: number;
loading?: boolean;
loadFailed?: boolean;
}
export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreState> {
static defaultProps: Partial<ViewerProps> = {
visible: false,
onClose: noop,
images: [],
activeIndex: 0,
zIndex: 1000,
drag: true,
attribute: true,
zoomable: true,
rotatable: true,
scalable: true,
onMaskClick: noop,
changeable: true,
customToolbar: (toolbars) => toolbars,
zoomSpeed: .05,
disableKeyboardSupport: false,
noResetZoomAfterChange: false,
noLimitInitializationSize: false,
defaultScale: 1,
loop: true,
disableMouseZoom: false,
};
private prefixCls: string;
private containerWidth: number;
private containerHeight: number;
private footerHeight: number;
constructor(props) {
super(props);
this.prefixCls = 'react-viewer';
this.state = {
visible: false,
visibleStart: false,
transitionEnd: false,
activeIndex: this.props.activeIndex,
width: 0,
height: 0,
top: 15,
left: null,
rotate: 0,
imageWidth: 0,
imageHeight: 0,
scaleX: this.props.defaultScale,
scaleY: this.props.defaultScale,
loading: false,
loadFailed: false,
};
this.setContainerWidthHeight();
this.footerHeight = constants.FOOTER_HEIGHT;
}
setContainerWidthHeight() {
this.containerWidth = window.innerWidth;
this.containerHeight = window.innerHeight;
if (this.props.container) {
this.containerWidth = this.props.container.offsetWidth;
this.containerHeight = this.props.container.offsetHeight;
this.setInlineContainerHeight();
}
}
setInlineContainerHeight() {
const core = (this.refs['viewerCore'] as HTMLDivElement);
if (core) {
this.containerHeight = core.offsetHeight;
}
}
handleClose = () => {
this.props.onClose();
}
startVisible(activeIndex: number) {
if (!this.props.container) {
document.body.style.overflow = 'hidden';
if (document.body.scrollHeight > document.body.clientHeight) {
document.body.style.paddingRight = '15px';
}
}
this.setState({
visibleStart: true,
});
setTimeout(() => {
this.setState({
visible: true,
activeIndex,
});
setTimeout(() => {
this.bindEvent();
this.loadImg(activeIndex);
}, 300);
}, 10);
}
componentDidMount() {
const core = (this.refs['viewerCore'] as HTMLDivElement);
core.addEventListener(
'transitionend',
this.handleTransitionEnd,
false,
);
// Though onWheel can be directly used on the div "viewerCore", to be able to
// prevent default action, a listener is added here instead
(this.refs['viewerCore'] as HTMLDivElement).addEventListener(
'wheel',
this.handleMouseScroll,
false,
);
if (this.containerHeight === 0) {
this.setInlineContainerHeight();
}
this.startVisible(this.state.activeIndex);
}
getImgWidthHeight(imgWidth, imgHeight) {
let width = 0;
let height = 0;
let maxWidth = this.containerWidth * 0.8;
let maxHeight = (this.containerHeight - this.footerHeight) * 0.8;
width = Math.min(maxWidth, imgWidth);
height = width / imgWidth * imgHeight;
if (height > maxHeight) {
height = maxHeight;
width = height / imgHeight * imgWidth;
}
if (this.props.noLimitInitializationSize) {
width = imgWidth;
height = imgHeight;
}
return [width, height];
}
loadImgSuccess = (activeImage: ImageDecorator, imgWidth, imgHeight, isNewImage: boolean) => {
let realImgWidth = imgWidth;
let realImgHeight = imgHeight;
if (this.props.defaultSize) {
realImgWidth = this.props.defaultSize.width;
realImgHeight = this.props.defaultSize.height;
}
if (activeImage.defaultSize) {
realImgWidth = activeImage.defaultSize.width;
realImgHeight = activeImage.defaultSize.height;
}
let [width, height] = this.getImgWidthHeight(realImgWidth, realImgHeight);
let left = (this.containerWidth - width) / 2;
let top = (this.containerHeight - height - this.footerHeight) / 2;
let scaleX = this.props.defaultScale;
let scaleY = this.props.defaultScale;
if (this.props.noResetZoomAfterChange && isNewImage) {
scaleX = this.state.scaleX;
scaleY = this.state.scaleY;
}
this.setState({
width: width,
height: height,
left: left,
top: top,
imageWidth: imgWidth,
imageHeight: imgHeight,
loading: false,
rotate: 0,
scaleX: scaleX,
scaleY: scaleY,
});
}
loadImg(activeIndex, isNewImage: boolean = false) {
let activeImage: ImageDecorator = null;
let images = this.props.images || [];
if (images.length > 0) {
activeImage = images[activeIndex];
}
let loadComplete = false;
let img = new Image();
this.setState({
activeIndex: activeIndex,
loading: true,
loadFailed: false,
}, () => {
img.onload = () => {
if (!loadComplete) {
this.loadImgSuccess(activeImage, img.width, img.height, isNewImage);
}
};
img.onerror = () => {
if (this.props.defaultImg) {
this.setState({
loadFailed: true,
});
const deafultImgWidth = this.props.defaultImg.width || this.containerWidth * .5;
const defaultImgHeight = this.props.defaultImg.height || this.containerHeight * .5;
this.loadImgSuccess(activeImage, deafultImgWidth, defaultImgHeight, isNewImage);
} else {
this.setState({
activeIndex: activeIndex,
imageWidth: 0,
imageHeight: 0,
loading: false,
});
}
};
img.src = activeImage.src;
if (img.complete) {
loadComplete = true;
this.loadImgSuccess(activeImage, img.width, img.height, isNewImage);
}
});
}
handleChangeImg = (newIndex: number) => {
if (!this.props.loop && (newIndex >= this.props.images.length || newIndex < 0)) {
return;
}
if (newIndex >= this.props.images.length) {
newIndex = 0;
}
if (newIndex < 0) {
newIndex = this.props.images.length - 1;
}
if (newIndex === this.state.activeIndex) {
return;
}
if (this.props.onChange) {
const activeImage = this.getActiveImage(newIndex);
this.props.onChange(activeImage, newIndex);
}
this.loadImg(newIndex, true);
}
handleChangeImgState = (width, height, top, left) => {
this.setState({
width: width,
height: height,
top: top,
left: left,
});
}
handleDefaultAction = (type: ActionType) => {
switch (type) {
case ActionType.prev:
this.handleChangeImg(this.state.activeIndex - 1);
break;
case ActionType.next:
this.handleChangeImg(this.state.activeIndex + 1);
break;
case ActionType.zoomIn:
let imgCenterXY = this.getImageCenterXY();
this.handleZoom(imgCenterXY.x, imgCenterXY.y, 1, this.props.zoomSpeed);
break;
case ActionType.zoomOut:
let imgCenterXY2 = this.getImageCenterXY();
this.handleZoom(imgCenterXY2.x, imgCenterXY2.y, -1, this.props.zoomSpeed);
break;
case ActionType.rotateLeft:
this.handleRotate();
break;
case ActionType.rotateRight:
this.handleRotate(true);
break;
case ActionType.reset:
this.loadImg(this.state.activeIndex);
break;
case ActionType.scaleX:
this.handleScaleX(-1);
break;
case ActionType.scaleY:
this.handleScaleY(-1);
break;
case ActionType.download:
this.handleDownload();
break;
default:
break;
}
}
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) => {
this.setState({
scaleY: this.state.scaleY * newScale,
});
}
handleScrollZoom = (targetX, targetY, direct) => {
this.handleZoom(targetX, targetY, direct, this.props.zoomSpeed);
}
handleZoom = (targetX, targetY, direct, scale) => {
let imgCenterXY = this.getImageCenterXY();
let diffX = targetX - imgCenterXY.x;
let diffY = targetY - imgCenterXY.y;
let top = 0;
let left = 0;
let width = 0;
let height = 0;
let scaleX = 0;
let scaleY = 0;
if (this.state.width === 0) {
const [imgWidth, imgHeight] = this.getImgWidthHeight(
this.state.imageWidth,
this.state.imageHeight,
);
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 {
let directX = this.state.scaleX > 0 ? 1 : -1;
let directY = this.state.scaleY > 0 ? 1 : -1;
scaleX = this.state.scaleX + scale * direct * directX;
scaleY = this.state.scaleY + scale * direct * directY;
if (Math.abs(scaleX) < 0.1 || Math.abs(scaleY) < 0.1) {
return;
}
top = this.state.top + -direct * diffY / this.state.scaleX * scale * directX;
left = this.state.left + -direct * diffX / this.state.scaleY * scale * directY;
width = this.state.width;
height = this.state.height;
}
this.setState({
width: width,
scaleX: scaleX,
scaleY: scaleY,
height: height,
top: top,
left: left,
loading: false,
});
}
getImageCenterXY = () => {
return {
x: this.state.left + this.state.width / 2,
y: this.state.top + this.state.height / 2,
};
}
handleRotate = (isRight: boolean = false) => {
this.setState({
rotate: this.state.rotate + 90 * (isRight ? 1 : -1),
});
}
handleResize = () => {
this.setContainerWidthHeight();
if (this.props.visible) {
let left = (this.containerWidth - this.state.width) / 2;
let top = (this.containerHeight - this.state.height - this.footerHeight) / 2;
this.setState({
left: left,
top: top,
});
}
}
handleKeydown = (e) => {
let keyCode = e.keyCode || e.which || e.charCode;
let isFeatrue = false;
switch (keyCode) {
// key: esc
case 27:
this.props.onClose();
isFeatrue = true;
break;
// key: ←
case 37:
if (e.ctrlKey) {
this.handleDefaultAction(ActionType.rotateLeft);
} else {
this.handleDefaultAction(ActionType.prev);
}
isFeatrue = true;
break;
// key: →
case 39:
if (e.ctrlKey) {
this.handleDefaultAction(ActionType.rotateRight);
} else {
this.handleDefaultAction(ActionType.next);
}
isFeatrue = true;
break;
// key: ↑
case 38:
this.handleDefaultAction(ActionType.zoomIn);
isFeatrue = true;
break;
// key: ↓
case 40:
this.handleDefaultAction(ActionType.zoomOut);
isFeatrue = true;
break;
// key: Ctrl + 1
case 49:
if (e.ctrlKey) {
this.loadImg(this.state.activeIndex);
isFeatrue = true;
}
break;
default:
break;
}
if (isFeatrue) {
e.preventDefault();
}
}
handleTransitionEnd = () => {
if (!this.state.transitionEnd || this.state.visibleStart) {
this.setState({
visibleStart: false,
transitionEnd: true,
});
}
}
bindEvent(remove: boolean = false) {
let funcName = 'addEventListener';
if (remove) {
funcName = 'removeEventListener';
}
if (!this.props.disableKeyboardSupport) {
document[funcName]('keydown', this.handleKeydown, false);
}
}
componentWillUnmount() {
this.bindEvent(true);
(this.refs['viewerCore'] as HTMLDivElement).removeEventListener(
'transitionend',
this.handleTransitionEnd,
false,
);
}
componentWillReceiveProps(nextProps: ViewerProps) {
if (!this.props.visible && nextProps.visible) {
this.startVisible(nextProps.activeIndex);
return;
}
if (this.props.visible && !nextProps.visible) {
this.bindEvent(true);
this.handleZoom(
this.containerWidth / 2,
(this.containerHeight - this.footerHeight) / 2,
-1,
(this.state.scaleX > 0 ? 1 : -1) * this.state.scaleX - 0.11,
);
setTimeout(() => {
document.body.style.overflow = '';
document.body.style.paddingRight = '';
this.setState({
visible: false,
transitionEnd: false,
width: 0,
height: 0,
scaleX: this.props.defaultScale,
scaleY: this.props.defaultScale,
rotate: 1,
imageWidth: 0,
imageHeight: 0,
loadFailed: false,
});
}, transitionDuration);
return;
}
if (this.props.activeIndex !== nextProps.activeIndex) {
this.handleChangeImg(nextProps.activeIndex);
return;
}
}
handleCanvasMouseDown = e => {
this.props.onMaskClick(e);
}
getActiveImage = (activeIndex = undefined) => {
let activeImg: ImageDecorator = {
src: '',
alt: '',
downloadUrl: '',
};
let images = this.props.images || [];
let realActiveIndex = null;
if (activeIndex !== undefined) {
realActiveIndex = activeIndex;
} else {
realActiveIndex = this.state.activeIndex;
}
if (images.length > 0 && realActiveIndex >= 0) {
activeImg = images[realActiveIndex];
}
return activeImg;
}
handleMouseScroll = (e) => {
if (this.props.disableMouseZoom) {
return;
}
e.preventDefault();
let direct: 0 | 1 | -1 = 0;
const value = e.deltaY;
if (value === 0) {
direct = 0;
} else {
direct = value > 0 ? -1 : 1;
}
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.handleScrollZoom(x, y, direct);
}
}
render() {
let activeImg: ImageDecorator = {
src: '',
alt: '',
};
let zIndex = 1000;
if (this.props.zIndex) {
zIndex = this.props.zIndex;
}
let viewerStryle: React.CSSProperties = {
opacity: this.state.visible ? 1 : 0,
};
if (!this.state.visible && this.state.transitionEnd) {
viewerStryle.display = 'none';
}
if (!this.state.visible && this.state.visibleStart) {
viewerStryle.display = 'block';
}
if (this.state.visible && this.state.transitionEnd) {
activeImg = this.getActiveImage();
}
let className = `${this.prefixCls} ${this.prefixCls}-transition`;
if (this.props.container) {
className += ` ${this.prefixCls}-inline`;
}
return (
<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={this.state.loadFailed ? this.props.defaultImg.src || activeImg.src : 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}
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}
/>
{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>
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -8,39 +8,35 @@ export interface ViewerNavProps {
onChangeImg: (index: number) => void;
}
export default class ViewerNav extends React.Component<ViewerNavProps, any> {
static defaultProps = {
activeIndex: 0,
};
export default function ViewerNav(props: ViewerNavProps) {
const { activeIndex = 0 } = props;
handleChangeImg = (newIndex) => {
if (this.props.activeIndex === newIndex) {
function handleChangeImg(newIndex) {
if (activeIndex === newIndex) {
return;
}
this.props.onChangeImg(newIndex);
props.onChangeImg(newIndex);
}
render() {
let marginLeft = `calc(50% - ${this.props.activeIndex + 1} * 31px)`;
let listStyle = {
marginLeft: marginLeft,
};
let marginLeft = `calc(50% - ${activeIndex + 1} * 31px)`;
let listStyle = {
marginLeft: marginLeft,
};
return (
<div className={`${this.props.prefixCls}-navbar`}>
<ul className={`${this.props.prefixCls}-list ${this.props.prefixCls}-list-transition`} style={listStyle}>
{this.props.images.map((item, index) =>
<li
key={index}
className={index === this.props.activeIndex ? 'active' : ''}
onClick={() => { this.handleChangeImg(index); }}
>
<img src={item.src} alt={item.alt} />
</li>,
)
}
</ul>
</div>
);
}
return (
<div className={`${props.prefixCls}-navbar`}>
<ul className={`${props.prefixCls}-list ${props.prefixCls}-list-transition`} style={listStyle}>
{props.images.map((item, index) =>
<li
key={index}
className={index === activeIndex ? 'active' : ''}
onClick={() => { handleChangeImg(index); }}
>
<img src={item.src} alt={item.alt} />
</li>,
)
}
</ul>
</div>
);
}

View File

@ -67,17 +67,12 @@ function deleteToolbarFromKey(toolbars: ToolbarConfig[], keys: string[]) {
return targetToolbar;
}
export default class ViewerToolbar extends React.Component<ViewerToolbarProps, any> {
constructor() {
super();
export default function ViewerToolbar(props: ViewerToolbarProps) {
function handleAction(config: ToolbarConfig) {
props.onAction(config);
}
handleAction(config: ToolbarConfig) {
this.props.onAction(config);
}
renderAction = (config: ToolbarConfig) => {
function renderAction(config: ToolbarConfig) {
let content = null;
// default toolbar
if (typeof ActionType[config.actionType] !== 'undefined') {
@ -90,49 +85,46 @@ export default class ViewerToolbar extends React.Component<ViewerToolbarProps, a
return (
<li
key={config.key}
className={`${this.props.prefixCls}-btn`}
onClick={() => {this.handleAction(config); }}
className={`${props.prefixCls}-btn`}
onClick={() => {handleAction(config); }}
data-key={config.key}
>
{content}
</li>
);
}
render() {
let attributeNode = this.props.attribute ? (
<p className={`${this.props.prefixCls}-attribute`}>
{this.props.alt && `${this.props.alt}`}
{this.props.noImgDetails || <span className={`${this.props.prefixCls}-img-details`}>
{`(${this.props.width} x ${this.props.height})`}
</span>}
</p>
) : null;
let toolbars = this.props.toolbars;
if (!this.props.zoomable) {
toolbars = deleteToolbarFromKey(toolbars, ['zoomIn', 'zoomOut']);
}
if (!this.props.changeable) {
toolbars = deleteToolbarFromKey(toolbars, ['prev', 'next']);
}
if (!this.props.rotatable) {
toolbars = deleteToolbarFromKey(toolbars, ['rotateLeft', 'rotateRight']);
}
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`}>
{toolbars.map(item => {
return this.renderAction(item);
})}
</ul>
</div>
);
let attributeNode = props.attribute ? (
<p className={`${props.prefixCls}-attribute`}>
{props.alt && `${props.alt}`}
{props.noImgDetails || <span className={`${props.prefixCls}-img-details`}>
{`(${props.width} x ${props.height})`}
</span>}
</p>
) : null;
let toolbars = props.toolbars;
if (!props.zoomable) {
toolbars = deleteToolbarFromKey(toolbars, ['zoomIn', 'zoomOut']);
}
if (!props.changeable) {
toolbars = deleteToolbarFromKey(toolbars, ['prev', 'next']);
}
if (!props.rotatable) {
toolbars = deleteToolbarFromKey(toolbars, ['rotateLeft', 'rotateRight']);
}
if (!props.scalable) {
toolbars = deleteToolbarFromKey(toolbars, ['scaleX', 'scaleY']);
}
if (!props.downloadable) {
toolbars = deleteToolbarFromKey(toolbars, ['download']);
}
return (
<div>
{attributeNode}
<ul className={`${props.prefixCls}-toolbar`}>
{toolbars.map(item => {
return renderAction(item);
})}
</ul>
</div>
);
}