Skip to content

Commit

Permalink
Merge pull request #325 from bjoernricks/sortable-grid
Browse files Browse the repository at this point in the history
Sortable grid part #4
  • Loading branch information
swaterkamp committed Feb 1, 2018
2 parents a11b50b + 58a1e01 commit 27361d5
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 50 deletions.
1 change: 1 addition & 0 deletions ng/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ set (NG_JS_SRC_FILES
${NG_SRC_DIR}/src/web/components/sortable/emptyrow.js
${NG_SRC_DIR}/src/web/components/sortable/grid.js
${NG_SRC_DIR}/src/web/components/sortable/item.js
${NG_SRC_DIR}/src/web/components/sortable/resizer.js
${NG_SRC_DIR}/src/web/components/sortable/row.js
${NG_SRC_DIR}/src/web/components/sticky/container.js
${NG_SRC_DIR}/src/web/components/sticky/sticky.js
Expand Down
58 changes: 37 additions & 21 deletions ng/src/web/components/sortable/grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export const createItem = callback => {
};
};

const updateRow = (row, data) => {
return {
...row,
...data,
};
};

class Grid extends React.Component {

static propTypes = {
Expand All @@ -68,6 +75,25 @@ class Grid extends React.Component {

this.handleDragEnd = this.handleDragEnd.bind(this);
this.handleDragStart = this.handleDragStart.bind(this);
this.handleRowResize = this.handleRowResize.bind(this);
}

notifyChange(items) {
const {onChange} = this.props;

if (is_defined(onChange)) {
onChange(items);
}
}

handleRowResize(row, height) {
let {items} = this.props;
items = [...items];

const rowIndex = findRowIndex(items, row.id);
items[rowIndex] = updateRow(row, {height});

this.notifyChange(items);
}

handleDragStart(drag) {
Expand Down Expand Up @@ -110,10 +136,8 @@ class Grid extends React.Component {

if (destrowId === 'empty') {
// update row
items[sourcerowIndex] = {
id: sourcerowId,
items: sourceRowItems,
};
items[sourcerowIndex] = updateRow(sourceRow,
{id: sourcerowId, items: sourceRowItems});

// create new row with the removed item
items = [...items, createRow([item])];
Expand All @@ -122,25 +146,19 @@ class Grid extends React.Component {
// add at position destindex
sourceRowItems.splice(destIndex, 0, item);

items[sourcerowIndex] = {
id: sourcerowId,
items: sourceRowItems,
};
items[sourcerowIndex] = updateRow(sourceRow,
{id: sourcerowId, items: sourceRowItems});
}
else {
items[sourcerowIndex] = {
id: sourcerowId,
items: sourceRowItems,
};
items[sourcerowIndex] = updateRow(sourceRow,
{id: sourcerowId, items: sourceRowItems});

// add to destination row
const destrowItems = [...destRow.items];
destrowItems.splice(destIndex, 0, item);

items[destrowIndex] = {
id: destrowId,
items: destrowItems,
};
items[destrowIndex] = updateRow(destRow,
{id: destrowId, items: destrowItems});
}

// remove possible empty last row
Expand All @@ -149,11 +167,7 @@ class Grid extends React.Component {
items.pop();
}

const {onChange} = this.props;

if (is_defined(onChange)) {
onChange(items);
}
this.notifyChange(items);
}

render() {
Expand All @@ -174,6 +188,8 @@ class Grid extends React.Component {
key={row.id}
id={row.id}
dropDisabled={disabled}
height={row.height}
onResize={height => this.handleRowResize(row, height)}
>
{row.items.map((item, index) => (
<Item
Expand Down
2 changes: 1 addition & 1 deletion ng/src/web/components/sortable/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const GridItem = glamorous.div({
display: 'flex',
flexGrow: 1,
userSelect: 'none',
margin: '5px',
margin: '5px 8px',
});

const Item = ({
Expand Down
114 changes: 114 additions & 0 deletions ng/src/web/components/sortable/resizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* Greenbone Security Assistant
*
* Authors:
* Björn Ricks <bjoern.ricks@greenbone.net>
*
* Copyright:
* Copyright (C) 2018 Greenbone Networks GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import React from 'react';

import glamorous from 'glamorous';

import {is_defined, throttleAnimation} from 'gmp/utils';

import PropTypes from '../../utils/proptypes';

const ResizeContainer = glamorous.div({
cursor: 'row-resize',
height: '10px',
width: '100%',
zIndex: '1',
display: 'flex',
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
});

const ResizeIcon = glamorous.span({
height: '2px',
width: '20px',
borderTop: '1px solid rgba(0, 0, 0, 0.3)',
borderBottom: '1px solid rgba(0, 0, 0, 0.3)',
});

class Resizer extends React.Component {

static propTypes = {
onResize: PropTypes.func,
}

constructor(...args) {
super(...args);

this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);

this.notifyResize = throttleAnimation(this.notifyResize.bind(this));
}

handleMouseDown(event) {
if (event.buttons & 1) { // eslint-disable-line no-bitwise
this.startY = event.pageY;

document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
event.preventDefault();
}
}

handleMouseMove(event) {
const {onResize} = this.props;

event.preventDefault();

if (is_defined(onResize)) {
const diffY = event.pageY - this.startY;
this.startY = event.pageY;
this.notifyResize(diffY); // difference since last notification
}
}

notifyResize(diffY) {
const {onResize} = this.props;
onResize(diffY);
}

handleMouseUp(event) {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
event.preventDefault();
}

componentWillUnmount() {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
}

render() {
return (
<ResizeContainer onMouseDown={this.handleMouseDown}>
<ResizeIcon/>
</ResizeContainer>
);
}
}

export default Resizer;

// vim: set ts=2 sw=2 tw=80:
92 changes: 66 additions & 26 deletions ng/src/web/components/sortable/row.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,81 @@ import glamorous from 'glamorous';

import {Droppable} from 'react-beautiful-dnd';

import PropTypes from '../../utils/proptypes.js';
import {is_defined} from 'gmp/utils';

const GridRow = glamorous.div({
import PropTypes from '../../utils/proptypes';

import Resizer from './resizer';

const MIN_HEIGHT = 50;

const GridRow = glamorous.div('grid-row', {
display: 'flex',
margin: '8px 0px',
minHeight: '50px',
}, ({isDraggingOver}) => ({
minHeight: `${MIN_HEIGHT}px`,
}, ({isDraggingOver, height}) => ({
background: isDraggingOver ? 'lightblue' : 'none',
height,
}));

const Row = ({
children,
dropDisabled,
id,
}) => (
<Droppable
isDropDisabled={dropDisabled}
droppableId={id}
direction="horizontal"
>
{(provided, snapshot) => (
<GridRow
innerRef={provided.innerRef}
isDraggingOver={snapshot.isDraggingOver}
>
{children}
{provided.placeholder}
</GridRow>
)}
</Droppable>
);
class Row extends React.Component {
constructor(...args) {
super(...args);

this.handleResize = this.handleResize.bind(this);
}

handleResize(diffY) {
const {onResize} = this.props;

if (is_defined(onResize)) {
const box = this.row.getBoundingClientRect();
const height = box.height + diffY;

if (height > MIN_HEIGHT) {
onResize(height);
}
}
}

render() {
const {
children,
dropDisabled,
id,
height,
} = this.props;
return (
<React.Fragment>
<Droppable
isDropDisabled={dropDisabled}
droppableId={id}
direction="horizontal"
>
{(provided, snapshot) => (
<GridRow
innerRef={ref => {
this.row = ref;
provided.innerRef(ref);
}}
isDraggingOver={snapshot.isDraggingOver}
height={height}
>
{children}
{provided.placeholder}
</GridRow>
)}
</Droppable>
<Resizer onResize={this.handleResize} />
</React.Fragment>
);
}
}

Row.propTypes = {
dropDisabled: PropTypes.bool,
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
id: PropTypes.string.isRequired,
onResize: PropTypes.func,
};

export default Row;
Expand Down
Loading

0 comments on commit 27361d5

Please sign in to comment.