Skip to content

Commit f8c27eb

Browse files
douglasmuraokadavimacedo
authored andcommitted
feat(Database Browser): Change columns order and visibility (#1235)
* feat: Column configuration component Allows users to configure the columns visibility and order though a single component. Closes #106 Closes #138 * fix: Simplify components * fix: ColumnPreferences test * fix: External click not closing component
1 parent cb6286e commit f8c27eb

15 files changed

+312
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { useDrag, useDrop } from 'react-dnd';
3+
4+
import Icon from 'components/Icon/Icon.react';
5+
import styles from 'components/ColumnsConfiguration/ColumnConfigurationItem.scss';
6+
7+
const DND_TYPE = 'ColumnConfigurationItem';
8+
9+
export default ({ name, handleColumnDragDrop, index, onChangeVisible, visible }) => {
10+
const [ { isDragging}, drag ] = useDrag({
11+
item: { type: DND_TYPE, index },
12+
collect: monitor => ({ isDragging: !!monitor.isDragging() })
13+
});
14+
15+
const [ { canDrop, isOver }, drop ] = useDrop({
16+
accept: DND_TYPE,
17+
drop: item => handleColumnDragDrop(item.index, index),
18+
canDrop: item => item.index !== index,
19+
collect: monitor => ({
20+
isOver: !!monitor.isOver(),
21+
canDrop: !!monitor.canDrop()
22+
})
23+
});
24+
25+
return drag(drop(
26+
<section
27+
className={styles.columnConfigItem}
28+
style={{
29+
opacity: isDragging ? 0.5 : 1,
30+
cursor: isDragging ? 'grabbing' : null,
31+
backgroundColor: isOver && canDrop ? '#208aec' : null
32+
}}
33+
onClick={() => onChangeVisible(!visible)}>
34+
<div className={[styles.icon, styles.visibilityIcon].join(' ')}>
35+
<Icon name={visible ? 'visibility' : 'visibility_off'} width={18} height={18} fill={visible ? 'white' : 'rgba(0,0,0,0.4)'} />
36+
</div>
37+
<div className={styles.columnConfigItemName} title={name}>{name}</div>
38+
<div className={styles.icon}>
39+
<Icon name='drag-indicator' width={14} height={14} fill="white" />
40+
</div>
41+
</section>
42+
));
43+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.columnConfigItem {
2+
padding: 8px 10px;
3+
display: flex;
4+
justify-content: space-between;
5+
border-radius: 5px;
6+
cursor: grab;
7+
}
8+
9+
.icon {
10+
display: flex;
11+
align-items: center;
12+
height: 24px;
13+
}
14+
15+
.visibilityIcon {
16+
cursor: pointer;
17+
width: 30px;
18+
}
19+
20+
.columnConfigItemName {
21+
width: 110px;
22+
text-overflow: ellipsis;
23+
overflow: hidden;
24+
line-height: 24px;
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React from 'react';
2+
import { DndProvider } from 'react-dnd'
3+
import HTML5Backend from 'react-dnd-html5-backend'
4+
import ReactDOM from 'react-dom';
5+
6+
import Button from 'components/Button/Button.react';
7+
import ColumnConfigurationItem from 'components/ColumnsConfiguration/ColumnConfigurationItem.react';
8+
import styles from 'components/ColumnsConfiguration/ColumnsConfiguration.scss';
9+
import Icon from 'components/Icon/Icon.react';
10+
import Popover from 'components/Popover/Popover.react';
11+
import Position from 'lib/Position';
12+
13+
export default class ColumnsConfiguration extends React.Component {
14+
constructor() {
15+
super();
16+
17+
this.state = {
18+
open: false
19+
};
20+
}
21+
22+
componentDidMount() {
23+
this.node = ReactDOM.findDOMNode(this);
24+
}
25+
26+
componentWillReceiveProps(props) {
27+
if (props.schema !== this.props.schema) {
28+
this.setState({
29+
open: false
30+
});
31+
}
32+
}
33+
34+
toggle() {
35+
this.setState({
36+
open: !this.state.open
37+
})
38+
}
39+
40+
showAll() {
41+
this.props.handleColumnsOrder(this.props.order.map(order => ({ ...order, visible: true })));
42+
}
43+
44+
hideAll() {
45+
this.props.handleColumnsOrder(this.props.order.map(order => ({ ...order, visible: false })));
46+
}
47+
48+
render() {
49+
const { handleColumnDragDrop, handleColumnsOrder, order } = this.props;
50+
const [ title, entry ] = [styles.title, styles.entry ].map(className => (
51+
<div className={className} onClick={this.toggle.bind(this)}>
52+
<Icon name='manage-columns' width={14} height={14} />
53+
<span>Manage Columns</span>
54+
</div>
55+
));
56+
57+
let popover = null;
58+
if (this.state.open) {
59+
popover = (
60+
<Popover fixed={true} position={Position.inDocument(this.node)} onExternalClick={this.toggle.bind(this)}>
61+
<div className={styles.popover}>
62+
{title}
63+
<div className={styles.body}>
64+
<div className={styles.columnConfigContainer}>
65+
<DndProvider backend={HTML5Backend}>
66+
{order.map(({ name, visible, ...rest }, index) => {
67+
return <ColumnConfigurationItem
68+
key={index}
69+
index={index}
70+
name={name}
71+
visible={visible}
72+
onChangeVisible={visible => {
73+
const updatedOrder = [...order];
74+
updatedOrder[index] = {
75+
...rest,
76+
name,
77+
visible
78+
};
79+
handleColumnsOrder(updatedOrder);
80+
}}
81+
handleColumnDragDrop={handleColumnDragDrop} />
82+
})}
83+
</DndProvider>
84+
</div>
85+
<div className={styles.footer}>
86+
<Button
87+
color='white'
88+
value='Hide All'
89+
width='85px'
90+
onClick={this.hideAll.bind(this)} />
91+
<Button
92+
color='white'
93+
value='Show all'
94+
width='85px'
95+
onClick={this.showAll.bind(this)} />
96+
</div>
97+
</div>
98+
</div>
99+
</Popover>
100+
);
101+
}
102+
return (
103+
<>
104+
{entry}
105+
{popover}
106+
</>
107+
);
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
@import 'stylesheets/globals.scss';
2+
3+
.entry {
4+
display: inline-block;
5+
height: 14px;
6+
padding: 0 8px;
7+
8+
svg {
9+
fill: #66637A;
10+
}
11+
12+
&:hover svg {
13+
fill: white;
14+
}
15+
}
16+
17+
.title {
18+
margin-top: -4px;
19+
background: #797691;
20+
padding: 4px 8px;
21+
border-radius: 5px 5px 0 0;
22+
23+
svg {
24+
fill: white;
25+
}
26+
}
27+
28+
.entry, .title {
29+
@include NotoSansFont;
30+
font-size: 14px;
31+
color: #ffffff;
32+
cursor: pointer;
33+
34+
svg {
35+
vertical-align: middle;
36+
margin-right: 4px;
37+
}
38+
39+
span {
40+
vertical-align: middle;
41+
line-height: 14px;
42+
}
43+
}
44+
45+
.body {
46+
color: white;
47+
position: absolute;
48+
top: 22px;
49+
right: 0;
50+
border-radius: 5px 0 5px 5px;
51+
background: #797691;
52+
width: 220px;
53+
font-size: 14px;
54+
55+
.columnConfigContainer {
56+
max-height: calc(100vh - 180px);
57+
overflow: auto;
58+
margin: 10px;
59+
}
60+
}
61+
62+
.footer {
63+
background: rgba(0,0,0,0.2);
64+
padding: 15px 20px;
65+
display: flex;
66+
justify-content: space-between;
67+
68+
> a {
69+
margin-right: 10px;
70+
71+
&:last-child {
72+
margin-right: 0;
73+
}
74+
}
75+
}

src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export default class DataBrowserHeaderBar extends React.Component {
2727
</div>
2828
];
2929

30-
headers.forEach(({ width, name, type, targetClass, order }, i) => {
30+
headers.forEach(({ width, name, type, targetClass, order, visible }, i) => {
31+
if (!visible) return;
3132
let wrapStyle = { width };
3233
if (i % 2) {
3334
wrapStyle.background = '#726F85';

src/components/Popover/Popover.react.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export default class Popover extends React.Component {
9090
}
9191

9292
render() {
93-
return <div></div>;
93+
return null;
9494
}
9595
}
9696

src/dashboard/Data/Browser/BrowserTable.react.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export default class BrowserTable extends React.Component {
8989
checked={this.props.selection['*'] || this.props.selection[obj.id]}
9090
onChange={(e) => this.props.selectRow(obj.id, e.target.checked)} />
9191
</span>
92-
{this.props.order.map(({ name, width }, j) => {
92+
{this.props.order.map(({ name, width, visible }, j) => {
93+
if (!visible) return null;
9394
let type = this.props.columns[name].type;
9495
let attr = obj;
9596
if (!this.props.isUnique) {
@@ -147,22 +148,23 @@ export default class BrowserTable extends React.Component {
147148
}
148149
}
149150

150-
let headers = this.props.order.map(({ name, width }) => (
151+
let headers = this.props.order.map(({ name, width, visible }) => (
151152
{
152153
width: width,
153154
name: name,
154155
type: this.props.columns[name].type,
155156
targetClass: this.props.columns[name].targetClass,
156-
order: ordering.col === name ? ordering.direction : null
157+
order: ordering.col === name ? ordering.direction : null,
158+
visible
157159
}
158160
));
159161
let editor = null;
160162
let table = <div ref='table' />;
161163
if (this.props.data) {
162-
let rowWidth = 210;
163-
for (let i = 0; i < this.props.order.length; i++) {
164-
rowWidth += this.props.order[i].width;
165-
}
164+
const rowWidth = this.props.order.reduce(
165+
(rowWidth, { visible, width }) => visible ? rowWidth + width : rowWidth,
166+
210
167+
);
166168
let newRow = null;
167169
if (this.props.newObject && this.state.offset <= 0) {
168170
newRow = (

src/dashboard/Data/Browser/BrowserToolbar.react.js

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88
import BrowserFilter from 'components/BrowserFilter/BrowserFilter.react';
99
import BrowserMenu from 'components/BrowserMenu/BrowserMenu.react';
10+
import ColumnsConfiguration
11+
from 'components/ColumnsConfiguration/ColumnsConfiguration.react';
1012
import Icon from 'components/Icon/Icon.react';
1113
import MenuItem from 'components/BrowserMenu/MenuItem.react';
1214
import prettyNumber from 'lib/prettyNumber';
@@ -42,6 +44,9 @@ let BrowserToolbar = ({
4244
onRefresh,
4345
hidePerms,
4446
isUnique,
47+
handleColumnDragDrop,
48+
handleColumnsOrder,
49+
order,
4550

4651
enableDeleteAllRows,
4752
enableExportClass,
@@ -150,6 +155,11 @@ let BrowserToolbar = ({
150155
<span>Add Row</span>
151156
</a>
152157
<div className={styles.toolbarSeparator} />
158+
<ColumnsConfiguration
159+
handleColumnsOrder={handleColumnsOrder}
160+
handleColumnDragDrop={handleColumnDragDrop}
161+
order={order} />
162+
<div className={styles.toolbarSeparator} />
153163
<a className={styles.toolbarButton} onClick={onRefresh}>
154164
<Icon name='refresh-solid' width={14} height={14} />
155165
<span>Refresh</span>

src/dashboard/Data/Browser/DataBrowser.react.js

+9
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ export default class DataBrowser extends React.Component {
188188
}
189189
}
190190

191+
handleColumnsOrder(order) {
192+
this.setState({ order }, () => {
193+
this.updatePreferences(order);
194+
});
195+
}
196+
191197
render() {
192198
let { className, ...other } = this.props;
193199
const { preventSchemaEdits } = this.context.currentApp;
@@ -213,6 +219,9 @@ export default class DataBrowser extends React.Component {
213219
enableSecurityDialog={this.context.currentApp.serverInfo.features.schemas.editClassLevelPermissions && !preventSchemaEdits}
214220
enableColumnManipulation={!preventSchemaEdits}
215221
enableClassManipulation={!preventSchemaEdits}
222+
handleColumnDragDrop={this.handleHeaderDragDrop.bind(this)}
223+
handleColumnsOrder={this.handleColumnsOrder.bind(this)}
224+
order={this.state.order}
216225
{...other}/>
217226
</div>
218227
);

src/icons/drag-indicator.svg

+3
Loading

src/icons/manage-columns.svg

+3
Loading

src/icons/visibility.svg

+3
Loading

src/icons/visibility_off.svg

+3
Loading

0 commit comments

Comments
 (0)