import { CompactSelection } from '@glideapps/glide-data-grid'; import { useDebouncedCallback } from '@mantine/hooks'; import React, { useEffect, useRef } from 'react'; import { useGridlerStore } from './GridlerStore'; //The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders. export const Computer = React.memo(() => { const refFirstRun = useRef(0); const refLastSearch = useRef(''); const refLastFilters = useRef(null); const { _glideref, _gridSelectionRows, colFilters, colOrder, colSize, colSort, columns, getRowIndexByKey, getState, loadPage, ready, scrollToRowKey, searchStr, selectedRowKey, setState, setStateFN, values, } = useGridlerStore((s) => ({ _glideref: s._glideref, _gridSelectionRows: s._gridSelectionRows, colFilters: s.colFilters, colOrder: s.colOrder, colSize: s.colSize, colSort: s.colSort, columns: s.columns, getRowIndexByKey: s.getRowIndexByKey, getState: s.getState, loadPage: s.loadPage, ready: s.ready, scrollToRowKey: s.scrollToRowKey, searchStr: s.searchStr, selectedRowKey: s.selectedRowKey, setState: s.setState, setStateFN: s.setStateFN, uniqueid: s.uniqueid, values: s.values, })); const debouncedDoSearch = useDebouncedCallback( (searchStr: string) => { loadPage(0, 'all').then(() => { getState('refreshCells')?.(); getState('_events')?.dispatchEvent?.( new CustomEvent('onSearched', { detail: { search: searchStr }, }) ); }); }, { delay: 300, leading: false, } ); //When values change, update selection useEffect(() => { const searchSelection = async () => { const page_data = getState('_page_data'); const pageSize = getState('pageSize'); const keyField = getState('keyField') ?? 'id'; const rowIndexes = []; for (const vi in values as Array>) { let rowIndex = -1; const key = String( typeof values?.[vi] === 'object' ? values?.[vi]?.[keyField] : typeof values?.[vi] === 'string' ? values?.[vi] : undefined ); for (const p in page_data) { for (const r in page_data[p]) { const idx = Number(p) * pageSize + Number(r); if (String(page_data[p][r]?.[keyField]) === key) { //console.log('Found row S', idx, page_data[p][r], page_data[p][r]?.[keyField], key); rowIndex = idx; break; } } if (rowIndex >= 0) { rowIndexes.push(rowIndex); break; } } if (!(rowIndex >= 0)) { const idx = await getRowIndexByKey(key); if (idx) { rowIndexes.push(idx); } } } return rowIndexes; }; if (values) { searchSelection().then((rowIndexes) => { let rows = CompactSelection.empty(); rowIndexes.forEach((r) => { rows = rows.add(r); }); setStateFN('_gridSelectionRows', () => { return rows; }); setStateFN('_gridSelection', (c) => ({ columns: c?.columns ?? CompactSelection.empty(), ...c, rows, })); }); } }, [values]); //Fire onChange when selection changes useEffect(() => { const onChange = getState('onChange'); if (onChange && typeof onChange === 'function') { const getGridSelectedRows = getState('getGridSelectedRows'); const buffers = getGridSelectedRows(); const _values = getState('values'); if (JSON.stringify(_values) !== JSON.stringify(buffers)) { onChange(buffers); } } }, [JSON.stringify(_gridSelectionRows), getState]); useEffect(() => { setState( 'renderColumns', columns?.map((c) => ({ ...c, hasMenu: c?.hasMenu ?? true, icon: 'sort', })) ); }, [columns]); useEffect(() => { if (searchStr === undefined || searchStr === null) { refLastSearch.current = ''; return; } if (refLastSearch.current !== searchStr) { debouncedDoSearch(searchStr); refLastSearch.current = searchStr; } }, [searchStr]); useEffect(() => { if (!colSort) { return; } setState('_gridSelection', { columns: CompactSelection.empty(), current: undefined, rows: CompactSelection.empty(), }); setState('_gridSelectionRows', CompactSelection.empty()); setStateFN('renderColumns', (cols) => { return cols?.map((c) => ({ ...c, icon: c.id && colSort?.find((col) => col.id === c.id)?.direction ? colSort?.find((col) => col.id === c.id)?.direction === 'asc' ? 'sortup' : 'sortdown' : (c.defaultIcon ?? 'sort'), })); }).then(() => { loadPage(0, 'all').then(() => { getState('refreshCells')?.(); getState('_events')?.dispatchEvent?.( new CustomEvent('onColumnSorted', { detail: { cols: colSort }, }) ); }); }); }, [colSort]); useEffect(() => { if (!colFilters) { return; } if (JSON.stringify(refLastFilters.current) !== JSON.stringify(colFilters)) { loadPage(0, 'all').then(() => { getState('refreshCells')?.(); getState('_events')?.dispatchEvent?.( new CustomEvent('onColumnFiltered', { detail: { filters: colFilters }, }) ); }); refLastFilters.current = colFilters; } }, [colFilters]); useEffect(() => { if (!colSize) { return; } setStateFN('renderColumns', (cols) => { return cols?.map((c) => ({ ...c, width: c.id && colSize?.[c.id] ? colSize?.[c.id] : c.width, })); }).then(() => { getState('refreshCells')?.(); }); }, [colSize]); useEffect(() => { if (!colOrder) { return; } setStateFN('renderColumns', (cols) => { const result = cols?.sort((a, b) => { if (colOrder[a.id] > colOrder[b.id]) { return 1; } return -1; }); return result; }).then(() => { getState('refreshCells')?.(); }); }, [colOrder]); //Initial Load useEffect(() => { if (!_glideref) { return; } if (refFirstRun.current > 0) { return; } refFirstRun.current = 1; loadPage(0).then(() => { getState('refreshCells')?.(); }); }, [ready, loadPage]); //Logic to select first row on mount useEffect(() => { const _events = getState('_events'); const loadPage = () => { const selectFirstRowOnMount = getState('selectFirstRowOnMount'); if (ready && selectFirstRowOnMount) { const scrollToRowKey = getState('scrollToRowKey'); if (scrollToRowKey && scrollToRowKey >= 0) { return; } const keyField = getState('keyField') ?? 'id'; const page_data = getState('_page_data'); const firstBuffer = page_data?.[0]?.[0]; const firstRow = firstBuffer?.[keyField]; const currentValues = getState('values') ?? []; if (firstRow && firstRow > 0 && (currentValues.length ?? 0) === 0) { const values = [firstBuffer, ...(currentValues as Array>)]; const onChange = getState('onChange'); //console.log('Selecting first row:', firstRow, firstBuffer, values); if (onChange) { onChange(values); } else { setState('values', values); } setState('scrollToRowKey', firstRow); } } }; _events?.addEventListener('loadPage', loadPage); return () => { _events?.removeEventListener('loadPage', loadPage); }; }, [ready]); /// logic to apply the selected row. // useEffect(() => { // const ready = getState('ready'); // const ref = getState('_glideref'); // const getRowIndexByKey = getState('getRowIndexByKey'); // if (scrollToRowKey && ref && ready) { // getRowIndexByKey?.(scrollToRowKey).then((r) => { // if (r !== undefined) { // //console.log('Scrolling to selected row:', scrollToRowKey, r); // ref.scrollTo(0, r); // getState('_events').dispatchEvent( // new CustomEvent('scrollToRowKeyFound', { // detail: { rowNumber: r, scrollToRowKey: scrollToRowKey }, // }) // ); // } // }); // } // }, [scrollToRowKey]); useEffect(() => { const ready = getState('ready'); const ref = getState('_glideref'); const getRowIndexByKey = getState('getRowIndexByKey'); const key = selectedRowKey ?? scrollToRowKey; if (key && ref && ready) { getRowIndexByKey?.(key).then((r) => { if (r !== undefined) { //console.log('Scrolling to selected row:', r, selectedRowKey, scrollToRowKey); if (selectedRowKey) { const onChange = getState('onChange'); const selected = [{ [getState('keyField') ?? 'id']: selectedRowKey }]; if (onChange) { onChange(selected); } else { setState('values', selected); } } ref.scrollTo(0, r); getState('_events').dispatchEvent( new CustomEvent('scrollToRowKeyFound', { detail: { rowNumber: r, scrollToRowKey: scrollToRowKey, selectedRowKey: selectedRowKey, }, }) ); } }); } }, [scrollToRowKey, selectedRowKey]); // console.log('Gridler:Debug:Computer', { // colFilters, // colOrder, // colSize, // colSort, // columns, // uniqueid // }); return <>; }); Computer.displayName = 'Gridler-Computer';