378 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			378 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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<unknown>(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<Record<string, unknown>>) {
 | |
|         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<Record<string, unknown>>)];
 | |
| 
 | |
|           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';
 |