Conversation
…o UI-kit add chevron Icon to UI-kit (used in Search Panel)
…ggle functionality
…archPanel components
…ve match index and triggering search on find actions
That functionality comes in handy when we want to use Undo and Replace All functions together: Before that commit we used to perform N operations where N is a number of replaced cells. And history would record them as atomic operations, so pressing Undo would revert only the last replace. setMany enables batch editing and batch reverting, so now we can Replace All and then revert it by using one Undo operation
…in useGridSearch
sergeyteleshev
left a comment
There was a problem hiding this comment.
All in all feature looks awesome! Nice integration with undo/redo btw
Please also consider cases with readonly tables (probably block replace functionality with disabled state) - Session Manager, readonly tables, tables where we cannot edit cells, but can add/delete rows
Also, keep in mind that if we replace 1 item. We want to scroll and focus this item because it may be outside the visible view, and we need to show the user that this value has changed. For "replace all" the view should remain the same (no selection, no scroll to cell)
Some of the issues can be resolved in the next tickets. We need to decide what we can do now and what later
webapp/common-react/@dbeaver/react-data-grid/src/search/GridSearchEngine.ts
Outdated
Show resolved
Hide resolved
webapp/common-react/@dbeaver/react-data-grid/src/search/GridSearchEngine.ts
Outdated
Show resolved
Hide resolved
| for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { | ||
| for (let colIdx = 0; colIdx < columnCount; colIdx++) { | ||
| const cellText = getCellText(rowIdx, colIdx); | ||
| if (searchPattern.test(cellText)) { | ||
| matches.push({ rowIdx, colIdx }); | ||
| if (searchPattern.global) { | ||
| searchPattern.lastIndex = 0; | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
well for big tables this would just crash the app due to performance issues in the worst case scenario. for most cases it would be like a freeze
we need something tricky here. maybe batch search in new thread (service worker)
I suppose not a bad idea to sync with the team how should we handle that so we do not dig into this real hard
also we have react-minisearch lib for searching. maybe it can offer something for us. we need to check the API for ideas
There was a problem hiding this comment.
this simple solution still shows decent results even on huge tables. I would take it to separate ticket for further exploration.
I tried to use Web Worker for that calculations, but the bottleneck here is the getCellText which can't be called somewhere else. Batching, serializing/deserializing, orchestration adds too much overhead and didn't show any significant improvements. Actually, for me it even for worse
There was a problem hiding this comment.
We’ll need to come up with a solution anyway. Users with large datasets will definitely run into issues, but we can address performance problems on large datasets in a separate ticket. The main thing for now is that the new functionality should not affect users who don’t use the search and replace feature
webapp/common-react/@dbeaver/react-data-grid/src/search/GridSearchPanel.tsx
Outdated
Show resolved
Hide resolved
webapp/common-react/@dbeaver/react-data-grid/src/search/GridSearchPanel.tsx
Show resolved
Hide resolved
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx
Outdated
Show resolved
Hide resolved
| @@ -189,13 +199,20 @@ export const DataGridTable = observer<IDataPresentationProps>(function DataGridT | |||
| return; | |||
| } | |||
|
|
|||
| if (dataGridRef.current?.isReplacing()) { | |||
| return; | |||
| } | |||
There was a problem hiding this comment.
I think we should revert that new behavior
As user I want to see all changes focused. For example we have huge table and our change is out of the screen right now. Currently if I replace 1 value I wont see what was replaced cause it was not scrolled or selected this cell. so we can miss something important
webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/Document/DocumentEditAction.ts
Show resolved
Hide resolved
webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/Grid/GridEditAction.ts
Show resolved
Hide resolved
webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataCacheAction.ts
Outdated
Show resolved
Hide resolved
…onditional read-only state
…ting the DataGrid component with the search-related logic
…or cell value updates
…lity in DataGrid
…and setMany methods
…e batch edit references
…udbeaver into 7023-cb-find-and-replace-new
… Cannot access refs during render)
| if (rowsCountRef.current !== rowsCount) { | ||
| const previousRowCount = rowsCountRef.current; | ||
| rowsCountRef.current = rowsCount; | ||
| if (prevRowsCount !== rowsCount) { | ||
| setPrevRowsCount(rowsCount); | ||
|
|
||
| if (previousRowCount === 0) { | ||
| if (prevRowsCount === 0) { | ||
| setColumnWidths(new Map<string, ColumnWidth>()); | ||
| } | ||
| } |
There was a problem hiding this comment.
why do we need this changes? it seems not related to the initial issue
There was a problem hiding this comment.
Cannot access refs during render
There was a problem hiding this comment.
https://react.dev/reference/react/useRef
see pitfall
| abstract getElementState(key: TKey): DatabaseEditChangeType | null; | ||
| abstract get(key: TKey): TValue | undefined; | ||
| abstract set(key: TKey, value: TValue): void; | ||
| abstract setMany(updates: Array<{ key: TKey; value: TValue }>): void; |
There was a problem hiding this comment.
maybe it's better to use Array<[TKey, TValue]>?
There was a problem hiding this comment.
I'm not sure that tuple is better here.
Even if we look at the usage, compare:
updates.push({ key: { row, column }, value });
updates.push([{ row, column }, value ]);
For me the first option is more readable.
And if we look at edit actions payloads they are also shaped like objects:
this.historyManager.recordCellEdit({
updates: [{ key, value, prevValue }],
});
| getElementState: (key: TKey) => DatabaseEditChangeType | null; | ||
| get: (key: TKey) => TValue | undefined; | ||
| set: (key: TKey, value: TValue) => void; | ||
| setMany: (updates: Array<{ key: TKey; value: TValue }>) => void; |
| if (dataGridRef.current?.isReplacing()) { | ||
| return; | ||
| } | ||
|
|
||
| handlers.selectCell({ colIdx, rowIdx }); | ||
| } | ||
|
|
||
| tableData.editor?.action.addHandler(syncEditor); | ||
|
|
||
| function syncFocus(data: DatabaseDataSelectActionsData<Partial<IGridDataKey>>) { | ||
| if (data.type === 'focus') { | ||
| if (dataGridRef.current?.isReplacing()) { | ||
| return; | ||
| } | ||
| // TODO: we need this delay to update focus after render rows update | ||
| setTimeout(() => { |
There was a problem hiding this comment.
i belive this should be handled on the grid component side, if you don't want to change focus or any, you can:
- Block focus change event in the
Gridcomponent - Block focus change method in the
Gridcomponent
| this.compressLastEditedCellHistory(data.key); | ||
| const singleUpdateKey = data.updates.length === 1 ? data.updates[0]!.key : undefined; | ||
| this.compressLastEditedCellHistory(singleUpdateKey); |
There was a problem hiding this comment.
can we use if here and restrict passing undefined to the compressLastEditedCellHistory because it make no sense
| if (rowsCountRef.current !== rowsCount) { | ||
| const previousRowCount = rowsCountRef.current; | ||
| rowsCountRef.current = rowsCount; | ||
| if (prevRowsCount !== rowsCount) { | ||
| setPrevRowsCount(rowsCount); | ||
|
|
||
| if (previousRowCount === 0) { | ||
| if (prevRowsCount === 0) { | ||
| setColumnWidths(new Map<string, ColumnWidth>()); | ||
| } | ||
| } |
There was a problem hiding this comment.
https://react.dev/reference/react/useRef
see pitfall
…andle Replaced onCellClassNameChange prop + useEffect with useImperativeHandle as suggested in code review. Using useState setter directly as callback ref either caused infinite re-renders (without deps) or lost context synchronization entirely (with stable deps). Settled on useCallback ref that sets both useRef (for method access) and useState (for context propagation).
…id component Previously DataGridTable checked isReplacing() before syncing focus/selection, leaking grid internals Now the grid's selectCell blocks during replacing internally and supports deferred mode (setTimeout + rAF) for post-render focus sync.
…cer and improve state management
moved ref synchronization out of render, unified search+scroll into one shared path, added a SET_MATCHES no-op guard to reduce unnecessary updates, removed duplicate open=false persistence from useDataGridSearch (single owner now in useGridSearch).
…or improved clarity
…al in grid search
…tureView hotkeys
Screen.Recording.2026-01-28.at.15.37.39.mov