diff --git a/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js b/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js
new file mode 100644
index 0000000000..a681b5d0f6
--- /dev/null
+++ b/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js
@@ -0,0 +1,43 @@
+import React from 'react';
+import { useDrag, useDrop } from 'react-dnd';
+
+import Icon from 'components/Icon/Icon.react';
+import styles from 'components/ColumnsConfiguration/ColumnConfigurationItem.scss';
+
+const DND_TYPE = 'ColumnConfigurationItem';
+
+export default ({ name, handleColumnDragDrop, index, onChangeVisible, visible }) => {
+ const [ { isDragging}, drag ] = useDrag({
+ item: { type: DND_TYPE, index },
+ collect: monitor => ({ isDragging: !!monitor.isDragging() })
+ });
+
+ const [ { canDrop, isOver }, drop ] = useDrop({
+ accept: DND_TYPE,
+ drop: item => handleColumnDragDrop(item.index, index),
+ canDrop: item => item.index !== index,
+ collect: monitor => ({
+ isOver: !!monitor.isOver(),
+ canDrop: !!monitor.canDrop()
+ })
+ });
+
+ return drag(drop(
+ onChangeVisible(!visible)}>
+
+
+
+ {name}
+
+
+
+
+ ));
+};
\ No newline at end of file
diff --git a/src/components/ColumnsConfiguration/ColumnConfigurationItem.scss b/src/components/ColumnsConfiguration/ColumnConfigurationItem.scss
new file mode 100644
index 0000000000..68400d4ae6
--- /dev/null
+++ b/src/components/ColumnsConfiguration/ColumnConfigurationItem.scss
@@ -0,0 +1,25 @@
+.columnConfigItem {
+ padding: 8px 10px;
+ display: flex;
+ justify-content: space-between;
+ border-radius: 5px;
+ cursor: grab;
+}
+
+.icon {
+ display: flex;
+ align-items: center;
+ height: 24px;
+}
+
+.visibilityIcon {
+ cursor: pointer;
+ width: 30px;
+}
+
+.columnConfigItemName {
+ width: 110px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ line-height: 24px;
+}
\ No newline at end of file
diff --git a/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js b/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js
new file mode 100644
index 0000000000..b56f1466b8
--- /dev/null
+++ b/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import { DndProvider } from 'react-dnd'
+import HTML5Backend from 'react-dnd-html5-backend'
+import ReactDOM from 'react-dom';
+
+import Button from 'components/Button/Button.react';
+import ColumnConfigurationItem from 'components/ColumnsConfiguration/ColumnConfigurationItem.react';
+import styles from 'components/ColumnsConfiguration/ColumnsConfiguration.scss';
+import Icon from 'components/Icon/Icon.react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+
+export default class ColumnsConfiguration extends React.Component {
+ constructor() {
+ super();
+
+ this.state = {
+ open: false
+ };
+ }
+
+ componentDidMount() {
+ this.node = ReactDOM.findDOMNode(this);
+ }
+
+ componentWillReceiveProps(props) {
+ if (props.schema !== this.props.schema) {
+ this.setState({
+ open: false
+ });
+ }
+ }
+
+ toggle() {
+ this.setState({
+ open: !this.state.open
+ })
+ }
+
+ showAll() {
+ this.props.handleColumnsOrder(this.props.order.map(order => ({ ...order, visible: true })));
+ }
+
+ hideAll() {
+ this.props.handleColumnsOrder(this.props.order.map(order => ({ ...order, visible: false })));
+ }
+
+ render() {
+ const { handleColumnDragDrop, handleColumnsOrder, order } = this.props;
+ const [ title, entry ] = [styles.title, styles.entry ].map(className => (
+
+
+ Manage Columns
+
+ ));
+
+ let popover = null;
+ if (this.state.open) {
+ popover = (
+
+
+ {title}
+
+
+
+ {order.map(({ name, visible, ...rest }, index) => {
+ return {
+ const updatedOrder = [...order];
+ updatedOrder[index] = {
+ ...rest,
+ name,
+ visible
+ };
+ handleColumnsOrder(updatedOrder);
+ }}
+ handleColumnDragDrop={handleColumnDragDrop} />
+ })}
+
+
+
+
+
+
+
+
+
+ );
+ }
+ return (
+ <>
+ {entry}
+ {popover}
+ >
+ );
+ }
+}
diff --git a/src/components/ColumnsConfiguration/ColumnsConfiguration.scss b/src/components/ColumnsConfiguration/ColumnsConfiguration.scss
new file mode 100644
index 0000000000..046621f7cf
--- /dev/null
+++ b/src/components/ColumnsConfiguration/ColumnsConfiguration.scss
@@ -0,0 +1,75 @@
+@import 'stylesheets/globals.scss';
+
+.entry {
+ display: inline-block;
+ height: 14px;
+ padding: 0 8px;
+
+ svg {
+ fill: #66637A;
+ }
+
+ &:hover svg {
+ fill: white;
+ }
+}
+
+.title {
+ margin-top: -4px;
+ background: #797691;
+ padding: 4px 8px;
+ border-radius: 5px 5px 0 0;
+
+ svg {
+ fill: white;
+ }
+}
+
+.entry, .title {
+ @include NotoSansFont;
+ font-size: 14px;
+ color: #ffffff;
+ cursor: pointer;
+
+ svg {
+ vertical-align: middle;
+ margin-right: 4px;
+ }
+
+ span {
+ vertical-align: middle;
+ line-height: 14px;
+ }
+}
+
+.body {
+ color: white;
+ position: absolute;
+ top: 22px;
+ right: 0;
+ border-radius: 5px 0 5px 5px;
+ background: #797691;
+ width: 220px;
+ font-size: 14px;
+
+ .columnConfigContainer {
+ max-height: calc(100vh - 180px);
+ overflow: auto;
+ margin: 10px;
+ }
+}
+
+.footer {
+ background: rgba(0,0,0,0.2);
+ padding: 15px 20px;
+ display: flex;
+ justify-content: space-between;
+
+ > a {
+ margin-right: 10px;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
index e63a9a208e..ed0b411696 100644
--- a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
+++ b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
@@ -27,7 +27,8 @@ export default class DataBrowserHeaderBar extends React.Component {
];
- headers.forEach(({ width, name, type, targetClass, order }, i) => {
+ headers.forEach(({ width, name, type, targetClass, order, visible }, i) => {
+ if (!visible) return;
let wrapStyle = { width };
if (i % 2) {
wrapStyle.background = '#726F85';
diff --git a/src/components/Popover/Popover.react.js b/src/components/Popover/Popover.react.js
index 1e8f01b5ee..b5a8821345 100644
--- a/src/components/Popover/Popover.react.js
+++ b/src/components/Popover/Popover.react.js
@@ -90,7 +90,7 @@ export default class Popover extends React.Component {
}
render() {
- return ;
+ return null;
}
}
diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js
index 21d70ade19..7c60289361 100644
--- a/src/dashboard/Data/Browser/BrowserTable.react.js
+++ b/src/dashboard/Data/Browser/BrowserTable.react.js
@@ -89,7 +89,8 @@ export default class BrowserTable extends React.Component {
checked={this.props.selection['*'] || this.props.selection[obj.id]}
onChange={(e) => this.props.selectRow(obj.id, e.target.checked)} />
- {this.props.order.map(({ name, width }, j) => {
+ {this.props.order.map(({ name, width, visible }, j) => {
+ if (!visible) return null;
let type = this.props.columns[name].type;
let attr = obj;
if (!this.props.isUnique) {
@@ -147,22 +148,23 @@ export default class BrowserTable extends React.Component {
}
}
- let headers = this.props.order.map(({ name, width }) => (
+ let headers = this.props.order.map(({ name, width, visible }) => (
{
width: width,
name: name,
type: this.props.columns[name].type,
targetClass: this.props.columns[name].targetClass,
- order: ordering.col === name ? ordering.direction : null
+ order: ordering.col === name ? ordering.direction : null,
+ visible
}
));
let editor = null;
let table = ;
if (this.props.data) {
- let rowWidth = 210;
- for (let i = 0; i < this.props.order.length; i++) {
- rowWidth += this.props.order[i].width;
- }
+ const rowWidth = this.props.order.reduce(
+ (rowWidth, { visible, width }) => visible ? rowWidth + width : rowWidth,
+ 210
+ );
let newRow = null;
if (this.props.newObject && this.state.offset <= 0) {
newRow = (
diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js
index 67fd5cc4b9..331a9f4d67 100644
--- a/src/dashboard/Data/Browser/BrowserToolbar.react.js
+++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js
@@ -7,6 +7,8 @@
*/
import BrowserFilter from 'components/BrowserFilter/BrowserFilter.react';
import BrowserMenu from 'components/BrowserMenu/BrowserMenu.react';
+import ColumnsConfiguration
+ from 'components/ColumnsConfiguration/ColumnsConfiguration.react';
import Icon from 'components/Icon/Icon.react';
import MenuItem from 'components/BrowserMenu/MenuItem.react';
import prettyNumber from 'lib/prettyNumber';
@@ -42,6 +44,9 @@ let BrowserToolbar = ({
onRefresh,
hidePerms,
isUnique,
+ handleColumnDragDrop,
+ handleColumnsOrder,
+ order,
enableDeleteAllRows,
enableExportClass,
@@ -150,6 +155,11 @@ let BrowserToolbar = ({
Add Row
+
+
Refresh
diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js
index bed0253267..c9bacb32a5 100644
--- a/src/dashboard/Data/Browser/DataBrowser.react.js
+++ b/src/dashboard/Data/Browser/DataBrowser.react.js
@@ -188,6 +188,12 @@ export default class DataBrowser extends React.Component {
}
}
+ handleColumnsOrder(order) {
+ this.setState({ order }, () => {
+ this.updatePreferences(order);
+ });
+ }
+
render() {
let { className, ...other } = this.props;
const { preventSchemaEdits } = this.context.currentApp;
@@ -213,6 +219,9 @@ export default class DataBrowser extends React.Component {
enableSecurityDialog={this.context.currentApp.serverInfo.features.schemas.editClassLevelPermissions && !preventSchemaEdits}
enableColumnManipulation={!preventSchemaEdits}
enableClassManipulation={!preventSchemaEdits}
+ handleColumnDragDrop={this.handleHeaderDragDrop.bind(this)}
+ handleColumnsOrder={this.handleColumnsOrder.bind(this)}
+ order={this.state.order}
{...other}/>
);
diff --git a/src/icons/drag-indicator.svg b/src/icons/drag-indicator.svg
new file mode 100644
index 0000000000..cdfd8a21b1
--- /dev/null
+++ b/src/icons/drag-indicator.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/icons/manage-columns.svg b/src/icons/manage-columns.svg
new file mode 100644
index 0000000000..a11664dd60
--- /dev/null
+++ b/src/icons/manage-columns.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/icons/visibility.svg b/src/icons/visibility.svg
new file mode 100644
index 0000000000..59f51a67ac
--- /dev/null
+++ b/src/icons/visibility.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/icons/visibility_off.svg b/src/icons/visibility_off.svg
new file mode 100644
index 0000000000..892a3a0dfb
--- /dev/null
+++ b/src/icons/visibility_off.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/lib/ColumnPreferences.js b/src/lib/ColumnPreferences.js
index da5fbd2de6..e43768a620 100644
--- a/src/lib/ColumnPreferences.js
+++ b/src/lib/ColumnPreferences.js
@@ -72,7 +72,7 @@ export function getColumnSort(sortBy, appId, className) {
}
export function getOrder(cols, appId, className) {
- let prefs = getPreferences(appId, className) || [ { name: 'objectId', width: DEFAULT_WIDTH } ];
+ let prefs = getPreferences(appId, className) || [ { name: 'objectId', width: DEFAULT_WIDTH, visible: true } ];
let order = [].concat(prefs);
let seen = {};
for (let i = 0; i < order.length; i++) {
@@ -83,14 +83,21 @@ export function getOrder(cols, appId, className) {
for (let name in cols) {
requested[name] = true;
if (!seen[name]) {
- order.push({ name: name, width: DEFAULT_WIDTH });
+ order.push({ name: name, width: DEFAULT_WIDTH, visible: true });
seen[name] = true;
updated = true;
}
}
let filtered = [];
for (let i = 0; i < order.length; i++) {
- let name = order[i].name;
+ const { name, visible } = order[i];
+
+ // If "visible" attribute is not defined, sets to true
+ // and updates the cached preferences.
+ if (typeof visible === 'undefined') {
+ order[i].visible = true;
+ updated = true;
+ }
if (requested[name]) {
filtered.push(order[i]);
} else {
diff --git a/src/lib/tests/ColumnPreferences.test.js b/src/lib/tests/ColumnPreferences.test.js
index 749e077e2c..626433358a 100644
--- a/src/lib/tests/ColumnPreferences.test.js
+++ b/src/lib/tests/ColumnPreferences.test.js
@@ -43,17 +43,17 @@ describe('ColumnPreferences', () => {
it('can retrive column orderings', () => {
ColumnPreferences.updatePreferences([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }], 'testapp', 'Klass');
expect(ColumnPreferences.getOrder({ objectId: {}, createdAt: {} }, 'testapp', 'Klass')).toEqual(
- [{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }]
+ [{ name: 'objectId', width: 100, visible: true }, { name: 'createdAt', width: 150, visible: true }]
);
});
it('tacks unknown columns onto the end', () => {
ColumnPreferences.updatePreferences([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }], 'testapp', 'Klass');
expect(ColumnPreferences.getOrder({ objectId: {}, updatedAt: {}, createdAt: {}, someField: {} }, 'testapp', 'Klass')).toEqual(
- [{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }, { name: 'updatedAt', width: 150 }, { name: 'someField', width: 150 }]
+ [{ name: 'objectId', width: 100, visible: true }, { name: 'createdAt', width: 150, visible: true }, { name: 'updatedAt', width: 150, visible: true }, { name: 'someField', width: 150, visible: true }]
);
expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual(
- [{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }, { name: 'updatedAt', width: 150 }, { name: 'someField', width: 150 }]
+ [{ name: 'objectId', width: 100, visible: true }, { name: 'createdAt', width: 150, visible: true }, { name: 'updatedAt', width: 150, visible: true }, { name: 'someField', width: 150, visible: true }]
);
});
@@ -64,17 +64,17 @@ describe('ColumnPreferences', () => {
'Klass'
);
expect(ColumnPreferences.getOrder({ objectId: {}, createdAt: {}, updatedAt: {} }, 'testapp', 'Klass')).toEqual(
- [{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }, { name: 'updatedAt', width: 150 }]
+ [{ name: 'objectId', width: 100, visible: true }, { name: 'createdAt', width: 150, visible: true }, { name: 'updatedAt', width: 150, visible: true }]
);
expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual(
- [{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }, { name: 'updatedAt', width: 150 }]
+ [{ name: 'objectId', width: 100, visible: true }, { name: 'createdAt', width: 150, visible: true }, { name: 'updatedAt', width: 150, visible: true }]
);
expect(ColumnPreferences.getOrder({ objectId: {}, updatedAt: {}, someField: {} }, 'testapp', 'Klass')).toEqual(
- [{ name: 'objectId', width: 100 }, { name: 'updatedAt', width: 150 }, { name: 'someField', width: 150 }]
+ [{ name: 'objectId', width: 100, visible: true }, { name: 'updatedAt', width: 150, visible: true }, { name: 'someField', width: 150, visible: true }]
);
expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual(
- [{ name: 'objectId', width: 100 }, { name: 'updatedAt', width: 150 }, { name: 'someField', width: 150 }]
+ [{ name: 'objectId', width: 100, visible: true }, { name: 'updatedAt', width: 150, visible: true }, { name: 'someField', width: 150, visible: true }]
);
});
});