diff --git a/.gitignore b/.gitignore index 40b878d..0f6d69f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules/ \ No newline at end of file +node_modules/ +dist/ +lib/ \ No newline at end of file diff --git a/demo/index.tsx b/demo/index.tsx index 4fc9f36..e2b3954 100644 --- a/demo/index.tsx +++ b/demo/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import ViewerWrap from '../src/ViewerWrap'; +import Viewer from '../src/Viewer'; const img2 = require('./images/landscape2.jpg'); const img = require('./images/landscape.jpg'); const img3 = require('./images/tibet-6.jpg'); @@ -18,7 +18,7 @@ class App extends React.Component { return (
- { this.setState({ visible: false }); } } images={[img2, img, img3]} diff --git a/getBabelCommonConfig.js b/getBabelCommonConfig.js new file mode 100644 index 0000000..4f9bffe --- /dev/null +++ b/getBabelCommonConfig.js @@ -0,0 +1,6 @@ +module.exports = function getBabelCommonConfig() { + const babelConfig = require('atool-build/lib/getBabelCommonConfig')(); + babelConfig.plugins.push([require.resolve('babel-plugin-transform-runtime'), + { polyfill: false }]); + return babelConfig; +}; diff --git a/getTSCommonConfig.js b/getTSCommonConfig.js new file mode 100644 index 0000000..572d89e --- /dev/null +++ b/getTSCommonConfig.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = function () { + return { + target: 'es6', + jsx: 'preserve', + moduleResolution: 'node', + declaration: true, + allowSyntheticDefaultImports: true, + }; +}; diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..a379aed --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,41 @@ +const gulp = require('gulp'); +const ts = require('gulp-typescript'); +const tsConfig = require('./getTSCommonConfig')(); +const babelConfig = require('./getBabelCommonConfig')(); +delete babelConfig.cacheDirectory; +const babel = require('gulp-babel'); +const transformLess = require('atool-build/lib/transformLess'); +const through2 = require('through2'); +const merge2 = require('merge2'); + +function babelify(js) { + return js.pipe(babel(babelConfig)) + .pipe(gulp.dest('lib')); +} + +gulp.task('default', () => { + const less = gulp.src(['src/' + '**/' + '*.less']) + .pipe(through2.obj(function (file, encoding, next) { + this.push(file.clone()); + if (file.path.match(/\/style\/index\.less$/)) { + transformLess(file.path).then((css) => { + file.contents = new Buffer(css); + file.path = file.path.replace(/\.less$/, '.css'); + this.push(file); + next(); + }).catch((e) => { + console.error(e); + }); + } else { + next(); + } + })) + .pipe(gulp.dest('lib')); + const img = gulp.src(['src/' + '**/' + '*.png']).pipe(gulp.dest('lib')); + const tsResult = gulp.src([ + 'src/' + '**/' + '*.tsx', + ]).pipe(ts(tsConfig)); + const tsFiles = babelify(tsResult.js); + const tsd = tsResult.dts.pipe(gulp.dest('lib')); + return merge2([tsFiles, tsd]); +}); \ No newline at end of file diff --git a/package.json b/package.json index a16e953..b3f6f3a 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,12 @@ "name": "react-viewer", "version": "0.0.1", "description": "react image viewer", - "main": "dist/ViewerWrap", + "main": "dist/index", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "dora --port 8001 --plugins \"webpack,webpack-hmr,browser-history?index=/demo/index.html\"", - "lint": "tslint -c tslint.json src/**/*.ts src/**/*.tsx" + "lint": "tslint -c tslint.json src/**/*.ts src/**/*.tsx", + "build": "atool-build --config webpack.config.prop.js && gulp" }, "repository": { "type": "git", @@ -22,21 +23,33 @@ "bugs": { "url": "https://github.com/infeng/react-viewer/issues" }, + "files": [ + "dist", + "lib" + ], + "typings": "lib/index.d.ts", "homepage": "https://github.com/infeng/react-viewer#readme", "devDependencies": { "@types/node": "^6.0.45", "@types/react": "^0.14.39", "@types/react-dom": "^0.14.17", "atool-build": "^0.8.1", + "babel-plugin-transform-runtime": "^6.15.0", + "babel-runtime": "^6.11.6", "dora": "^0.4.3", "dora-plugin-browser-history": "^0.2.0", "dora-plugin-webpack": "^0.8.1", "dora-plugin-webpack-hmr": "^0.2.1", + "gulp": "^3.9.1", + "gulp-babel": "^6.1.2", + "gulp-typescript": "^3.0.2", "html-loader": "^0.4.4", "html-webpack-plugin": "^2.22.0", + "merge2": "^1.0.2", "pre-commit": "^1.1.3", "react": "^15.3.2", "react-dom": "^15.3.2", + "through2": "^2.0.1", "tslint": "^3.15.1", "typescript": "^2.0.3", "webpack": "^1.13.2" diff --git a/src/Viewer.tsx b/src/Viewer.tsx index 9d757d0..0287530 100644 --- a/src/Viewer.tsx +++ b/src/Viewer.tsx @@ -1,78 +1,67 @@ import * as React from 'react'; -import './style/index.less'; +import * as ReactDOM from 'react-dom'; import ViewerCore from './ViewerCore'; -import ViewerNav from './ViewerNav'; +import ViewerProps from './ViewerProps'; -function noop() {} +export default class Viewer extends React.Component { + private container: HTMLDivElement; + private component: React.ReactNode; -interface ViewerState { - activeIndex?: number; -} + constructor() { + super(); -export default class Viewer extends React.Component { - static defaultProps = { - visible: false, - onClose: noop, - images: [], - activeIndex: 0, - }; - - private prefixCls: string; - - constructor(props) { - super(props); - - this.prefixCls = 'react-viewer'; - - this.state = { - activeIndex: this.props.activeIndex, - }; - - this.handleChangeImg = this.handleChangeImg.bind(this); + this.container = null; + this.component = null; } - handleClose(e) { - this.props.onClose(); - } - - handleChangeImg(newIndex: number) { - let newState = this.state; - newState.activeIndex = newIndex; - this.setState(newState); - } - - componentWillReceiveProps(nextProps: ViewerProps) { - if (this.state.activeIndex !== nextProps.activeIndex) { - this.setState({ - activeIndex: nextProps.activeIndex, - }); + renderViewer() { + if (this.props.visible || this.component) { + if (!this.container) { + this.container = document.createElement('div'); + } + document.body.appendChild(this.container); + let instance = this; + ReactDOM.unstable_renderSubtreeIntoContainer( + this, + , + 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.removeViewer(); + } else { + this.removeViewer(); + } + } + + componentDidMount() { + this.renderViewer(); + } + + componentDidUpdate() { + this.renderViewer(); + } + render() { - let activeImgSrc = ''; - if (this.props.images.length > 0) { - activeImgSrc = this.props.images[this.state.activeIndex]; - } - - return ( -
-
-
- -
- -
-
- ); + return null; } } diff --git a/src/ViewerCavans.tsx b/src/ViewerCavans.tsx new file mode 100644 index 0000000..9a07713 --- /dev/null +++ b/src/ViewerCavans.tsx @@ -0,0 +1,169 @@ +import * as React from 'react'; + +export interface ViewerCavansProps { + prefixCls: string; + imgSrc: string; + visible: boolean; +} + +export interface ViewerCavansState { + width?: number; + top?: number; + left?: number; + isMouseDown?: boolean; + mouseX?: number; + mouseY?: number; +} + +export default class ViewerCavans extends React.Component { + + constructor() { + super(); + + this.state = { + width: 0, + top: 15, + left: null, + isMouseDown: false, + 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); + this.handleResize = this.handleResize.bind(this); + } + + componentDidMount() { + this.bindEvent(); + this.resizeImg(this.props.imgSrc); + } + + resizeImg(imgSrc) { + let img = new Image(); + img.src = imgSrc; + img.onload = () => { + let width = 0; + let height = 0; + let imgWidth = img.width; + let imgHeight = img.height; + let aspectRatio = imgWidth / imgHeight; + if (aspectRatio > 1) { + width = Math.min(window.innerWidth * .9, imgWidth); + height = (width / imgWidth) * imgHeight; + }else { + height = Math.min((window.innerHeight - 52) * .8, imgHeight); + width = (height / imgHeight) * imgWidth; + } + let left = ( window.innerWidth - width ) / 2; + let top = (window.innerHeight - height) / 2; + this.setState({ + width: width, + left: left, + top: top, + }); + }; + } + + handleResize(e) { + this.resizeImg(this.props.imgSrc); + } + + handleMouseDown(e) { + e.preventDefault(); + e.stopPropagation(); + this.setState({ + isMouseDown: true, + mouseX: e.nativeEvent.pageX, + mouseY: e.nativeEvent.pageY, + }); + } + + handleMouseMove(e) { + if (this.state.isMouseDown) { + let diffX = e.x - this.state.mouseX; + let diffY = e.y - this.state.mouseY; + this.setState({ + top: this.state.top + diffY, + left: this.state.left + diffX, + mouseX: e.x, + mouseY: e.y, + }); + } + } + + handleMouseUp(e) { + this.setState({ + isMouseDown: false, + }); + } + + handleMouseScroll(e) { + let direct: 0 | 1 | -1 = 0; + if (e.wheelDelta) { + direct = e.wheelDelta > 0 ? 1 : -1; + }else if (e.detail) { + direct = e.detail > 0 ? 1 : -1; + } + if (direct !== 0) { + this.setState({ + width: this.state.width + direct * this.state.width * 0.05, + }); + } + } + + bindEvent(remove?: boolean) { + let funcName = 'addEventListener'; + if (remove) { + funcName = 'removeEventListener'; + } + document[funcName]('mousewheel', this.handleMouseScroll, false); + document[funcName]('click', this.handleMouseUp, false); + document[funcName]('mousemove', this.handleMouseMove, false); + window[funcName]('resize', this.handleResize, false); + } + + componentWillReceiveProps(nextProps: ViewerCavansProps) { + if (this.props.imgSrc !== nextProps.imgSrc) { + this.resizeImg(nextProps.imgSrc); + } + if (!this.props.visible && nextProps.visible) { + this.bindEvent(); + this.resizeImg(this.props.imgSrc); + } + if (this.props.visible && !nextProps.visible) { + this.bindEvent(true); + } + } + + render() { + let imgStyle: React.CSSProperties = { + width: `${this.state.width}px`, + marginTop: `${this.state.top}px`, + marginLeft: this.state.left ? `${this.state.left}px` : 'auto', + }; + + let imgClass = ''; + if (!this.state.isMouseDown) { + imgClass = `${this.props.prefixCls}-transition`; + } + + return ( +
+ +
+ ); + } +} diff --git a/src/ViewerCore.tsx b/src/ViewerCore.tsx index c8c35c7..70b197e 100644 --- a/src/ViewerCore.tsx +++ b/src/ViewerCore.tsx @@ -1,168 +1,78 @@ import * as React from 'react'; +import './style/index.less'; +import ViewerCavans from './ViewerCavans'; +import ViewerNav from './ViewerNav'; +import ViewerProps from './ViewerProps'; -export interface ViewerCoreProps { - prefixCls: string; - imgSrc: string; - visible: boolean; +function noop() {} + +export interface ViewerCoreState { + activeIndex?: number; } -interface ViewerCoreState { - width?: number; - top?: number; - left?: number; - isMouseDown?: boolean; - mouseX?: number; - mouseY?: number; -} +export default class ViewerCore extends React.Component { + static defaultProps = { + visible: false, + onClose: noop, + images: [], + activeIndex: 0, + }; -export default class ViewerCore extends React.Component { + private prefixCls: string; - constructor() { - super(); + constructor(props) { + super(props); + + this.prefixCls = 'react-viewer'; this.state = { - width: 0, - top: 15, - left: null, - isMouseDown: false, - mouseX: 0, - mouseY: 0, + activeIndex: this.props.activeIndex, }; - 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); - this.handleResize = this.handleResize.bind(this); + this.handleChangeImg = this.handleChangeImg.bind(this); } - componentDidMount() { - this.bindEvent(); - this.resizeImg(this.props.imgSrc); + handleClose(e) { + this.props.onClose(); } - resizeImg(imgSrc) { - let img = new Image(); - img.src = imgSrc; - img.onload = () => { - let width = 0; - let height = 0; - let imgWidth = img.width; - let imgHeight = img.height; - let aspectRatio = imgWidth / imgHeight; - if (aspectRatio > 1) { - width = Math.min(window.innerWidth * .9, imgWidth); - height = (width / imgWidth) * imgHeight; - }else { - height = Math.min((window.innerHeight - 52) * .8, imgHeight); - width = (height / imgHeight) * imgWidth; - } - let left = ( window.innerWidth - width ) / 2; - let top = (window.innerHeight - height) / 2; + handleChangeImg(newIndex: number) { + let newState = this.state; + newState.activeIndex = newIndex; + this.setState(newState); + } + + componentWillReceiveProps(nextProps: ViewerProps) { + if (this.state.activeIndex !== nextProps.activeIndex) { this.setState({ - width: width, - left: left, - top: top, + activeIndex: nextProps.activeIndex, }); - }; - } - - handleResize(e) { - this.resizeImg(this.props.imgSrc); - } - - handleMouseDown(e) { - e.preventDefault(); - e.stopPropagation(); - this.setState({ - isMouseDown: true, - mouseX: e.nativeEvent.pageX, - mouseY: e.nativeEvent.pageY, - }); - } - - handleMouseMove(e) { - if (this.state.isMouseDown) { - let diffX = e.x - this.state.mouseX; - let diffY = e.y - this.state.mouseY; - this.setState({ - top: this.state.top + diffY, - left: this.state.left + diffX, - mouseX: e.x, - mouseY: e.y, - }); - } - } - - handleMouseUp(e) { - this.setState({ - isMouseDown: false, - }); - } - - handleMouseScroll(e) { - let direct: 0 | 1 | -1 = 0; - if (e.wheelDelta) { - direct = e.wheelDelta > 0 ? 1 : -1; - }else if (e.detail) { - direct = e.detail > 0 ? 1 : -1; - } - if (direct !== 0) { - this.setState({ - width: this.state.width + direct * this.state.width * 0.05, - }); - } - } - - bindEvent(remove?: boolean) { - let funcName = 'addEventListener'; - if (remove) { - funcName = 'removeEventListener'; - } - document[funcName]('mousewheel', this.handleMouseScroll, false); - document[funcName]('click', this.handleMouseUp, false); - document[funcName]('mousemove', this.handleMouseMove, false); - window[funcName]('resize', this.handleResize, false); - } - - componentWillReceiveProps(nextProps: ViewerCoreProps) { - if (this.props.imgSrc !== nextProps.imgSrc) { - this.resizeImg(nextProps.imgSrc); - } - if (!this.props.visible && nextProps.visible) { - this.bindEvent(); - this.resizeImg(this.props.imgSrc); - } - if (this.props.visible && !nextProps.visible) { - this.bindEvent(true); } } render() { - let imgStyle: React.CSSProperties = { - width: `${this.state.width}px`, - marginTop: `${this.state.top}px`, - marginLeft: this.state.left ? `${this.state.left}px` : 'auto', - }; - - let imgClass = ''; - if (!this.state.isMouseDown) { - imgClass = `${this.props.prefixCls}-transition`; + let activeImgSrc = ''; + if (this.props.images.length > 0) { + activeImgSrc = this.props.images[this.state.activeIndex]; } return ( -
- +
+
+ +
+ +
); } diff --git a/src/ViewerProps.ts b/src/ViewerProps.ts index 58e9d0b..5a01efb 100644 --- a/src/ViewerProps.ts +++ b/src/ViewerProps.ts @@ -8,3 +8,5 @@ interface ViewerProps { /** 当前图像index */ activeIndex?: number; } + +export default ViewerProps; diff --git a/src/ViewerWrap.tsx b/src/ViewerWrap.tsx deleted file mode 100644 index 536a4fc..0000000 --- a/src/ViewerWrap.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import Viewer from './Viewer'; - -export default class ViewerWrap extends React.Component { - private container: HTMLDivElement; - private component: React.ReactNode; - - constructor() { - super(); - - this.container = null; - this.component = null; - } - - renderViewer() { - if (this.props.visible || this.component) { - if (!this.container) { - this.container = document.createElement('div'); - } - document.body.appendChild(this.container); - let instance = this; - ReactDOM.unstable_renderSubtreeIntoContainer( - this, - , - 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.removeViewer(); - } else { - this.removeViewer(); - } - } - - componentDidMount() { - this.renderViewer(); - } - - componentDidUpdate() { - this.renderViewer(); - } - - render() { - return null; - } -} diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..c055a22 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,3 @@ +import Viewer from './Viewer'; + +export default Viewer; diff --git a/src/style/index.less b/src/style/index.less index f227a79..9010e52 100644 --- a/src/style/index.less +++ b/src/style/index.less @@ -29,7 +29,7 @@ z-index: @zIndex; } - &-core { + &-cavans { position: absolute; top: 0; right: 0; @@ -38,7 +38,7 @@ overflow: hidden; } - &-core > img { + &-cavans > img { display: block; margin: 15px auto; width: auto; diff --git a/webpack.config.prop.js b/webpack.config.prop.js new file mode 100644 index 0000000..633536f --- /dev/null +++ b/webpack.config.prop.js @@ -0,0 +1,68 @@ +const webpack = require('atool-build/lib/webpack'); +var path = require('path'); + +const packageName = require(path.join(process.cwd(), 'package.json')).name; + +const entry = './src/index.tsx'; + +module.exports = function (webpackConfig) { + + webpackConfig.entry = Object.assign({}, webpackConfig.entry, { + ['index.min']: entry, + }); + + webpackConfig.externals = { + react: { + root: 'React', + commonjs2: 'react', + commonjs: 'react', + amd: 'react', + }, + 'react-dom': { + root: 'ReactDOM', + commonjs2: 'react-dom', + commonjs: 'react-dom', + amd: 'react-dom', + }, + }; + + webpackConfig.output = { + path: path.resolve(__dirname, './dist'), + filename: '[name].js', + library: packageName, + libraryTarget: 'umd', + }; + + webpackConfig.plugins.some(function (plugin, i) { + if (plugin instanceof webpack.optimize.CommonsChunkPlugin) { + webpackConfig.plugins.splice(i, 1); + + return true; + } + }); + + const uncompressedWebpackConfig = Object.assign({}, webpackConfig); + + uncompressedWebpackConfig.entry = { + [`index`]: entry, + }; + + uncompressedWebpackConfig.plugins = webpackConfig.plugins.filter((plugin) => { + const ret = !(plugin instanceof webpack.optimize.UglifyJsPlugin); + return ret; + }); + + uncompressedWebpackConfig.plugins = uncompressedWebpackConfig.plugins.filter((plugin) => { + const ret = !(plugin instanceof webpack.DefinePlugin); + return ret; + }); + + uncompressedWebpackConfig.plugins.push(new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('development'), + })); + + return [ + webpackConfig, + uncompressedWebpackConfig, + ]; +}; \ No newline at end of file