Form interface work

This commit is contained in:
Hein 2025-10-17 17:04:12 +02:00
parent 8d26d56599
commit 6350b513ca
6 changed files with 197 additions and 62 deletions

View File

@ -2,6 +2,9 @@ import '@glideapps/glide-data-grid/dist/index.css';
import React from 'react'; import React from 'react';
import { MantineBetterMenusProvider } from '../MantineBetterMenu'; 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 { type GridlerProps, Provider } from './components/Store';
import { GridlerDataGrid } from './GridlerDataGrid'; import { GridlerDataGrid } from './GridlerDataGrid';
@ -23,3 +26,7 @@ export const Gridler = (props: GridlerProps) => {
</MantineBetterMenusProvider> </MantineBetterMenusProvider>
); );
}; };
Gridler.GlidlerFormInterface = GlidlerFormInterface;
Gridler.APIAdaptorGoLangv2 = APIAdaptorGoLangv2;
Gridler.LocalDataAdaptor = LocalDataAdaptor;

View File

@ -1,26 +1,112 @@
import { useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import type { MantineBetterMenuInstanceItem } from '../../MantineBetterMenu';
import type { FormRequestType } from '../utils/types'; import type { FormRequestType } from '../utils/types';
import type { GridlerColumn } from './Column';
import { type GridlerProps, useGridlerStore } from './Store'; import { type GridlerProps, type GridlerState, useGridlerStore } from './Store';
export function GlidlerFormInterface(props: { export function GlidlerFormInterface(props: {
getMenuItems?: GridlerProps['getMenuItems']; getMenuItems?: GridlerProps['getMenuItems'];
onRequestForm: (request: FormRequestType, data: Record<string, any>) => void; onReload?: () => void;
onRequestForm: (request: FormRequestType, data: Record<string, unknown>) => void;
}) { }) {
// const [getMenuItems, getState, , mounted, setState] = useGridlerStore((s) => [ const [getState, mounted, setState, reload] = useGridlerStore((s) => [
// s.getMenuItems, s.getState,
// s.getState, s.mounted,
// s.mounted, s.setState,
// s.setState, s.reload,
// ]); ]);
// useEffect(() => { const getMenuItems = useCallback(
// if (mounted) { (
// setState('getMenuItems', props.getMenuItems); id: string,
// setState('onRequestForm', props.onRequestForm); storeState: GridlerState,
// } row?: unknown,
// }, [props.getMenuItems, props.onRequestForm, mounted, setState]); col?: GridlerColumn,
defaultItems?: Array<unknown>
) => {
//console.log('GlidlerFormInterface getMenuItems', id);
if (id === 'header-menu') {
return defaultItems || [];
}
const items = [] as Array<MantineBetterMenuInstanceItem>;
if (!row) {
const firstRow = getState('_gridSelection')?.rows?.first();
if (firstRow !== undefined) {
row = storeState.getRowBuffer(firstRow);
}
}
if (id === 'other') {
items.push({
c: 'blue',
label: 'Add',
onClick: () => {
props.onRequestForm('insert', row as Record<string, unknown>);
},
});
}
if ((id === 'cell' && row) || (id === 'menu' && row)) {
items.push({
c: 'blue',
label: 'Add',
onClick: () => {
props.onRequestForm('insert', row as Record<string, unknown>);
},
});
items.push({
c: 'green',
label: 'Change',
onClick: () => {
props.onRequestForm('change', row as Record<string, unknown>);
},
});
items.push({
c: 'red',
label: 'Delete',
onClick: () => {
props.onRequestForm('delete', row as Record<string, unknown>);
},
});
}
items.push({
isDivider: true,
});
items.push({
c: 'orange',
label: 'Refresh',
onClick: () => {
reload?.();
},
});
const result = props.getMenuItems
? props.getMenuItems(id, storeState, row, col, items)
: items;
//console.log('GlidlerFormInterface getMenuItems', id, items);
if (!items || items.length === 0) {
return defaultItems || [];
}
return result;
},
[props.onRequestForm, getState]
);
useEffect(() => {
if (mounted && typeof setState === 'function') {
//console.log('GlidlerFormInterface setState getMenuItems1', mounted);
if (getState('getMenuItems') !== getMenuItems) {
setState('getMenuItems', getMenuItems);
}
}
return () => {};
}, [props.getMenuItems, mounted]);
return <></>; return <></>;
} }

