jack's boxes

by Felis Catus

HTML

<script src="http://fb.me/react-with-addons-0.12.0.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.0.js"></script>
<script src="http://fb.me/react-js-fiddle-integration.js"></script>

CSS

.Grid {
    position: relative;
    margin: 10px;
    padding: 5px;
    background-color: rgba(6, 60, 109, 1);
    -webkit-transition: all .3s;
    -moz-transition: all .3s;
    -o-transition: all .3s;
    transition: all .3s;
}

.block {
    background-color: rgba(255, 164, 25, 1);
    -webkit-transition: all .3s;
    -moz-transition: all .3s;
    -o-transition: all .3s;
    transition: all .3s;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

.active {
    opacity: 0.4;
}

.placeholder {
    z-index: 1000;
    background-color: rgba(255, 164, 25, 1);
    -moz-box-shadow:    1px 1px 2px 2px #000;
    -webkit-box-shadow: 1px 1px 2px 2px #000;
    box-shadow:         1px 1px 2px 2px #000;
}

.addBlock {
    background-color: rgba(255, 164, 25, 0.6);
    border: 5px dashed rgba(255, 164, 25, 0.8);
    cursor: pointer;
    -webkit-transition: all .3s;
    -moz-transition: all .3s;
    -o-transition: all .3s;
    transition: all .3s;
    -webkit-box-sizing:border-box;
    -moz-box-sizing:border-box;
    box-sizing:border-box;
}

.removeBlock {
    min-width: 24px;
    min-height: 18px;
    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAABigAAAYoBM5cwWAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACDSURBVDjLY/j//z8DLTHDgFsABAZAzINFnAckR5EFQFABxCDGZSAWQxIXg4qBOBVkWQAENlADYPgqEItD8VU0ORtyLBAF4vtoBl2HYmQxkBpRcoNIDojvohmIjEFycpRGsiwQ38FiOEhMlhqpiHYW0DSIaB7JNE+mNM9odCkqhkRpCgDUbYLSOAz5bwAAAABJRU5ErkJggg71016572dfa09f0c81c90339e17120a3');
    position: absolute;
    top: 0;
    right: -3px;
    cursor: pointer;
}

.resizeBlock {
    min-width: 16px;
    min-height: 16px;
    background-image:...

JavaScript 1.7

var u = {
    copyLayout: function(layout) {
        return layout.map(function (block, i) {
            var b = {};
            Object.keys(block).forEach(function (key) {
                b[key] = block[key];
            });
            return b;
        });
    },

    getRowsCols: function (layout, arg) {
        return layout.reduce(function (prev, cur) {
            return Math.max(prev, cur[arg] + cur[arg + 'span']);
        }, 1);
    },

    exclude: function (layout, block) {
        return layout.filter(function (b) {
            return b !== block;
        });
    },

    getBlock: function (layout, row, col) {
        return layout.filter(function (b) {
            return b.row === row && b.col === col;
        })[0];
    },

    getCollisions: function (layout, block) {
        return layout.filter(function (b) {
            return b !== block && !(
                b.row + b.rowspan <= block.row ||
                b.row >= block.row + block.rowspan ||
                b.col + b.colspan <= block.col ||
                b.col >= block.col + block.colspan
            );
        });
    },

    doesBlockCollide: function (layout, block) {
        return u.getCollisions(layout, block).length > 0;
    },

    compactLayout: function (layout) {
        var rows = u.getRowsCols(layout, 'row');
        var cols = u.getRowsCols(layout, 'col');
        for (var i = 0; i < rows; ++i) {
            for (var j = 0; j < cols; ++j) {
                var b = u.getBlock(layout, i, j);
                
                if (b !== undefined) {
                    for (var r = b.row - 1; r >= 0; --r) {
                        b.row = r;
                        if (u.doesBlockCollide(layout, b)) {
                            b.row += 1;
                            break;
                        }
                    }
                }
            }
        }
        
        return layout;
    },

    resolveCollisions: function (layout, block, depth) {
        if...