A lot of refectoring
This commit is contained in:
@@ -1,71 +1,73 @@
|
||||
import { MantineProvider } from '@mantine/core'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { Gridler } from './Gridler'
|
||||
import { Gridler } from './Gridler';
|
||||
|
||||
// Mock the complex sub-components
|
||||
vi.mock('./GridlerDataGrid', () => ({
|
||||
GridlerDataGrid: () => <div data-testid="gridler-data-grid">Data Grid</div>
|
||||
}))
|
||||
GridlerDataGrid: () => <div data-testid="gridler-data-grid">Data Grid</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../MantineBetterMenu', () => ({
|
||||
MantineBetterMenusProvider: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="menu-provider">{children}</div>
|
||||
)
|
||||
}))
|
||||
),
|
||||
useMantineBetterMenus: () => ({
|
||||
hide: vi.fn(),
|
||||
show: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock the Store Provider
|
||||
vi.mock('./components/Store', () => ({
|
||||
vi.mock('./components/GridlerStore', () => ({
|
||||
Provider: ({ children, uniqueid }: { children: React.ReactNode; uniqueid: string }) => (
|
||||
<div data-testid={`store-provider-${uniqueid}`}>{children}</div>
|
||||
)
|
||||
}))
|
||||
),
|
||||
}));
|
||||
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider>
|
||||
{children}
|
||||
</MantineProvider>
|
||||
)
|
||||
<MantineProvider>{children}</MantineProvider>
|
||||
);
|
||||
|
||||
describe('Gridler', () => {
|
||||
const defaultProps = {
|
||||
cols: [
|
||||
{ key: 'id', title: 'ID' },
|
||||
{ key: 'name', title: 'Name' }
|
||||
{ key: 'name', title: 'Name' },
|
||||
],
|
||||
uniqueid: 'test-grid'
|
||||
}
|
||||
uniqueid: 'test-grid',
|
||||
};
|
||||
|
||||
it('renders without crashing', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<Gridler {...defaultProps} />
|
||||
</TestWrapper>
|
||||
)
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('gridler-data-grid')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByTestId('gridler-data-grid')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('wraps content with MantineBetterMenusProvider', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<Gridler {...defaultProps} />
|
||||
</TestWrapper>
|
||||
)
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('menu-provider')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByTestId('menu-provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('creates store provider with unique ID', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<Gridler {...defaultProps} />
|
||||
</TestWrapper>
|
||||
)
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('store-provider-test-grid')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByTestId('store-provider-test-grid')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders children when provided', () => {
|
||||
render(
|
||||
@@ -74,18 +76,18 @@ describe('Gridler', () => {
|
||||
<div data-testid="test-child">Custom Child</div>
|
||||
</Gridler>
|
||||
</TestWrapper>
|
||||
)
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('test-child')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByTestId('test-child')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles different uniqueid prop', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<Gridler {...defaultProps} uniqueid="different-grid" />
|
||||
</TestWrapper>
|
||||
)
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('store-provider-different-grid')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
expect(screen.getByTestId('store-provider-different-grid')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,13 +2,12 @@ import '@glideapps/glide-data-grid/dist/index.css';
|
||||
import React from 'react';
|
||||
|
||||
import { MantineBetterMenusProvider } from '../MantineBetterMenu';
|
||||
import { APIAdaptorGoLangv2 } from './components/APIAdaptorGoLangv2';
|
||||
import { GlidlerFormInterface } from './components/GridlerFormInterface';
|
||||
import { LocalDataAdaptor } from './components/LocalDataAdaptor';
|
||||
import { type GridlerProps, Provider } from './components/Store';
|
||||
import { GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor';
|
||||
import { GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor';
|
||||
import { type GridlerProps, Provider } from './components/GridlerStore';
|
||||
import { GridlerDataGrid } from './GridlerDataGrid';
|
||||
|
||||
export const Gridler = (props: GridlerProps) => {
|
||||
const Gridler = (props: GridlerProps) => {
|
||||
return (
|
||||
<MantineBetterMenusProvider>
|
||||
<Provider
|
||||
@@ -27,6 +26,8 @@ export const Gridler = (props: GridlerProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
Gridler.GlidlerFormInterface = GlidlerFormInterface;
|
||||
Gridler.APIAdaptorGoLangv2 = APIAdaptorGoLangv2;
|
||||
Gridler.LocalDataAdaptor = LocalDataAdaptor;
|
||||
Gridler.FormAdaptor = GlidlerFormAdaptor;
|
||||
Gridler.LocalDataAdaptor = GlidlerLocalDataAdaptor;
|
||||
|
||||
export { Gridler };
|
||||
export default Gridler;
|
||||
|
||||
@@ -5,21 +5,20 @@ import {
|
||||
type DataEditorRef,
|
||||
type GridColumn,
|
||||
} from '@glideapps/glide-data-grid';
|
||||
import { ActionIcon, Group, Stack } from '@mantine/core';
|
||||
import { useElementSize, useMergedRef } from '@mantine/hooks';
|
||||
import { IconMenu2 } from '@tabler/icons-react';
|
||||
import { Group, Stack } from '@mantine/core';
|
||||
import { useMergedRef } from '@mantine/hooks';
|
||||
import React from 'react';
|
||||
|
||||
import { BottomBar } from './components/BottomBar';
|
||||
import { Computer } from './components/Computer';
|
||||
import { useGridlerStore } from './components/GridlerStore';
|
||||
import { Pager } from './components/Pager';
|
||||
import { RightMenuIcon } from './components/RightMenuIcon';
|
||||
import { SortSprite } from './components/sprites/Sort';
|
||||
import { SortDownSprite } from './components/sprites/SortDown';
|
||||
import { SortUpSprite } from './components/sprites/SortUp';
|
||||
import { useGridlerStore } from './components/Store';
|
||||
import classes from './Gridler.module.css';
|
||||
import { useGridTheme } from './hooks/use-grid-theme';
|
||||
import { RightMenuIcon } from './components/RightMenuIcon';
|
||||
|
||||
export const GridlerDataGrid = () => {
|
||||
const ref = React.useRef<DataEditorRef | null>(null);
|
||||
@@ -34,6 +33,7 @@ export const GridlerDataGrid = () => {
|
||||
glideProps,
|
||||
hasLocalData,
|
||||
headerHeight,
|
||||
heightProp,
|
||||
mounted,
|
||||
onCellEdited,
|
||||
onColumnMoved,
|
||||
@@ -51,7 +51,6 @@ export const GridlerDataGrid = () => {
|
||||
setState,
|
||||
setStateFN,
|
||||
total_rows,
|
||||
heightProp,
|
||||
widthProp,
|
||||
} = useGridlerStore((s) => ({
|
||||
_gridSelection: s._gridSelection,
|
||||
@@ -62,6 +61,7 @@ export const GridlerDataGrid = () => {
|
||||
glideProps: s.glideProps,
|
||||
hasLocalData: s.hasLocalData,
|
||||
headerHeight: s.headerHeight,
|
||||
heightProp: s.height,
|
||||
mounted: s.mounted,
|
||||
onCellEdited: s.onCellEdited,
|
||||
onColumnMoved: s.onColumnMoved,
|
||||
@@ -79,7 +79,6 @@ export const GridlerDataGrid = () => {
|
||||
setState: s.setState,
|
||||
setStateFN: s.setStateFN,
|
||||
total_rows: s.total_rows,
|
||||
heightProp: s.height,
|
||||
widthProp: s.width,
|
||||
}));
|
||||
|
||||
@@ -209,7 +208,7 @@ export const GridlerDataGrid = () => {
|
||||
checkboxStyle: 'square',
|
||||
kind: 'both',
|
||||
}}
|
||||
rows={total_rows}
|
||||
rows={total_rows ?? 0}
|
||||
rowSelect="multi"
|
||||
rowSelectionMode="auto"
|
||||
spanRangeBehavior="default"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useGridlerStore } from './Store';
|
||||
import { useGridlerStore } from './GridlerStore';
|
||||
|
||||
export function BottomBar() {
|
||||
const { _activeTooltip, tooltipBarProps } = useGridlerStore((s) => ({
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { IconX } from '@tabler/icons-react';
|
||||
import { type ReactNode, useEffect, useState } from 'react';
|
||||
|
||||
import type { FilterOption, FilterOptionOperator, GridlerStoreState } from './Store';
|
||||
import type { FilterOption, FilterOptionOperator, GridlerStoreState } from './GridlerStore';
|
||||
|
||||
export type GridCellLoose = {
|
||||
kind: GridCellKind | string;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { CompactSelection } from '@glideapps/glide-data-grid';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { useGridlerStore } from './Store';
|
||||
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(() => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type Item,
|
||||
type Rectangle,
|
||||
} from '@glideapps/glide-data-grid';
|
||||
import { IconGrid4x4 } from '@tabler/icons-react';
|
||||
import { getUUID } from '@warkypublic/artemis-kit';
|
||||
import { getNestedValue } from '@warkypublic/artemis-kit/object';
|
||||
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
||||
@@ -27,12 +28,10 @@ import {
|
||||
type MantineBetterMenuInstanceItem,
|
||||
useMantineBetterMenus,
|
||||
} from '../../MantineBetterMenu';
|
||||
import { type FormRequestType } from '../utils/types';
|
||||
import { ColumnFilterSet, type GridlerColumn, type GridlerColumns } from './Column';
|
||||
import { SortDownSprite } from './sprites/SortDown';
|
||||
import { SortUpSprite } from './sprites/SortUp';
|
||||
import { SpriteImage } from './sprites/SpriteImage';
|
||||
import { IconGrid4x4 } from '@tabler/icons-react';
|
||||
|
||||
export type FilterOption = {
|
||||
datatype?: 'array' | 'boolean' | 'date' | 'function' | 'number' | 'object' | 'string';
|
||||
@@ -99,6 +98,7 @@ export interface GridlerProps extends PropsWithChildren {
|
||||
selectMode?: 'cell' | 'row';
|
||||
showMenu?: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
||||
tooltipBarProps?: React.HTMLAttributes<HTMLDivElement>;
|
||||
total_rows?: number;
|
||||
uniqueid: string;
|
||||
useAPIQuery?: (index: number) => Promise<Array<Record<string, any>>>;
|
||||
values?: Array<Record<string, any>>;
|
||||
@@ -177,7 +177,6 @@ export interface GridlerState {
|
||||
value: (current: GridlerStoreState[K]) => Partial<GridlerStoreState[K]>
|
||||
) => Promise<void>;
|
||||
toCell: <TRowType extends Record<string, string>>(row: any, col: number) => GridCell;
|
||||
total_rows: number;
|
||||
}
|
||||
|
||||
export type GridlerStoreState = GridlerProps & GridlerState;
|
||||
@@ -850,10 +849,10 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
||||
|
||||
return {
|
||||
...props,
|
||||
hasLocalData: props.data && props.data.length > 0,
|
||||
|
||||
hideMenu: props.hideMenu ?? menus.hide,
|
||||
showMenu: props.showMenu ?? menus.show,
|
||||
total_rows: props.data?.length ?? getState('total_rows'),
|
||||
total_rows: props.total_rows ?? getState('total_rows') ?? 0,
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { range } from '../utils/range';
|
||||
import { useGridlerStore } from './Store';
|
||||
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 Pager = React.memo(() => {
|
||||
@@ -14,7 +13,7 @@ export const Pager = React.memo(() => {
|
||||
pageSize,
|
||||
loadPage,
|
||||
_loadingList,
|
||||
hasLocalData
|
||||
hasLocalData,
|
||||
] = useGridlerStore((s) => [
|
||||
s.setState,
|
||||
s._glideref,
|
||||
@@ -24,17 +23,21 @@ export const Pager = React.memo(() => {
|
||||
s.loadPage,
|
||||
|
||||
s._loadingList,
|
||||
s.hasLocalData
|
||||
s.hasLocalData,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!glideref) {return;}
|
||||
if (!glideref) {
|
||||
return;
|
||||
}
|
||||
setState('mounted', true);
|
||||
}, [setState]);
|
||||
|
||||
//Maybe move this into a computer component.
|
||||
useEffect(() => {
|
||||
if (!glideref) {return;}
|
||||
if (!glideref) {
|
||||
return;
|
||||
}
|
||||
if (hasLocalData) {
|
||||
//using local data, no need to load pages
|
||||
return;
|
||||
@@ -56,7 +59,7 @@ export const Pager = React.memo(() => {
|
||||
for (const page of range(firstPage, lastPage + 1, 1)) {
|
||||
loadPage(page);
|
||||
}
|
||||
}, [loadPage, pageSize, visiblePages, glideref, _loadingList,hasLocalData]);
|
||||
}, [loadPage, pageSize, visiblePages, glideref, _loadingList, hasLocalData]);
|
||||
|
||||
return <></>;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ActionIcon } from '@mantine/core';
|
||||
import { IconMenu2 } from '@tabler/icons-react';
|
||||
|
||||
import { useGridlerStore } from './Store';
|
||||
import { useGridlerStore } from './GridlerStore';
|
||||
|
||||
export function RightMenuIcon() {
|
||||
const { loadingData, onContextClick } = useGridlerStore((s) => ({
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import type { APIOptions } from '../utils/types';
|
||||
import type { APIOptions } from '../../utils/types';
|
||||
|
||||
import { useGridlerStore } from './Store';
|
||||
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 APIAdaptorGoLangv2 = React.memo((props: APIOptions) => {
|
||||
export const GlidlerAPIAdaptorForGoLangv2 = React.memo((props: APIOptions) => {
|
||||
const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [
|
||||
s.setStateFN,
|
||||
s.setState,
|
||||
@@ -7,13 +7,13 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import type { MantineBetterMenuInstanceItem } from '../../MantineBetterMenu';
|
||||
import type { FormRequestType } from '../utils/types';
|
||||
import type { GridlerColumn } from './Column';
|
||||
import type { MantineBetterMenuInstanceItem } from '../../../MantineBetterMenu';
|
||||
import type { FormRequestType } from '../../utils/types';
|
||||
import type { GridlerColumn } from '../Column';
|
||||
|
||||
import { type GridlerProps, type GridlerState, useGridlerStore } from './Store';
|
||||
import { type GridlerProps, type GridlerState, useGridlerStore } from '../GridlerStore';
|
||||
|
||||
export function GlidlerFormInterface(props: {
|
||||
export function GlidlerFormAdaptor(props: {
|
||||
descriptionField?: ((data: Record<string, unknown>) => string) | string;
|
||||
getMenuItems?: GridlerProps['getMenuItems'];
|
||||
onReload?: () => void;
|
||||
@@ -1,23 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import type { APIOptions } from '../utils/types';
|
||||
import { useGridlerStore } from '../GridlerStore';
|
||||
|
||||
import { useGridlerStore } from './Store';
|
||||
|
||||
interface LocalDataAdaptorProps {
|
||||
export interface GlidlerLocalDataAdaptorProps {
|
||||
data: Array<unknown>;
|
||||
}
|
||||
|
||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||
export const LocalDataAdaptor = React.memo((props: LocalDataAdaptorProps) => {
|
||||
const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [
|
||||
s.setStateFN,
|
||||
s.setState,
|
||||
s.getState,
|
||||
s.addError,
|
||||
s.mounted,
|
||||
]);
|
||||
export const GlidlerLocalDataAdaptor = React.memo((props: GlidlerLocalDataAdaptorProps) => {
|
||||
const [setState, getState, mounted] = useGridlerStore((s) => [s.setState, s.getState, s.mounted]);
|
||||
|
||||
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
||||
const pageSize = getState('pageSize');
|
||||
3
src/Gridler/components/adaptors/index.ts
Normal file
3
src/Gridler/components/adaptors/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './GlidlerAPIAdaptorForGoLangv2'
|
||||
export * from './GlidlerFormAdaptor'
|
||||
export * from './GlidlerLocalDataAdaptor'
|
||||
@@ -3,7 +3,7 @@ import type { GetRowThemeCallback } from '@glideapps/glide-data-grid';
|
||||
import { darken, lighten, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useGridlerStore } from '../components/Store';
|
||||
import { useGridlerStore } from '../components/GridlerStore';
|
||||
|
||||
export const offsetRows = (colorScheme: any) => (i: number) =>
|
||||
i % 2 === 0
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export {GlidlerAPIAdaptorForGoLangv2 } from './components/adaptors/GlidlerAPIAdaptorForGoLangv2'
|
||||
export {GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor'
|
||||
export {GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor'
|
||||
export * from './components/Column'
|
||||
export {useGridlerStore } from './components/Store'
|
||||
export {useGridlerStore } from './components/GridlerStore'
|
||||
export {Gridler} from './Gridler'
|
||||
@@ -4,7 +4,7 @@ import { useState } from 'react';
|
||||
|
||||
import type { GridlerColumns } from '../components/Column';
|
||||
|
||||
import { APIAdaptorGoLangv2 } from '../components/APIAdaptorGoLangv2';
|
||||
import { GlidlerAPIAdaptorForGoLangv2 } from '../components/adaptors';
|
||||
import { Gridler } from '../Gridler';
|
||||
|
||||
export const GridlerGoAPIExampleEventlog = () => {
|
||||
@@ -51,9 +51,10 @@ export const GridlerGoAPIExampleEventlog = () => {
|
||||
<TextInput label="API Key" onChange={(e) => setApiKey(e.target.value)} value={apiKey} />
|
||||
<Divider />
|
||||
<Checkbox
|
||||
label="Show Side Sections"
|
||||
checked={!!sections}
|
||||
label="Show Side Sections"
|
||||
onChange={(e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
e.target.checked
|
||||
? setSections({
|
||||
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>bottom</div>,
|
||||
@@ -66,8 +67,8 @@ export const GridlerGoAPIExampleEventlog = () => {
|
||||
/>
|
||||
<Divider />
|
||||
<Gridler
|
||||
height="100%"
|
||||
columns={columns}
|
||||
height="100%"
|
||||
// getMenuItems={(id, _state, row, col, defaultItems) => {
|
||||
// console.log('GridlerGoAPIExampleEventlog getMenuItems root', id, row, col, defaultItems);
|
||||
// return [
|
||||
@@ -92,8 +93,8 @@ export const GridlerGoAPIExampleEventlog = () => {
|
||||
uniqueid="gridtest"
|
||||
values={values}
|
||||
>
|
||||
<Gridler.APIAdaptorGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} />
|
||||
<Gridler.GlidlerFormInterface
|
||||
<GlidlerAPIAdaptorForGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} />
|
||||
<Gridler.FormAdaptor
|
||||
descriptionField={'process'}
|
||||
onRequestForm={(request, data) => {
|
||||
console.log('Form requested', request, data);
|
||||
|
||||
Reference in New Issue
Block a user