import React from 'react'
import PropTypes from 'prop-types'
import GridItem from './GridItem'
import './style.css'
const correctLayout = (layout) => {
var copy = [...layout];
for (let i = 0; i < layout.length - 1; i++) {
if (collision(copy[i], copy[i + 1])) {
copy = layoutCheck(copy, layout[i], layout[i].key, layout[i].key, undefined)
}
}
return copy;
}
/**
* 用key从layout中拿出item
* @param {*} layout 输入进来的布局
* @param {*} key
*/
const layoutItemForkey = (layout, key) => {
for (let i = 0, length = layout.length; i < length; i++) {
if (key === layout[i].key) {
return layout[i]
}
}
}
/**
* 初始化的时候调用
* 会把isUserMove和key一起映射到layout中
* 不用用户设置
* @param {*} layout
* @param {*} children
*/
const MapLayoutTostate = (layout, children) => {
return layout.map((child, index) => {
let newChild = { ...child, isUserMove: true, key: children[index].key }
return newChild
})
}
/**
* 把用户移动的块,标记为true
* @param {*} layout
* @param {*} key
* @param {*} GridX
* @param {*} GridY
* @param {*} isUserMove
*/
const syncLayout = (layout, key, GridX, GridY, isUserMove) => {
const newlayout = layout.map((item) => {
if (item.key === key) {
item.GridX = GridX
item.GridY = GridY
item.isUserMove = isUserMove
return item
}
return item
})
return newlayout
}
const collision = (a, b) => {
if (a.GridX === b.GridX && a.GridY === b.GridY &&
a.w === b.w && a.h === b.h) {
return true
}
if (a.GridX + a.w <= b.GridX) return false
if (a.GridX >= b.GridX + b.w) return false
if (a.GridY + a.h <= b.GridY) return false
if (a.GridY >= b.GridY + b.h) return false
return true
}
const sortLayout = (layout) => {
return [].concat(layout).sort((a, b) => {
if (a.GridY > b.GridY || (a.GridY === b.GridY && a.GridX > b.GridX)) {
return 1
} else if (a.GridY === b.GridY && a.GridX === b.GridX) {
return 0
}
return -1
})
}
/**获取layout中,item第一个碰撞到的物体 */
const getFirstCollison = (layout, item) => {
for (let i = 0, length = layout.length; i < length; i++) {
if (collision(layout[i], item)) {
return layout[i]
}
}
return null
}
/**
* 压缩单个元素,使得每一个元素都会紧挨着边界或者相邻的元素
* @param {*} finishedLayout 压缩完的元素会放进这里来,用来对比之后的每一个元素是否需要压缩
* @param {*} item
*/
const compactItem = (finishedLayout, item) => {
const newItem = { ...item }
if (finishedLayout.length === 0) {
return { ...newItem, GridY: 0 }
}
/**
* 类似一个递归调用
*/
while (true) {
let FirstCollison = getFirstCollison(finishedLayout, newItem)
if (FirstCollison) {
/**第一次发生碰撞时,就可以返回了 */
newItem.GridY = FirstCollison.GridY + FirstCollison.h
return newItem
}
newItem.GridY--
if (newItem.GridY < 0) return { ...newItem, GridY: 0 }/**碰到边界的时候,返回 */
}
return newItem
}
/**
* 压缩layout,使得每一个元素都会紧挨着边界或者相邻的元素
* @param {*} layout
*/
const compactLayout = (layout) => {
let sorted = sortLayout(layout)
const needCompact = Array(layout.length)
const compareList = []
for (let i = 0, length = sorted.length; i < length; i++) {
let finished = compactItem(compareList, sorted[i])
finished.isUserMove = false
compareList.push(finished)
needCompact[i] = finished//用于输出从小到大的位置
}
return sortLayout(needCompact)
}
const layoutCheck = (layout, layoutItem, key, fristItemkey, moving) => {
let i = [], movedItem = []/**收集所有移动过的物体 */
let newlayout = layout.map((item, idx) => {
if (item.key !== key) {
if (collision(item, layoutItem)) {
i.push(item.key)
/**
* 这里就是奇迹发生的地方,如果向上移动,那么必须注意的是
* 一格一格的移动,而不是一次性移动
*/
let offsetY = item.GridY + 1
/**这一行也非常关键,当向上移动的时候,碰撞的元素必须固定 */
// if (moving < 0 && layoutItem.GridY > 0) offsetY = item.GridY
if (layoutItem.GridY > item.GridY && layoutItem.GridY < item.GridY + item.h) {
/**
* 元素向上移动时,元素的上面空间不足,则不移动这个元素
* 当元素移动到GridY>所要向上交换的元素时,就不会进入这里,直接交换元素
*
*/
offsetY = item.GridY
}
/**
* 物体向下移动的时候
*/
if (moving > 0) {
if (layoutItem.GridY + layoutItem.h < item.GridY) {
let collision;
let copy = { ...item }
while (true) {
let newLayout = layout.filter((item) => {
if (item.key !== key && (item.key !== copy.key)) {
return item
}
})
collision = getFirstCollison(newLayout, copy)
if (collision) {
offsetY = collision.GridY + collision.h
break
} else {
copy.GridY--
}
if (copy.GridY < 0) {
offsetY = 0
break
}
}
}
}
movedItem.push({ ...item, GridY: offsetY, isUserMove: false })
return { ...item, GridY: offsetY, isUserMove: false }
}
} else if (fristItemkey === key) {
/**永远保持用户移动的块是 isUserMove === true */
return { ...item, GridX: layoutItem.GridX, GridY: layoutItem.GridY, isUserMove: true }
}
return item
})
/** 递归调用,将layout中的所有重叠元素全部移动 */
const length = movedItem.length;
for (let c = 0; c < length; c++) {
newlayout = layoutCheck(newlayout, movedItem[c], i[c], fristItemkey, undefined)
}
return newlayout
}
const getMaxContainerHeight = (layout, elementHeight) => {
const height = (layout[layout.length - 1].GridY + layout[layout.length - 1].h) * (30 + 10) + 10
return height
}
class DraggerLayout extends React.Component {
constructor(props) {
super(props)
this.onDrag = this.onDrag.bind(this)
this.onDragStart = this.onDragStart.bind(this)
this.onDragEnd = this.onDragEnd.bind(this)
}
static PropTypes = {
/**外部属性 */
layout: PropTypes.array,
col: PropTypes.number,
width: PropTypes.number,
/**每个元素的最小高度 */
rowHeight: PropTypes.number,
padding: PropTypes.number,
}
state = {
GridXMoving: 0,
GridYMoving: 0,
wMoving: 0,
hMoving: 0,
placeholderShow: false,
placeholderMoving: false,
layout: MapLayoutTostate(this.props.layout, this.props.children),
containerHeight: 500
}
onDragStart(bundles) {
const { GridX, GridY, w, h, UniqueKey } = bundles
const newlayout = syncLayout(this.state.layout, UniqueKey, GridX, GridY, true)
this.setState({
GridXMoving: GridX,
GridYMoving: GridY,
wMoving: w,
hMoving: h,
placeholderShow: true,
placeholderMoving: true,
layout: newlayout,
})
}
onDrag(layoutItem, key) {
const { GridX, GridY } = layoutItem
const moving = GridY - this.state.GridYMoving
const newLayout = layoutCheck(this.state.layout, layoutItem, key, key/*用户移动方块的key */, moving)
const compactedLayout = compactLayout(newLayout)
for (let i = 0; i < compactedLayout.length; i++) {
if (key === compactedLayout[i].key) {
/**
* 特殊点:当我们移动元素的时候,元素在layout中的位置不断改变
* 但是当isUserMove=true的时候,鼠标拖拽的元素不会随着位图变化而变化
* 但是实际layout中的位置还是会改变
* (isUserMove=true用于解除placeholder和元素的绑定)
*/
compactedLayout[i].isUserMove = true
layoutItem.GridX = compactedLayout[i].GridX
layoutItem.GridY = compactedLayout[i].GridY
break
}
}
this.setState({
GridXMoving: layoutItem.GridX,
GridYMoving: layoutItem.GridY,
layout: compactedLayout,
containerHeight: getMaxContainerHeight(compactedLayout)
})
}
onDragEnd(key) {
const compactedLayout = compactLayout(this.state.layout)
this.setState({
placeholderShow: false,
layout: compactedLayout,
containerHeight: getMaxContainerHeight(compactedLayout)
})
}
placeholder() {
if (!this.state.placeholderShow) return null
const { col, width, padding, rowHeight } = this.props
const { GridXMoving, GridYMoving, wMoving, hMoving, placeholderMoving } = this.state
return (