View File

@ -5,7 +5,7 @@ import type { APIOptions } from '../utils/types';
import { useGridlerStore } from './Store'; import { useGridlerStore } from './Store';
interface LocalDataAdaptorProps extends APIOptions { interface LocalDataAdaptorProps {
data: Array<unknown>; data: Array<unknown>;
} }
@ -16,27 +16,23 @@ export const LocalDataAdaptor = React.memo((props: LocalDataAdaptorProps) => {
s.setState, s.setState,
s.getState, s.getState,
s.addError, s.addError,
s.mounted s.mounted,
]); ]);
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => { const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
const colSort = getState('colSort');
const pageSize = getState('pageSize'); const pageSize = getState('pageSize');
const colFilters = getState('colFilters');
const _active_requests = getState('_active_requests');
if (!(props.data && Array.isArray(props.data))) { if (!(props.data && Array.isArray(props.data))) {
return []; return [];
} }
setState('total_rows', props.data.length); setState('total_rows', props.data.length);
return props.data.slice(index * (pageSize ?? 50), (index + 1) * (pageSize ?? 50)); return props.data.slice(index * (pageSize ?? 50), (index + 1) * (pageSize ?? 50));
}; };
useEffect(() => { useEffect(() => {
setState('useAPIQuery', useAPIQuery); setState('useAPIQuery', useAPIQuery);
}, [props.url, props.authtoken, mounted, setState]); }, [mounted, setState]);
return <></>; return <></>;
}); });

View File

