Skip to content

Commit 970a575

Browse files
authored
feat: Goto Value Improvements (#1072)
Fixes #1027 - `null` values are not supported by the API, so it is not implemented
1 parent d852e49 commit 970a575

5 files changed

Lines changed: 172 additions & 18 deletions

File tree

packages/iris-grid/src/GotoRow.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
}
2626

2727
.goto-row-text {
28-
width: 10ch;
28+
min-width: 10ch;
2929
}
3030

3131
.goto-row-close {

packages/iris-grid/src/GotoRow.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ function GotoRow({
8585
const res = 'Row number';
8686

8787
const { rowCount } = model;
88+
89+
const handleGotoValueNumberKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
90+
if (e.key === 'Enter') {
91+
e.stopPropagation();
92+
e.preventDefault();
93+
onGotoValueSubmit();
94+
} else if (
95+
(e.key === 'Backspace' || e.key === 'Delete') &&
96+
(gotoValue === `${Number.POSITIVE_INFINITY}` ||
97+
gotoValue === `${Number.NEGATIVE_INFINITY}`)
98+
) {
99+
onGotoValueInputChanged('');
100+
}
101+
};
102+
88103
const handleGotoValueKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
89104
if (e.key === 'Enter') {
90105
e.stopPropagation();
@@ -124,13 +139,22 @@ function GotoRow({
124139
<div className="goto-row-input">
125140
<input
126141
ref={gotoValueInputRef}
127-
type="number"
128142
className={classNames('form-control', {
129143
'is-invalid': gotoValueError !== '',
130144
})}
131-
onKeyDown={handleGotoValueKeyDown}
145+
onKeyDown={handleGotoValueNumberKeyDown}
132146
placeholder="value"
133-
onChange={e => onGotoValueInputChanged(e.target.value)}
147+
onChange={e => {
148+
const value = e.target.value.toLowerCase();
149+
// regex tests for
150+
if (/^-?[0-9]*\.?[0-9]*$/.test(e.target.value)) {
151+
onGotoValueInputChanged(e.target.value);
152+
} else if (value === '-i' || value === '-infinity') {
153+
onGotoValueInputChanged(`${Number.NEGATIVE_INFINITY}`);
154+
} else if (value === 'i' || value === 'infinity') {
155+
onGotoValueInputChanged(`${Number.POSITIVE_INFINITY}`);
156+
}
157+
}}
134158
value={gotoValue}
135159
/>
136160
</div>
@@ -146,6 +170,7 @@ function GotoRow({
146170
'is-invalid': gotoValueError !== '',
147171
}
148172
)}
173+
defaultValue={gotoValue}
149174
onChange={onGotoValueInputChanged}
150175
/>
151176
</div>
@@ -200,6 +225,7 @@ function GotoRow({
200225
}}
201226
value={gotoValue}
202227
>
228+
<option aria-label="null value" key="null" value="" />
203229
<option key="true" value="true">
204230
true
205231
</option>

packages/iris-grid/src/IrisGrid.tsx

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,10 +2512,32 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
25122512
this.focusRowInGrid(row);
25132513
return;
25142514
}
2515+
2516+
const cursorRow = this.grid?.state.cursorRow;
2517+
const cursorColumn = this.grid?.state.cursorColumn;
2518+
2519+
if (cursorRow == null || cursorColumn == null) {
2520+
// if a cell is not selected / grid is not rendered
2521+
this.setState({
2522+
isGotoShown: !isGotoShown,
2523+
gotoRow: '',
2524+
gotoValue: '',
2525+
gotoRowError: '',
2526+
gotoValueError: '',
2527+
});
2528+
return;
2529+
}
2530+
// if a row is selected
2531+
const { model } = this.props;
2532+
const { name, type } = model.columns[cursorColumn];
2533+
2534+
const cellValue = model.valueForCell(cursorColumn, cursorRow);
2535+
const text = IrisGridUtils.convertValueToText(cellValue, type);
25152536
this.setState({
25162537
isGotoShown: !isGotoShown,
2517-
gotoRow: '',
2518-
gotoValue: '',
2538+
gotoRow: `${cursorRow}`,
2539+
gotoValue: text,
2540+
gotoValueSelectedColumnName: name,
25192541
gotoRowError: '',
25202542
gotoValueError: '',
25212543
});
@@ -3278,19 +3300,16 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
32783300
if (inputString === '') {
32793301
return;
32803302
}
3303+
const selectedColumn = IrisGridUtils.getColumnByName(
3304+
model.columns,
3305+
selectedColumnName
3306+
);
32813307

3282-
const columnIndex = model.getColumnIndexByName(selectedColumnName);
3283-
if (columnIndex === undefined) {
3308+
if (selectedColumn === undefined) {
32843309
return;
32853310
}
32863311

3287-
const selectedColumn = model.columns[columnIndex];
3288-
3289-
let searchFromRow;
3290-
3291-
if (this.grid) {
3292-
({ selectionEndRow: searchFromRow } = this.grid.state);
3293-
}
3312+
let searchFromRow = this.grid?.state.cursorRow;
32943313

32953314
if (searchFromRow == null) {
32963315
searchFromRow = 0;
@@ -3302,11 +3321,13 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
33023321
gotoValueSelectedFilter === FilterType.eqIgnoreCase;
33033322

33043323
try {
3324+
const { formatter } = model;
33053325
const columnDataType = TableUtils.getNormalizedType(selectedColumn.type);
33063326

33073327
let rowIndex;
33083328

33093329
switch (columnDataType) {
3330+
case TableUtils.dataType.CHAR:
33103331
case TableUtils.dataType.STRING: {
33113332
rowIndex = await model.seekRow(
33123333
isBackwards === true ? searchFromRow - 1 : searchFromRow + 1,
@@ -3320,7 +3341,6 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
33203341
break;
33213342
}
33223343
case TableUtils.dataType.DATETIME: {
3323-
const { formatter } = model;
33243344
const [startDate] = DateUtils.parseDateRange(
33253345
inputString,
33263346
formatter.timeZone
@@ -3342,7 +3362,13 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
33423362
!TableUtils.isBigDecimalType(selectedColumn.type) &&
33433363
!TableUtils.isBigIntegerType(selectedColumn.type)
33443364
) {
3345-
const inputValue = parseInt(inputString, 10);
3365+
let inputValue = parseInt(inputString, 10);
3366+
if (inputString === '-Infinity') {
3367+
inputValue = Number.NEGATIVE_INFINITY;
3368+
} else if (inputString === 'Infinity') {
3369+
inputValue = Number.POSITIVE_INFINITY;
3370+
}
3371+
33463372
rowIndex = await model.seekRow(
33473373
searchFromRow,
33483374
selectedColumn,
@@ -3370,7 +3396,11 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
33703396
searchFromRow,
33713397
selectedColumn,
33723398
dh.ValueType.STRING,
3373-
inputString,
3399+
TableUtils.makeValue(
3400+
selectedColumn.type,
3401+
inputString,
3402+
formatter.timeZone
3403+
),
33743404
undefined,
33753405
undefined,
33763406
isBackwards ?? false
@@ -3731,6 +3761,23 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
37313761
}
37323762

37333763
handleGotoValueSelectedColumnNameChanged(columnName: ColumnName): void {
3764+
const { model } = this.props;
3765+
const cursorRow = this.grid?.state.cursorRow;
3766+
3767+
if (cursorRow != null) {
3768+
const index = model.getColumnIndexByName(columnName);
3769+
const column = IrisGridUtils.getColumnByName(model.columns, columnName);
3770+
if (index == null || column == null) {
3771+
return;
3772+
}
3773+
const value = model.valueForCell(index, cursorRow);
3774+
const text = IrisGridUtils.convertValueToText(value, column.type);
3775+
this.setState({
3776+
gotoValueSelectedColumnName: columnName,
3777+
gotoValue: text,
3778+
gotoValueError: '',
3779+
});
3780+
}
37343781
this.setState({
37353782
gotoValueSelectedColumnName: columnName,
37363783
gotoValueError: '',

packages/iris-grid/src/IrisGridUtils.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { GridUtils, GridRange, MoveOperation } from '@deephaven/grid';
22
import dh, { Column, Table, Sort } from '@deephaven/jsapi-shim';
33
import { TypeValue as FilterTypeValue } from '@deephaven/filters';
4+
import { DateUtils } from '@deephaven/jsapi-utils';
45
import type { AdvancedFilter } from './CommonTypes';
56
import { FilterData } from './IrisGrid';
67
import IrisGridTestUtils from './IrisGridTestUtils';
@@ -572,3 +573,51 @@ describe('combineFiltersFromList', () => {
572573
);
573574
});
574575
});
576+
577+
describe('convert string to text', () => {
578+
it('converts null to empty string', () => {
579+
expect(IrisGridUtils.convertValueToText(null, 'string')).toEqual('');
580+
});
581+
it('converts empty string', () => {
582+
expect(IrisGridUtils.convertValueToText('', 'string')).toEqual('');
583+
});
584+
it('converts string to stri', () => {
585+
expect(IrisGridUtils.convertValueToText('test', 'string')).toEqual('test');
586+
});
587+
it('converts length 1 string to stri', () => {
588+
expect(IrisGridUtils.convertValueToText('t', 'string')).toEqual('t');
589+
});
590+
it('converts number to strin', () => {
591+
expect(IrisGridUtils.convertValueToText(65, 'string')).toEqual('65');
592+
});
593+
});
594+
595+
describe('convert char to text', () => {
596+
it('converts number to ascii', () => {
597+
expect(IrisGridUtils.convertValueToText(65, 'char')).toEqual('A');
598+
});
599+
it('converts null to empty char', () => {
600+
expect(IrisGridUtils.convertValueToText(null, 'char')).toEqual('');
601+
});
602+
});
603+
604+
describe('convert other column types to text', () => {
605+
it('converts number to string on number column', () => {
606+
expect(IrisGridUtils.convertValueToText(65, 'number')).toEqual('65');
607+
});
608+
it('converts null to empty string on number column', () => {
609+
expect(IrisGridUtils.convertValueToText(null, 'number')).toEqual('');
610+
});
611+
it('converts time correctly on datetime column', () => {
612+
expect(
613+
IrisGridUtils.convertValueToText(
614+
dh.i18n.DateTimeFormat.parse(
615+
DateUtils.FULL_DATE_FORMAT,
616+
'2022-02-03 02:14:59.000000000',
617+
dh.i18n.TimeZone.getTimeZone('NY')
618+
),
619+
'io.deephaven.time.DateTime'
620+
)
621+
).toEqual('2022-02-03 02:14:59.000');
622+
});
623+
});

packages/iris-grid/src/IrisGridUtils.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ function isValidIndex(x: number, array: readonly unknown[]): boolean {
136136
return x >= 0 && x < array.length;
137137
}
138138

139+
function isDateWrapper(value: unknown): value is DateWrapper {
140+
return (value as DateWrapper).asDate != null;
141+
}
142+
139143
class IrisGridUtils {
140144
/**
141145
* Exports the state from Grid component to a JSON stringifiable object
@@ -1660,6 +1664,34 @@ class IrisGridUtils {
16601664

16611665
return { groups: [...groupMap.values()], maxDepth, groupMap, parentMap };
16621666
}
1667+
1668+
/**
1669+
* @param value The value of the cell in a column
1670+
* @param columnType The type of the column
1671+
* @returns The value of the cell converted to text
1672+
*/
1673+
static convertValueToText(value: unknown, columnType: string): string {
1674+
if (
1675+
columnType != null &&
1676+
TableUtils.isCharType(columnType) &&
1677+
value != null &&
1678+
typeof value === 'number'
1679+
) {
1680+
return String.fromCharCode(value);
1681+
}
1682+
if (TableUtils.isDateType(columnType) && isDateWrapper(value)) {
1683+
const date = new Date(value.asDate());
1684+
const offset = date.getTimezoneOffset();
1685+
const offsetDate = new Date(date.getTime() - offset * 60 * 1000);
1686+
const dateText = offsetDate.toISOString();
1687+
const formattedText = dateText.replace('T', ' ').substring(0, 23);
1688+
return formattedText;
1689+
}
1690+
if (value == null) {
1691+
return '';
1692+
}
1693+
return `${value}`;
1694+
}
16631695
}
16641696

16651697
export default IrisGridUtils;

0 commit comments

Comments
 (0)