Skip to content

Commit

Permalink
#7061 DataGrid resize feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Mariusz Jurowicz committed Apr 19, 2018
1 parent cbb1713 commit baf1d8b
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 6 deletions.
168 changes: 165 additions & 3 deletions js/notebook/src/tableDisplay/dataGrid/DataGridResize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,29 @@
*/

import {BeakerXDataGrid} from "./BeakerXDataGrid";
import { IMessageHandler, Message, MessageLoop } from '@phosphor/messaging';
import { MessageLoop } from '@phosphor/messaging';
import {Widget} from "@phosphor/widgets";
import {DataModel} from "@phosphor/datagrid";
import {selectDataFontSize, selectHeaderFontSize, selectHeadersVertical} from "./model/selectors";
import {
DEFAULT_GRID_BORDER_WIDTH, DEFAULT_GRID_PADDING,
DEFAULT_ROW_HEIGHT, MIN_COLUMN_WIDTH
} from "./style/dataGridStyle";
import DataGridColumn from "./column/DataGridColumn";
import {selectColumnWidth} from "./column/selectors";
import {DataModel} from "@phosphor/datagrid";
import {COLUMN_TYPES} from "./column/enums";
import ColumnRegion = DataModel.ColumnRegion;
import {DataGridHelpers} from "./dataGridHelpers";
import {selectColumnWidth} from "./column/selectors";
import getStringSize = DataGridHelpers.getStringSize;

const DEFAULT_RESIZE_RECT_COLOR = 'rgba(57, 169, 237, 0.5)';
const DEFAULT_RESIZE_SECTION_SIZE_IN_PX = 6;