@ -22,7 +22,11 @@ import { createSyncStore } from '@warkypublic/zustandsyncstore';
import { produce } from 'immer'; import { produce } from 'immer';
import { type PropsWithChildren, type ReactNode, useEffect } from 'react'; import { type PropsWithChildren, type ReactNode, useEffect } from 'react';
import { type MantineBetterMenuInstance, useMantineBetterMenus } from '../../MantineBetterMenu'; import {
type MantineBetterMenuInstance,
type MantineBetterMenuInstanceItem,
useMantineBetterMenus,
} from '../../MantineBetterMenu';
import { type FormRequestType } from '../utils/types'; import { type FormRequestType } from '../utils/types';
import { ColumnFilterSet, type GridlerColumn, type GridlerColumns } from './Column'; import { ColumnFilterSet, type GridlerColumn, type GridlerColumns } from './Column';
import { SortDownSprite } from './sprites/SortDown'; import { SortDownSprite } from './sprites/SortDown';
@ -51,7 +55,7 @@ export type FilterOptionOperator =
export interface GridlerProps extends PropsWithChildren { export interface GridlerProps extends PropsWithChildren {
askAPIRowNumber?: (key: string) => Promise<number>; askAPIRowNumber?: (key: string) => Promise<number>;
columns?: GridlerColumns; columns?: GridlerColumns;
data?: Array<any>;
defaultSort?: Array<SortOption>; defaultSort?: Array<SortOption>;
enableOddEvenRowColor?: boolean; enableOddEvenRowColor?: boolean;
getMenuItems?: ( getMenuItems?: (
@ -59,8 +63,8 @@ export interface GridlerProps extends PropsWithChildren {
storeState: GridlerState, storeState: GridlerState,
row?: any, row?: any,
col?: GridlerColumn, col?: GridlerColumn,
defaultItems?: Array<any> defaultItems?: Array<MantineBetterMenuInstanceItem>
) => Array<any>; ) => Array<MantineBetterMenuInstanceItem>;
glideProps?: Partial<DataEditorProps>; glideProps?: Partial<DataEditorProps>;
headerHeight?: number; headerHeight?: number;
@ -103,6 +107,7 @@ export interface GridlerProps extends PropsWithChildren {
export interface GridlerState { export interface GridlerState {
_active_requests?: Array<{ controller: AbortController; page: number }>; _active_requests?: Array<{ controller: AbortController; page: number }>;
_activeTooltip?: ReactNode; _activeTooltip?: ReactNode;
_events: EventTarget;
_glideref?: DataEditorRef; _glideref?: DataEditorRef;
_gridSelection?: GridSelection; _gridSelection?: GridSelection;
_gridSelectionRows?: GridSelection['rows']; _gridSelectionRows?: GridSelection['rows'];
@ -111,14 +116,15 @@ export interface GridlerState {
_scrollTimeout?: any | number; _scrollTimeout?: any | number;
_visibleArea: Rectangle; _visibleArea: Rectangle;
_visiblePages: Rectangle; _visiblePages: Rectangle;
addError: (err: string, ...args: Array<any>) => void; addError: (err: string, ...args: Array<any>) => void;
colFilters?: Array<FilterOption>; colFilters?: Array<FilterOption>;
colOrder?: Record<string, number>; colOrder?: Record<string, number>;
colSize?: Record<string, number>; colSize?: Record<string, number>;
colSort?: Array<SortOption>; colSort?: Array<SortOption>;
data?: Array<any>; data?: Array<any>;
errors: Array<string>;
errors: Array<string>;
focused?: boolean; focused?: boolean;
get: () => GridlerState; get: () => GridlerState;
getCellContent: (cell: Item) => GridCell; getCellContent: (cell: Item) => GridCell;
@ -128,8 +134,8 @@ export interface GridlerState {
) => CellArray | GetCellsThunk; ) => CellArray | GetCellsThunk;
getRowBuffer: (row: number) => any; getRowBuffer: (row: number) => any;
getState: <K extends keyof GridlerStoreState>(key: K) => GridlerStoreState[K]; getState: <K extends keyof GridlerStoreState>(key: K) => GridlerStoreState[K];
hasLocalData: boolean;
hasLocalData: boolean;
loadingData?: boolean; loadingData?: boolean;
loadPage: (page: number, clearMode?: 'all' | 'page') => Promise<void>; loadPage: (page: number, clearMode?: 'all' | 'page') => Promise<void>;
mounted: boolean; mounted: boolean;
@ -146,6 +152,7 @@ export interface GridlerState {
onHeaderClicked: (colIndex: number, event: HeaderClickedEventArgs) => void; onHeaderClicked: (colIndex: number, event: HeaderClickedEventArgs) => void;
onHeaderMenuClick: (col: number, screenPosition: Rectangle) => void; onHeaderMenuClick: (col: number, screenPosition: Rectangle) => void;
onItemHovered: (args: GridMouseEventArgs) => void; onItemHovered: (args: GridMouseEventArgs) => void;
onVisibleRegionChanged: ( onVisibleRegionChanged: (
r: Rectangle, r: Rectangle,
tx: number, tx: number,
@ -158,7 +165,6 @@ export interface GridlerState {
) => void; ) => void;
pageSize: number; pageSize: number;
reload?: () => Promise<void>; reload?: () => Promise<void>;
renderColumns?: GridlerColumns; renderColumns?: GridlerColumns;
setState: <K extends keyof GridlerStoreState>( setState: <K extends keyof GridlerStoreState>(
@ -179,6 +185,7 @@ export type SortOption = { direction: 'asc' | 'desc'; id: string; order?: number
const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreState, GridlerProps>( const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreState, GridlerProps>(
(set, get) => ({ (set, get) => ({
_events: new EventTarget(),
_loadingList: CompactSelection.empty(), _loadingList: CompactSelection.empty(),
_page_data: {}, _page_data: {},
_visibleArea: { height: 10000, width: 1000, x: 0, y: 0 }, _visibleArea: { height: 10000, width: 1000, x: 0, y: 0 },
@ -265,6 +272,15 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
const state = get(); const state = get();
const page = pPage < 0 ? 0 : pPage; const page = pPage < 0 ? 0 : pPage;
const result = state._events.dispatchEvent(
new CustomEvent('before_loadPage', {
detail: { clearMode, page: pPage, state },
})
);
if (!result) {
return;
}
const damageList: { cell: [number, number] }[] = []; const damageList: { cell: [number, number] }[] = [];
const colLen = Object.keys(state.renderColumns ?? [1, 2, 3]).length; const colLen = Object.keys(state.renderColumns ?? [1, 2, 3]).length;
const upperPage = state.pageSize * page; const upperPage = state.pageSize * page;
@ -302,9 +318,19 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
} }
state._glideref?.updateCells(damageList); state._glideref?.updateCells(damageList);
state._events.dispatchEvent(
new CustomEvent('loadPage', {
detail: { clearMode, data, page: pPage, state },
})
);
}) })
.catch((e) => { .catch((e) => {
console.warn('loadPage Error: ', page, e); console.warn('loadPage Error: ', page, e);
state._events.dispatchEvent(
new CustomEvent('loadPage_error', {
detail: { clearMode, error: e, page: pPage, state },
})
);
}); });
} }
}, },
@ -316,6 +342,11 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
//const current = state._editData?.[row]; //const current = state._editData?.[row];
//if (current === undefined) return; //if (current === undefined) return;
//todo: complete //todo: complete
state._events.dispatchEvent(
new CustomEvent('onCellEdited', {
detail: { cell, newVal, row, state },
})
);
}, },
onColumnMoved: (from: number, to: number) => { onColumnMoved: (from: number, to: number) => {
const s = get(); const s = get();
@ -396,22 +427,17 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
}, },
]; ];
s.hideMenu?.(area); s.hideMenu?.(area);
s.showMenu?.(area, { s.showMenu?.(area, {
items: items:
coldef?.getMenuItems?.( coldef?.getMenuItems?.(
area, area,
s, s,
col && row ? s.getCellContent([col, row]) : undefined, col && row ? s.getRowBuffer(row) : undefined,
coldef,
items
) ??
s.getMenuItems?.(
area,
s,
col && row ? s.getCellContent([col, row]) : undefined,
coldef, coldef,
items items
) ?? ) ??
s.getMenuItems?.(area, s, col && row ? s.getRowBuffer(row) : undefined, coldef, items) ??
items, items,
x: event.clientX ?? event.bounds?.x, x: event.clientX ?? event.bounds?.x,
y: event.clientY ?? event.bounds?.y, y: event.clientY ?? event.bounds?.y,
@ -701,9 +727,19 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
window.document.body.appendChild(div); window.document.body.appendChild(div);
} }
} }
getState('_events').dispatchEvent(
new CustomEvent('mounted', {
detail: {},
})
);
return () => { return () => {
const onUnMounted = getState('onUnMounted'); const onUnMounted = getState('onUnMounted');
setState('mounted', false); setState('mounted', false);
getState('_events').dispatchEvent(
new CustomEvent('unmounted', {
detail: {},
})
);
if (typeof onUnMounted === 'function') { if (typeof onUnMounted === 'function') {
onUnMounted(); onUnMounted();
} }
@ -742,6 +778,11 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
.then((r) => { .then((r) => {
if (r >= 0) { if (r >= 0) {
ref.scrollTo(0, r); ref.scrollTo(0, r);
getState('_events').dispatchEvent(
new CustomEvent('selectedRowFound', {
detail: { rowNumber: r, selectedRow: selectedRow },
})
);
} }
}) })
.catch((e) => { .catch((e) => {
@ -751,6 +792,10 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
} }
}, [props.selectedRow]); }, [props.selectedRow]);
getState('_events').addEventListener('reload', (e: Event) => {
getState('reload')?.();
});
return { return {
...props, ...props,
hasLocalData: props.data && props.data.length > 0, hasLocalData: props.data && props.data.length > 0,

View File

@ -68,18 +68,19 @@ export const GridlerGoAPIExampleEventlog = () => {
<Gridler <Gridler
height="100%" height="100%"
columns={columns} columns={columns}
getMenuItems={(id, _state, row, col, defaultItems) => { // getMenuItems={(id, _state, row, col, defaultItems) => {
return [ // console.log('GridlerGoAPIExampleEventlog getMenuItems root', id, row, col, defaultItems);
...(defaultItems ?? []), // return [
// { // ...(defaultItems ?? []),
// id: 'test', // // {
// label: `Test -${id}`, // // id: 'test',
// onClick: () => { // // label: `Test -${id}`,
// console.log('Test clicked', row, col); // // onClick: () => {
// }, // // console.log('Test clicked', row, col);
// }, // // },
]; // // },
}} // ];
// }}
keyField="id_process" keyField="id_process"
onChange={(v) => { onChange={(v) => {
//console.log('GridlerGoAPIExampleEventlog onChange', v); //console.log('GridlerGoAPIExampleEventlog onChange', v);
@ -91,7 +92,12 @@ export const GridlerGoAPIExampleEventlog = () => {
uniqueid="gridtest" uniqueid="gridtest"
values={values} values={values}
> >
<APIAdaptorGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} /> <Gridler.APIAdaptorGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} />
<Gridler.GlidlerFormInterface
onRequestForm={(request, data) => {
console.log('Form requested', request, data);
}}
/>
</Gridler> </Gridler>
<Divider /> <Divider />
<Group> <Group>

File diff suppressed because one or more lines are too long