Skip to content

Commit 9d0a8b0

Browse files
committed
feat(data-browser): add column freeze option
1 parent 02b3153 commit 9d0a8b0

File tree

5 files changed

+129
-27
lines changed

5 files changed

+129
-27
lines changed

src/components/BrowserCell/BrowserCell.react.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,11 +632,19 @@ export default class BrowserCell extends Component {
632632
classes.push(styles.selected);
633633
}
634634

635+
const style = { width };
636+
if (this.props.stickyLeft !== undefined) {
637+
style.position = 'sticky';
638+
style.left = this.props.stickyLeft;
639+
style.zIndex = 1;
640+
style.background = this.props.rowBackground;
641+
}
642+
635643
return (
636644
<span
637645
ref={this.cellRef}
638646
className={classes.join(' ')}
639-
style={{ width }}
647+
style={style}
640648
onClick={e => {
641649
if (e.metaKey === true && type === 'Pointer') {
642650
onPointerCmdClick(value);

src/components/BrowserRow/BrowserRow.react.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export default class BrowserRow extends Component {
5151
onMouseUpRowCheckBox,
5252
onMouseOverRowCheckBox,
5353
onMouseOverRow,
54+
stickyLefts,
55+
freezeIndex,
5456
} = this.props;
5557
const attributes = obj.attributes;
5658
let requiredCols = [];
@@ -69,12 +71,19 @@ export default class BrowserRow extends Component {
6971
} else if (obj.className === '_User' && obj.get('authData') !== undefined) {
7072
requiredCols = ['authData'];
7173
}
74+
const rowBackground = row % 2 ? '#F4F5F7' : '#FFFFFF';
75+
const rowStyle = { minWidth: rowWidth };
7276
return (
73-
<div className={styles.tableRow} style={{ minWidth: rowWidth }} onMouseOver={() => onMouseOverRow(obj.id)}>
77+
<div className={styles.tableRow} style={rowStyle} onMouseOver={() => onMouseOverRow(obj.id)}>
7478
<span
7579
className={styles.checkCell}
7680
onMouseUp={onMouseUpRowCheckBox}
7781
onMouseOver={() => onMouseOverRowCheckBox(obj.id)}
82+
style={
83+
freezeIndex >= 0
84+
? { position: 'sticky', left: 0, zIndex: 1, background: rowBackground }
85+
: {}
86+
}
7887
>
7988
<input
8089
type="checkbox"
@@ -133,6 +142,8 @@ export default class BrowserRow extends Component {
133142
type={type}
134143
readonly={isUnique || readOnlyFields.indexOf(name) > -1}
135144
width={width}
145+
stickyLeft={freezeIndex >= j ? stickyLefts[j] : undefined}
146+
rowBackground={rowBackground}
136147
current={currentCol === j}
137148
onSelect={setCurrent}
138149
onEditChange={setEditing}

src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ import styles from 'components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss';
1313
import { DndProvider } from 'react-dnd';
1414

1515
export default class DataBrowserHeaderBar extends React.Component {
16+
handleContextMenu = (index, event) => {
17+
event.preventDefault();
18+
const { freezeIndex, freezeColumns, unfreezeColumns, setContextMenu } = this.props;
19+
const items =
20+
freezeIndex >= 0 && index <= freezeIndex
21+
? [{ text: 'Unfreeze column', callback: () => unfreezeColumns() }]
22+
: [{ text: 'Freeze column', callback: () => freezeColumns(index) }];
23+
setContextMenu(event.pageX, event.pageY, items);
24+
};
25+
1626
render() {
1727
const {
1828
headers,
@@ -26,9 +36,19 @@ export default class DataBrowserHeaderBar extends React.Component {
2636
isDataLoaded,
2737
setSelectedObjectId,
2838
setCurrent,
39+
stickyLefts,
40+
handleLefts,
41+
freezeIndex,
42+
freezeColumns,
43+
unfreezeColumns,
44+
setContextMenu,
2945
} = this.props;
3046
const elements = [
31-
<div key="check" className={[styles.wrap, styles.check].join(' ')}>
47+
<div
48+
key="check"
49+
className={[styles.wrap, styles.check].join(' ')}
50+
style={freezeIndex >= 0 ? { position: 'sticky', left: 0, zIndex: 11 } : {}}
51+
>
3252
{readonly ? null : (
3353
<input type="checkbox" checked={selected} onChange={e => selectAll(e.target.checked)} />
3454
)}
@@ -40,6 +60,11 @@ export default class DataBrowserHeaderBar extends React.Component {
4060
return;
4161
}
4262
const wrapStyle = { width };
63+
if (freezeIndex >= 0 && typeof stickyLefts[i] !== 'undefined' && i <= freezeIndex) {
64+
wrapStyle.position = 'sticky';
65+
wrapStyle.left = stickyLefts[i];
66+
wrapStyle.zIndex = 11;
67+
}
4368
if (i % 2) {
4469
wrapStyle.background = '#726F85';
4570
} else {
@@ -63,7 +88,13 @@ export default class DataBrowserHeaderBar extends React.Component {
6388
}
6489

6590
elements.push(
66-
<div onClick={onClick} key={'header' + i} className={className} style={wrapStyle}>
91+
<div
92+
onClick={onClick}
93+
onContextMenu={e => this.handleContextMenu(i, e)}
94+
key={'header' + i}
95+
className={className}
96+
style={wrapStyle}
97+
>
6798
<DataBrowserHeader
6899
name={name}
69100
type={type}
@@ -74,8 +105,20 @@ export default class DataBrowserHeaderBar extends React.Component {
74105
/>
75106
</div>
76107
);
108+
const handleStyle = {};
109+
if (freezeIndex >= 0 && typeof handleLefts[i] !== 'undefined' && i <= freezeIndex) {
110+
handleStyle.position = 'sticky';
111+
handleStyle.left = handleLefts[i];
112+
handleStyle.zIndex = 11;
113+
handleStyle.background = wrapStyle.background;
114+
}
77115
elements.push(
78-
<DragHandle key={'handle' + i} className={styles.handle} onDrag={onResize.bind(null, i)} />
116+
<DragHandle
117+
key={'handle' + i}
118+
className={styles.handle}
119+
onDrag={onResize.bind(null, i)}
120+
style={handleStyle}
121+
/>
79122
);
80123
});
81124

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

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ export default class BrowserTable extends React.Component {
118118
preventSort,
119119
required,
120120
}));
121+
122+
let stickyLefts = [];
123+
let handleLefts = [];
124+
if (typeof this.props.freezeIndex === 'number' && this.props.freezeIndex >= 0) {
125+
let left = 30;
126+
headers.forEach((h, i) => {
127+
stickyLefts[i] = left;
128+
handleLefts[i] = left + h.width;
129+
if (h.visible) {
130+
left += h.width + 8;
131+
}
132+
});
133+
}
121134
let editor = null;
122135
let table = <div ref={this.tableRef} />;
123136
if (this.props.data) {
@@ -170,6 +183,8 @@ export default class BrowserTable extends React.Component {
170183
callCloudFunction={this.props.callCloudFunction}
171184
isPanelVisible={this.props.isPanelVisible}
172185
setContextMenu={this.props.setContextMenu}
186+
stickyLefts={stickyLefts}
187+
freezeIndex={this.props.freezeIndex}
173188
onEditSelectedRow={this.props.onEditSelectedRow}
174189
markRequiredFieldRow={this.props.markRequiredFieldRow}
175190
showNote={this.props.showNote}
@@ -251,6 +266,8 @@ export default class BrowserTable extends React.Component {
251266
callCloudFunction={this.props.callCloudFunction}
252267
isPanelVisible={this.props.isPanelVisible}
253268
setContextMenu={this.props.setContextMenu}
269+
stickyLefts={stickyLefts}
270+
freezeIndex={this.props.freezeIndex}
254271
onEditSelectedRow={this.props.onEditSelectedRow}
255272
markRequiredFieldRow={this.props.markRequiredFieldRow}
256273
showNote={this.props.showNote}
@@ -342,6 +359,8 @@ export default class BrowserTable extends React.Component {
342359
setSelectedObjectId={this.props.setSelectedObjectId}
343360
isPanelVisible={this.props.isPanelVisible}
344361
setContextMenu={this.props.setContextMenu}
362+
stickyLefts={stickyLefts}
363+
freezeIndex={this.props.freezeIndex}
345364
onEditSelectedRow={this.props.onEditSelectedRow}
346365
showNote={this.props.showNote}
347366
onRefresh={this.props.onRefresh}
@@ -543,27 +562,33 @@ export default class BrowserTable extends React.Component {
543562
'overflow-x': this.props.isResizing ? 'hidden' : 'auto',
544563
}}
545564
>
546-
<DataBrowserHeaderBar
547-
selected={
548-
!!this.props.selection &&
549-
!!this.props.data &&
550-
Object.values(this.props.selection).filter(checked => checked).length ===
551-
this.props.data.length
552-
}
553-
selectAll={checked =>
554-
this.props.data.forEach(({ id }) => this.props.selectRow(id, checked))
555-
}
556-
headers={headers}
557-
updateOrdering={this.props.updateOrdering}
558-
readonly={!!this.props.relation || !!this.props.isUnique}
559-
handleDragDrop={this.props.handleHeaderDragDrop}
560-
onResize={this.props.handleResize}
561-
onAddColumn={this.props.onAddColumn}
562-
preventSchemaEdits={this.context.preventSchemaEdits}
563-
isDataLoaded={!!this.props.data}
564-
setSelectedObjectId={this.props.setSelectedObjectId}
565-
setCurrent={this.props.setCurrent}
566-
/>
565+
<DataBrowserHeaderBar
566+
selected={
567+
!!this.props.selection &&
568+
!!this.props.data &&
569+
Object.values(this.props.selection).filter(checked => checked).length ===
570+
this.props.data.length
571+
}
572+
selectAll={checked =>
573+
this.props.data.forEach(({ id }) => this.props.selectRow(id, checked))
574+
}
575+
headers={headers}
576+
stickyLefts={stickyLefts}
577+
handleLefts={handleLefts}
578+
freezeIndex={this.props.freezeIndex}
579+
freezeColumns={this.props.freezeColumns}
580+
unfreezeColumns={this.props.unfreezeColumns}
581+
updateOrdering={this.props.updateOrdering}
582+
readonly={!!this.props.relation || !!this.props.isUnique}
583+
handleDragDrop={this.props.handleHeaderDragDrop}
584+
onResize={this.props.handleResize}
585+
onAddColumn={this.props.onAddColumn}
586+
preventSchemaEdits={this.context.preventSchemaEdits}
587+
isDataLoaded={!!this.props.data}
588+
setSelectedObjectId={this.props.setSelectedObjectId}
589+
setCurrent={this.props.setCurrent}
590+
setContextMenu={this.props.setContextMenu}
591+
/>
567592
{table}
568593
</div>
569594
);

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export default class DataBrowser extends React.Component {
4949
isResizing: false,
5050
maxWidth: window.innerWidth - 300,
5151
showAggregatedData: true,
52+
frozenColumnIndex: -1,
5253
};
5354

5455
this.handleResizeDiv = this.handleResizeDiv.bind(this);
@@ -66,6 +67,8 @@ export default class DataBrowser extends React.Component {
6667
this.setCopyableValue = this.setCopyableValue.bind(this);
6768
this.setSelectedObjectId = this.setSelectedObjectId.bind(this);
6869
this.setContextMenu = this.setContextMenu.bind(this);
70+
this.freezeColumns = this.freezeColumns.bind(this);
71+
this.unfreezeColumns = this.unfreezeColumns.bind(this);
6972
this.handleCellClick = this.handleCellClick.bind(this);
7073
this.saveOrderTimeout = null;
7174
}
@@ -88,6 +91,7 @@ export default class DataBrowser extends React.Component {
8891
selectedCells: { list: new Set(), rowStart: -1, rowEnd: -1, colStart: -1, colEnd: -1 },
8992
firstSelectedCell: null,
9093
selectedData: [],
94+
frozenColumnIndex: -1,
9195
});
9296
} else if (
9397
Object.keys(props.columns).length !== Object.keys(this.props.columns).length ||
@@ -100,7 +104,7 @@ export default class DataBrowser extends React.Component {
100104
props.className,
101105
columnPreferences[props.className]
102106
);
103-
this.setState({ order });
107+
this.setState({ order, frozenColumnIndex: -1 });
104108
}
105109
if (props && props.className) {
106110
if (
@@ -539,6 +543,14 @@ export default class DataBrowser extends React.Component {
539543
this.setState({ contextMenuX, contextMenuY, contextMenuItems });
540544
}
541545

546+
freezeColumns(index) {
547+
this.setState({ frozenColumnIndex: index });
548+
}
549+
550+
unfreezeColumns() {
551+
this.setState({ frozenColumnIndex: -1 });
552+
}
553+
542554
handleColumnsOrder(order, shouldReload) {
543555
this.setState({ order: [...order] }, () => {
544556
this.updatePreferences(order, shouldReload);
@@ -643,6 +655,9 @@ export default class DataBrowser extends React.Component {
643655
setSelectedObjectId={this.setSelectedObjectId}
644656
callCloudFunction={this.props.callCloudFunction}
645657
setContextMenu={this.setContextMenu}
658+
freezeIndex={this.state.frozenColumnIndex}
659+
freezeColumns={this.freezeColumns}
660+
unfreezeColumns={this.unfreezeColumns}
646661
onFilterChange={this.props.onFilterChange}
647662
onFilterSave={this.props.onFilterSave}
648663
selectedCells={this.state.selectedCells}

0 commit comments

Comments
 (0)