diff --git a/README.md b/README.md index 3b45f3e..40d6d3e 100644 --- a/README.md +++ b/README.md @@ -61,18 +61,19 @@ ReactDOM.render( ## Props -| Prop | Description | Type | Default | -| ----------- | ------------------------------------------------------------ | ---------------------------------------- | ------- | -| src | Src of background image. | string | - | -| coordinates | An array of coordinate( see the table blew), {id, x, y, width, height}. | array | [] | -| width | Width of background image. | number(in pixel) | - | -| height | Height of background image. | number(in pixel) | - | -| onDraw | A callback which hanppends when a user starts drawing a new rectangle. | funtion(coordinate , index, coordinates) | - | -| onDrag | A callback which hanppends when a user stars draging a exited rectangle. | funtion(coordinate , index, coordinates) | - | -| onResize | A callback which hanppends when a user starts resizing a exited rectangle. | funtion(coordinate , index, coordinates) | - | -| onChange | A callback which hanppends when a user starts drawing, draging or resizing a new/exited rectangle. | funtion(coordinate , index, coordinates) | - | -| onDelete | A callback which hanppends when a user delete a exited rectangle. | funtion(coordinate , index, coordinates) | - | -| onLoad | The callback is triggered when the background image is loaded. | onLoad(e) | - | +| Prop | Description | Type | Default | +| ----------------- | ------------------------------------------------------------ | ---------------------------------------- | ------- | +| src | Src of background image. | string | - | +| coordinates | An array of coordinate( see the table blew), {id, x, y, width, height}. | array | [] | +| width | Width of background image. | number(in pixel) | - | +| height | Height of background image. | number(in pixel) | - | +| onDraw | A callback which hanppends when a user starts drawing a new rectangle. | funtion(coordinate , index, coordinates) | - | +| onDrag | A callback which hanppends when a user stars draging a exited rectangle. | funtion(coordinate , index, coordinates) | - | +| onResize | A callback which hanppends when a user starts resizing a exited rectangle. | funtion(coordinate , index, coordinates) | - | +| onChange | A callback which hanppends when a user starts drawing, draging or resizing a new/exited rectangle. | funtion(coordinate , index, coordinates) | - | +| onDelete | A callback which hanppends when a user delete a exited rectangle. | funtion(coordinate , index, coordinates) | - | +| onLoad | The callback is triggered when the background image is loaded. | onLoad(e) | - | +| permitAreaOverlap | If set to false prevent the overlap between crop areas | boolean | true | ### coordinate diff --git a/examples/index.js b/examples/index.js index 0dcfd5e..ad8ab75 100644 --- a/examples/index.js +++ b/examples/index.js @@ -39,6 +39,7 @@ class App extends React.Component { // onDraw={this.changeCoordinate} onChange={this.changeCoordinate} onDelete={this.deleteCoordinate} + permitAreaOverlap={false} // onLoad={e => console.log(e.target.height, e.target.width)} /> diff --git a/src/components/Crop.js b/src/components/Crop.js index 9e5b972..ca805a2 100644 --- a/src/components/Crop.js +++ b/src/components/Crop.js @@ -4,10 +4,12 @@ import { equals, is, update, remove } from 'ramda' import interact from 'interactjs' import { DeleteIcon, NumberIcon } from './Icons' +import { areaNotAvailable } from '../utils' + class Crop extends Component { static cropStyle = (coordinate) => { const { - x, y, width, height, + x, y, width, height, background, zIndex, } = coordinate return { @@ -19,10 +21,9 @@ class Crop extends Component { height, top: y, left: x, - - + zIndex: zIndex || 0, boxShadow: '0 0 6px #000', - background: '#8c8c8c', + background: background || '#8c8c8c', opacity: 0.6, } } @@ -35,6 +36,8 @@ class Crop extends Component { left: true, right: true, bottom: true, top: true, }, }) + .on('mousedown', this.handleMouseDown) + .on('mouseup', this.handleMouseUp) .on('dragmove', this.handleDragMove) .on('resizemove', this.handleResizeMove) } @@ -44,6 +47,35 @@ class Crop extends Component { || (nextProps.index !== this.props.index) } + handleMouseDown = () => { + const { index, coordinate } = this.props + this.previus = { + index, + coordinate, + } + } + + handleMouseUp = () => { + const { + coordinate, coordinates, onResize, onChange, permitAreaOverlap, + } = this.props + + if (!permitAreaOverlap) { + if (coordinate.background && this.previus) { + const nextCoordinates = update(this.previus.index, this.previus.coordinate)(coordinates) + if (is(Function, onResize)) { + onResize(this.previus.coordinate, this.previus.index, nextCoordinates) + } + if (is(Function, onChange)) { + onChange(this.previus.coordinate, this.previus.index, nextCoordinates) + } + } + } + coordinate.zIndex = undefined + this.previus = undefined + } + + handleResizeMove = (e) => { const { index, @@ -52,6 +84,7 @@ class Crop extends Component { coordinates, onResize, onChange, + permitAreaOverlap, } = this.props const { width, height } = e.rect const { left, top } = e.deltaRect @@ -59,6 +92,14 @@ class Crop extends Component { const nextCoordinate = { ...coordinate, x: x + left, y: y + top, width, height, } + + if (!permitAreaOverlap && areaNotAvailable(coordinates, coordinate)) { + nextCoordinate.background = 'red' + nextCoordinate.zIndex = 1 + } else { + nextCoordinate.background = null + } + const nextCoordinates = update(index, nextCoordinate)(coordinates) if (is(Function, onResize)) { onResize(nextCoordinate, index, nextCoordinates) @@ -75,9 +116,19 @@ class Crop extends Component { coordinates, onDrag, onChange, + permitAreaOverlap, } = this.props const { dx, dy } = e + const nextCoordinate = { ...coordinate, x: x + dx, y: y + dy } + + if (!permitAreaOverlap && areaNotAvailable(coordinates, coordinate)) { + nextCoordinate.background = 'red' + nextCoordinate.zIndex = 1 + } else { + nextCoordinate.background = null + } + const nextCoordinates = update(index, nextCoordinate)(coordinates) if (is(Function, onDrag)) { onDrag(nextCoordinate, index, nextCoordinates) @@ -138,7 +189,9 @@ Crop.propTypes = { onDrag: PropTypes.func, // eslint-disable-line onDelete: PropTypes.func, // eslint-disable-line onChange: PropTypes.func, // eslint-disable-line - coordinates: PropTypes.array // eslint-disable-line + coordinates: PropTypes.array, // eslint-disable-line + permitAreaOverlap: PropTypes.bool, // eslint-disable-line + previus: PropTypes.any // eslint-disable-line } export default Crop diff --git a/src/components/MultiCrops.js b/src/components/MultiCrops.js index 7990d18..5e6416e 100644 --- a/src/components/MultiCrops.js +++ b/src/components/MultiCrops.js @@ -3,7 +3,7 @@ import { both, clone, is, complement, equals, map, addIndex } from 'ramda' import PropTypes from 'prop-types' import shortid from 'shortid' import Crop, { coordinateType } from './Crop' - +import { areaNotAvailable } from '../utils' const isValidPoint = (point = {}) => { const strictNumber = number => both( @@ -54,7 +54,13 @@ class MultiCrops extends Component { handleMouseMove = (e) => { - const { onDraw, onChange, coordinates } = this.props + const { + onDraw, + onChange, + coordinates, + permitAreaOverlap, + } = this.props + const { pointA } = this if (isValidPoint(pointA)) { const pointB = this.getCursorPosition(e) @@ -67,6 +73,16 @@ class MultiCrops extends Component { height: Math.abs(pointA.y - pointB.y), id: this.id, } + + const elementIntoArea = areaNotAvailable(coordinates, coordinate) + + if (!permitAreaOverlap && elementIntoArea) { + coordinate.background = 'red' + coordinate.zIndex = 1 + } else { + coordinate.background = null + } + const nextCoordinates = clone(coordinates) nextCoordinates[this.drawingIndex] = coordinate if (is(Function, onDraw)) { @@ -79,6 +95,30 @@ class MultiCrops extends Component { } handlMouseUp = () => { + const { coordinates, onDraw, onChange } = this.props + + let deleteIndex + let coordinate + + coordinates.forEach((element, index) => { + if (element.id === this.id) { + deleteIndex = index + coordinate = element + } + }) + + if (coordinate && coordinate.background === 'red') { + coordinates.splice(deleteIndex, 1) + + const nextCoordinates = clone(coordinates) + if (is(Function, onDraw)) { + onDraw(coordinate, this.drawingIndex, nextCoordinates) + } + if (is(Function, onChange)) { + onChange(coordinate, this.drawingIndex, nextCoordinates) + } + } + this.pointA = {} } @@ -115,22 +155,24 @@ class MultiCrops extends Component { } const { - string, arrayOf, number, func, + string, arrayOf, number, func, bool, } = PropTypes MultiCrops.propTypes = { coordinates: arrayOf(coordinateType), src: string, - width: number, // eslint-disable-line - height: number, // eslint-disable-line - onDraw: func, // eslint-disable-line - onChange: func, // eslint-disable-line - onLoad: func, // eslint-disable-line + width: number, + height: number, + onDraw: func, + onChange: func, + onLoad: func, + permitAreaOverlap: bool, } MultiCrops.defaultProps = { coordinates: [], src: '', + permitAreaOverlap: true, } export default MultiCrops diff --git a/src/utils.js b/src/utils.js index 1b4758a..1bc4713 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,3 +4,33 @@ import { map, assoc, omit } from 'ramda' export const addid = map(assoc('id', shortid.generate())) export const removeid = map(omit(['id'])) + +export const isOverlapping = (self, other) => { + const left = self.x + const right = self.x + self.width + const top = self.y + const bottom = self.y + self.height + const otherleft = other.x + const otherright = other.x + other.width + const othertop = other.y + const otherbottom = other.y + other.height + return left <= otherright && right >= otherleft && top <= otherbottom && bottom >= othertop +} + +export const areaNotAvailable = (coordinates, coordinate) => { + let canContinue + + // eslint-disable-next-line no-plusplus + for (let i = 0; i < coordinates.length; i++) { + const element = coordinates[i] + if (element.id === coordinate.id) { + // eslint-disable-next-line no-continue + continue + } + if (isOverlapping(element, coordinate)) { + canContinue = element + break + } + } + return canContinue +}