From 09927cec34f06cda41e361663ec1db842df073ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 1 Feb 2018 12:54:08 +0100 Subject: [PATCH 1/7] Add a Resizer component It allows resizing via mouse drag and notifies about difference of movement in pixels since last notification. --- ng/CMakeLists.txt | 1 + ng/src/web/components/sortable/resizer.js | 114 ++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 ng/src/web/components/sortable/resizer.js diff --git a/ng/CMakeLists.txt b/ng/CMakeLists.txt index 537b5b93e5..fb0c01c965 100644 --- a/ng/CMakeLists.txt +++ b/ng/CMakeLists.txt @@ -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 diff --git a/ng/src/web/components/sortable/resizer.js b/ng/src/web/components/sortable/resizer.js new file mode 100644 index 0000000000..8661e12d2d --- /dev/null +++ b/ng/src/web/components/sortable/resizer.js @@ -0,0 +1,114 @@ +/* Greenbone Security Assistant + * + * Authors: + * Björn Ricks + * + * 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 ( + + + + ); + } +} + +export default Resizer; + +// vim: set ts=2 sw=2 tw=80: From 44a97b6a838b0a997a1306163933a0cb6a26586c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 1 Feb 2018 12:56:25 +0100 Subject: [PATCH 2/7] Add story for Resizer component --- ng/src/web/stories/sortable/index.js | 47 +++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/ng/src/web/stories/sortable/index.js b/ng/src/web/stories/sortable/index.js index 36eb5c95f6..362a6cfcf6 100644 --- a/ng/src/web/stories/sortable/index.js +++ b/ng/src/web/stories/sortable/index.js @@ -22,13 +22,54 @@ */ import React from 'react'; -import glamorous from 'glamorous'; +import glamorous, {Div} from 'glamorous'; import {storiesOf} from '@storybook/react'; import PropTypes from 'web/utils/proptypes'; import Grid, {createItem, createRow} from 'web/components/sortable/grid.js'; +import Resizer from 'web/components/sortable/resizer'; + +class ResizeContainer extends React.Component { + + constructor(...args) { + super(...args); + + this.state = { + height: '100px', + }; + + this.handleResize = this.handleResize.bind(this); + } + + handleResize(diffY) { + const box = this.div.getBoundingClientRect(); + const height = box.height + diffY; + + if (height > 20) { + this.setState({height}); + } + } + + render() { + const {height} = this.state; + return ( +
+
this.div = ref} + /> + +
+ ); + } +} class ItemController extends React.Component { @@ -104,4 +145,8 @@ storiesOf('Sortable/Grid', module) ); }); +storiesOf('Sortable/Resizer', module) + .add('default', () => ( + + )); // vim: set ts=2 sw=2 tw=80: From b387624bf075feb91e768eee6b0ebe6bf7d4608f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 1 Feb 2018 12:58:25 +0100 Subject: [PATCH 3/7] Extract updating rows into a helper function This allows to create a copy of the row and only updating specific properties. --- ng/src/web/components/sortable/grid.js | 31 +++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/ng/src/web/components/sortable/grid.js b/ng/src/web/components/sortable/grid.js index b4474d1fd7..1601020d1e 100644 --- a/ng/src/web/components/sortable/grid.js +++ b/ng/src/web/components/sortable/grid.js @@ -51,6 +51,13 @@ export const createItem = callback => { }; }; +const updateRow = (row, data) => { + return { + ...row, + ...data, + }; +}; + class Grid extends React.Component { static propTypes = { @@ -110,10 +117,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])]; @@ -122,25 +127,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 From 95841c0f69ae25ae95069b57a1fc3b2e3e1a62ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 1 Feb 2018 13:00:15 +0100 Subject: [PATCH 4/7] Extract notification about item changes into own method Add check if a listener function is passed via props into the new method. --- ng/src/web/components/sortable/grid.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ng/src/web/components/sortable/grid.js b/ng/src/web/components/sortable/grid.js index 1601020d1e..7585500b5a 100644 --- a/ng/src/web/components/sortable/grid.js +++ b/ng/src/web/components/sortable/grid.js @@ -77,6 +77,14 @@ class Grid extends React.Component { this.handleDragStart = this.handleDragStart.bind(this); } + notifyChange(items) { + const {onChange} = this.props; + + if (is_defined(onChange)) { + onChange(items); + } + } + handleDragStart(drag) { const {droppableId: rowId} = drag.source; @@ -148,11 +156,7 @@ class Grid extends React.Component { items.pop(); } - const {onChange} = this.props; - - if (is_defined(onChange)) { - onChange(items); - } + this.notifyChange(items); } render() { From 150c1d3c8c4eaf0f336c327487d3daa3dab3dbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 1 Feb 2018 13:02:00 +0100 Subject: [PATCH 5/7] Change left and right margin if grid items to 8px --- ng/src/web/components/sortable/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ng/src/web/components/sortable/item.js b/ng/src/web/components/sortable/item.js index 3ba39d6519..ef01114c7e 100644 --- a/ng/src/web/components/sortable/item.js +++ b/ng/src/web/components/sortable/item.js @@ -32,7 +32,7 @@ const GridItem = glamorous.div({ display: 'flex', flexGrow: 1, userSelect: 'none', - margin: '5px', + margin: '5px 8px', }); const Item = ({ From bfefb09ceedae245c5158f14b1eb37a6c07f2e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 1 Feb 2018 13:02:54 +0100 Subject: [PATCH 6/7] Allow resizing of grid rows Add a Resizer component underneath each grid Row. It will be possible to reduce the height of a row to currently 50px. --- ng/src/web/components/sortable/grid.js | 13 ++++ ng/src/web/components/sortable/row.js | 92 ++++++++++++++++++-------- 2 files changed, 79 insertions(+), 26 deletions(-) diff --git a/ng/src/web/components/sortable/grid.js b/ng/src/web/components/sortable/grid.js index 7585500b5a..97b0749712 100644 --- a/ng/src/web/components/sortable/grid.js +++ b/ng/src/web/components/sortable/grid.js @@ -75,6 +75,7 @@ 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) { @@ -85,6 +86,16 @@ class Grid extends React.Component { } } + 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) { const {droppableId: rowId} = drag.source; @@ -177,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) => ( ({ + minHeight: `${MIN_HEIGHT}px`, +}, ({isDraggingOver, height}) => ({ background: isDraggingOver ? 'lightblue' : 'none', + height, })); -const Row = ({ - children, - dropDisabled, - id, -}) => ( - - {(provided, snapshot) => ( - - {children} - {provided.placeholder} - - )} - -); +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 ( + + + {(provided, snapshot) => ( + { + this.row = ref; + provided.innerRef(ref); + }} + isDraggingOver={snapshot.isDraggingOver} + height={height} + > + {children} + {provided.placeholder} + + )} + + + + ); + } +} Row.propTypes = { dropDisabled: PropTypes.bool, + height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), id: PropTypes.string.isRequired, + onResize: PropTypes.func, }; export default Row; From 58a1e0144e9152bb1526267abb33e6ea467f76a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 1 Feb 2018 13:04:39 +0100 Subject: [PATCH 7/7] Set initial height of a grid row for story --- ng/src/web/stories/sortable/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ng/src/web/stories/sortable/index.js b/ng/src/web/stories/sortable/index.js index 362a6cfcf6..77d209624b 100644 --- a/ng/src/web/stories/sortable/index.js +++ b/ng/src/web/stories/sortable/index.js @@ -131,7 +131,7 @@ storiesOf('Sortable/Grid', module) }) .add('max 5 items per row', () => { const items = [ - createRow(getItems(0, 3)), + {...createRow(getItems(0, 3)), height: '400px'}, createRow(getItems(1, 5)), ]; return (