export class DataGridResize {
dataGrid: BeakerXDataGrid;
resizeRect: Widget;
resizeStartRect: { width: number, height: number, x: number, y: number };
resizeMode: 'h'|'v'|'both'|null;

constructor(dataGrid: BeakerXDataGrid) {
this.dataGrid = dataGrid;
Expand All @@ -39,7 +46,10 @@ export class DataGridResize {
this.updateColumnWidth = this.updateColumnWidth.bind(this);
this.setInitialSectionWidth = this.setInitialSectionWidth.bind(this);
this.resizeSectionWidth = this.resizeSectionWidth.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);

this.setResizeRect();
this.installMessageHook();
}

Expand Down Expand Up @@ -92,6 +102,16 @@ export class DataGridResize {
this.setSectionWidth(area, column, this.getSectionWidth(column));
}

fillEmptyDataGridSpace() {
const space = this.dataGrid.node.clientWidth - this.dataGrid.totalWidth - 2 * DEFAULT_GRID_PADDING - this.dataGrid['_vScrollBar'].node.clientWidth;
const position = this.dataGrid.columnSections.sectionCount - 1;
const column = this.dataGrid.columnManager.getColumnByPosition({ value: position, region: 'body' });
const value = this.dataGrid.columnSections.sectionSize(position) + space;
const minValue = this.getSectionWidth(column);

this.dataGrid.resizeSection('column', position, value < minValue ? minValue : value);
}

updateColumnWidth(region: ColumnRegion): Function {
return ({ index, size }) => {
let columnOnPosition = this.dataGrid.columnManager.getColumnByPosition({ region, value: index });
Expand All @@ -100,6 +120,148 @@ export class DataGridResize {
}
}

attachResizeRect(event: MouseEvent) {
if (!this.dataGrid.node.parentElement) {
return;
}

const width = this.dataGrid.viewport.node.clientWidth + this.dataGrid['_vScrollBar'].node.clientWidth + 3;
const height = this.dataGrid.viewport.node.clientHeight + this.dataGrid['_hScrollBar'].node.clientHeight + 3;

this.resizeRect.node.style.height = `${height}px`;
this.resizeRect.node.style.width = `${width}px`;
this.resizeStartRect = { width, height, x: event.clientX, y: event.clientY };

if (!this.resizeRect.isAttached) {
Widget.attach(this.resizeRect, this.dataGrid.node.parentElement);
}

this.resizeRect.node.addEventListener('mousedown', this.captureEvent, true);
this.resizeRect.node.addEventListener('mousemove', this.handleMouseMove, true);
this.dataGrid.node.parentElement.addEventListener('mouseup', this.handleMouseUp, true);
this.dataGrid.node.parentElement.addEventListener('mousemove', this.handleMouseMove, true);
}

detachResizeRect() {
this.resizeRect.isAttached && Widget.detach(this.resizeRect);

this.resizeMode = null;
this.dataGrid.node.parentElement.removeEventListener('mousemove', this.handleMouseMove, true);
this.dataGrid.node.parentElement.addEventListener('mouseup', this.handleMouseUp, true);

this.resizeRect.node.style.cursor = 'auto';
this.dataGrid.node.style.cursor = 'auto';
this.dataGrid.node.parentElement.style.cursor = 'auto';
this.dataGrid['_canvas'].style.cursor = 'auto';
}

isResizing() {
return this.resizeRect.isAttached;
}

shouldResizeDataGrid(event: MouseEvent): boolean {
const { horizontal, vertical } = this.getDataGridResizeConfig(event);

return vertical || horizontal;
}

setResizeMode(event: MouseEvent): void {
const { horizontal, vertical } = this.getDataGridResizeConfig(event);

if (!horizontal && !vertical) {
this.setCursorStyle('auto');

return;
}

if (vertical && horizontal) {
this.resizeMode = 'both';
this.setCursorStyle('nwse-resize');

return;
}

this.resizeMode = vertical ? 'v' : 'h';
this.setCursorStyle(vertical ? 'ns-resize' : 'ew-resize');
}

setCursorStyle(cursor: 'auto'|'ew-resize'|'ns-resize'|'nwse-resize') {
if (!this.dataGrid.node.parentElement) {
return;
}

this.dataGrid.node.parentElement.classList.remove('cursor-ns-resize');
this.dataGrid.node.parentElement.classList.remove('cursor-ew-resize');
this.dataGrid.node.parentElement.classList.remove('cursor-nwse-resize');

if (cursor !== 'auto') {
this.dataGrid.node.parentElement.classList.add(`cursor-${cursor}`);
}
}

private getDataGridResizeConfig(event: MouseEvent): { vertical: boolean, horizontal: boolean } {
const viewportRect = this.dataGrid.viewport.node.getBoundingClientRect();
const verticalOffset = event.clientY - viewportRect.bottom - this.dataGrid['_hScrollBar'].node.clientHeight;
const horizontalOffset = event.clientX - viewportRect.right - this.dataGrid['_vScrollBar'].node.clientWidth;
const vertical = verticalOffset >= 0 && verticalOffset <= DEFAULT_RESIZE_SECTION_SIZE_IN_PX;
const horizontal = horizontalOffset >= 0 && horizontalOffset <= DEFAULT_RESIZE_SECTION_SIZE_IN_PX;

return { vertical, horizontal };
}

private setResizeRect() {
this.resizeRect = new Widget();
this.resizeRect.node.style.position = 'absolute';
this.resizeRect.node.style.background = DEFAULT_RESIZE_RECT_COLOR;
this.resizeRect.node.style.top = `${DEFAULT_GRID_PADDING + 1}px`;
this.resizeRect.node.style.left = `${DEFAULT_GRID_PADDING + 1}px`;
}

private handleMouseMove(event: MouseEvent) {
if (event.buttons !== 1) {
return;
}

this.captureEvent(event);

if (this.resizeMode === 'both' || this.resizeMode === 'h') {
this.resizeRect.node.style.width = `${this.resizeStartRect.width + event.clientX - this.resizeStartRect.x}px`;
}

if (this.resizeMode === 'both' || this.resizeMode === 'v') {
this.resizeRect.node.style.height = `${this.resizeStartRect.height + event.clientY - this.resizeStartRect.y}px`;
}

if (
(this.resizeMode === 'both' || this.resizeMode === 'v')
&& (this.resizeRect.node.clientHeight + 2 * DEFAULT_GRID_PADDING) - this.dataGrid.node.clientHeight > this.dataGrid.baseRowSize / 3
) {
this.dataGrid.rowManager.setRowsToShow(this.dataGrid.rowManager.rowsToShow + 1);
}
}

private handleMouseUp(event: MouseEvent) {
if (!this.isResizing()) {
return;
}

this.captureEvent(event);

const rect = this.resizeRect.node.getBoundingClientRect();

this.dataGrid.rowManager.setRowsToShow(Math.round((rect.height - this.dataGrid.headerHeight) / this.dataGrid.baseRowSize));
this.dataGrid.node.style.width = `${rect.width + 2 * DEFAULT_GRID_PADDING}px`;
this.fillEmptyDataGridSpace();
this.setCursorStyle('auto');

this.detachResizeRect();
}

private captureEvent(event: MouseEvent) {
event.stopImmediatePropagation();
event.preventDefault();
}

private getWidgetHeight(): void {
const bodyRowCount = this.dataGrid.model.rowCount('body');
const rowsToShow = this.dataGrid.rowManager.rowsToShow;
Expand Down
20 changes: 18 additions & 2 deletions js/notebook/src/tableDisplay/dataGrid/event/EventManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export default class EventManager {
}

private handleMouseUp(event: MouseEvent) {
if (this.dataGrid.dataGridResize.isResizing()) {
return this.dataGrid.dataGridResize.detachResizeRect();
}

this.dataGrid.cellSelectionManager.handleMouseUp(event);
this.handleHeaderClick(event);
this.handleBodyClick(event);
Expand All @@ -126,10 +130,22 @@ export default class EventManager {
}

private handleMouseMove(event: MouseEvent): void {
if (this.dataGrid.dataGridResize.isResizing()) {
return;
}

if (event.buttons !== 1) {
this.dataGrid.columnPosition.stopDragging();
}

if (!this.dataGrid.dataGridResize.isResizing()) {
this.dataGrid.dataGridResize.setResizeMode(event);
}

if (this.dataGrid.dataGridResize.isResizing()) {
return;
}

this.dataGrid.columnPosition.moveDraggedHeader(event);
this.handleCellHover(event);
}
Expand All @@ -149,8 +165,8 @@ export default class EventManager {
!this.dataGrid.focused && this.dataGrid.setFocus(true);
this.dataGrid.cellSelectionManager.handleMouseDown(event);

if (!this.isHeaderClicked(event)) {
return;
if (!this.isHeaderClicked(event) && this.dataGrid.dataGridResize.shouldResizeDataGrid(event)) {
return this.dataGrid.dataGridResize.attachResizeRect(event);
}

const data = this.dataGrid.getCellData(event.clientX, event.clientY);
Expand Down
18 changes: 18 additions & 0 deletions js/notebook/src/tableDisplay/dataGrid/style/dataGrid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ $sortDescendingIconWhite: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4
}
}

.cursor-ns-resize {
&, * {
cursor: ns-resize!important;
}
}

.cursor-ew-resize {
&, * {
cursor: ew-resize!important;
}
}

.cursor-nwse-resize {
&, * {
cursor: nwse-resize!important;
}
}

.p-DataGrid-tooltip {
background-color: #ffffff;
box-shadow: 0 0 3px #333333;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import cellConfigMock from "../mock/cellConfigMock";
import { HIGHLIGHTER_TYPE } from "@beakerx/tableDisplay/dataGrid/interface/IHighlighterState";
import createStore from "@beakerx/tableDisplay/dataGrid/store/BeakerXDataStore";

describe.only('UniqueEntriesHighlighter', () => {
describe('UniqueEntriesHighlighter', () => {
const dataStore = createStore({ ...modelStateMock, types: ['double', 'double']});
const dataGrid = new BeakerXDataGrid({}, dataStore);
const column = new DataGridColumn(
Expand Down

0 comments on commit baf1d8b

Please sign in to comment.