Form interface and Loading menu
This commit is contained in:
parent
f084cf70ae
commit
8d26d56599
@ -5,7 +5,7 @@ import {
|
|||||||
type DataEditorRef,
|
type DataEditorRef,
|
||||||
type GridColumn,
|
type GridColumn,
|
||||||
} from '@glideapps/glide-data-grid';
|
} from '@glideapps/glide-data-grid';
|
||||||
import { ActionIcon, Stack } from '@mantine/core';
|
import { ActionIcon, Group, Stack } from '@mantine/core';
|
||||||
import { useElementSize, useMergedRef } from '@mantine/hooks';
|
import { useElementSize, useMergedRef } from '@mantine/hooks';
|
||||||
import { IconMenu2 } from '@tabler/icons-react';
|
import { IconMenu2 } from '@tabler/icons-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -19,6 +19,7 @@ import { SortUpSprite } from './components/sprites/SortUp';
|
|||||||
import { useGridlerStore } from './components/Store';
|
import { useGridlerStore } from './components/Store';
|
||||||
import classes from './Gridler.module.css';
|
import classes from './Gridler.module.css';
|
||||||
import { useGridTheme } from './hooks/use-grid-theme';
|
import { useGridTheme } from './hooks/use-grid-theme';
|
||||||
|
import { RightMenuIcon } from './components/RightMenuIcon';
|
||||||
|
|
||||||
export const GridlerDataGrid = () => {
|
export const GridlerDataGrid = () => {
|
||||||
const ref = React.useRef<DataEditorRef | null>(null);
|
const ref = React.useRef<DataEditorRef | null>(null);
|
||||||
@ -195,14 +196,11 @@ export const GridlerDataGrid = () => {
|
|||||||
rangeSelect="multi-rect"
|
rangeSelect="multi-rect"
|
||||||
ref={refMerged as any}
|
ref={refMerged as any}
|
||||||
rightElement={
|
rightElement={
|
||||||
<ActionIcon
|
<Group>
|
||||||
mr="xs"
|
{sections?.rightElementStart}
|
||||||
mt="2px"
|
<RightMenuIcon />
|
||||||
onClick={(e) => onContextClick('menu', e)}
|
{sections?.rightElementEnd}
|
||||||
variant="subtle"
|
</Group>
|
||||||
>
|
|
||||||
<IconMenu2 />
|
|
||||||
</ActionIcon>
|
|
||||||
}
|
}
|
||||||
rowHeight={rowHeight ?? 22}
|
rowHeight={rowHeight ?? 22}
|
||||||
//rowMarkersCheckboxStyle='square'
|
//rowMarkersCheckboxStyle='square'
|
||||||
|
|||||||
@ -20,70 +20,80 @@ export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => {
|
|||||||
const pageSize = getState('pageSize');
|
const pageSize = getState('pageSize');
|
||||||
const colFilters = getState('colFilters');
|
const colFilters = getState('colFilters');
|
||||||
const _active_requests = getState('_active_requests');
|
const _active_requests = getState('_active_requests');
|
||||||
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
setState('loadingData', true);
|
||||||
if (props && props.url) {
|
try {
|
||||||
const head = new Headers();
|
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
||||||
head.set('x-limit', String(pageSize ?? 50));
|
if (props && props.url) {
|
||||||
head.set('x-offset', String((pageSize ?? 50) * index));
|
const head = new Headers();
|
||||||
|
head.set('x-limit', String(pageSize ?? 50));
|
||||||
|
head.set('x-offset', String((pageSize ?? 50) * index));
|
||||||
|
|
||||||
head.set('Authorization', `Token ${props.authtoken}`);
|
head.set('Authorization', `Token ${props.authtoken}`);
|
||||||
|
|
||||||
if (colSort?.length && colSort.length > 0) {
|
if (colSort?.length && colSort.length > 0) {
|
||||||
head.set(
|
head.set(
|
||||||
'x-sort',
|
'x-sort',
|
||||||
colSort
|
colSort
|
||||||
?.map((sort: any) => `${sort.id} ${sort.direction}`)
|
?.map((sort: any) => `${sort.id} ${sort.direction}`)
|
||||||
.reduce((acc: any, val: any) => `${acc},${val}`)
|
.reduce((acc: any, val: any) => `${acc},${val}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colFilters?.length && colFilters.length > 0) {
|
||||||
|
colFilters
|
||||||
|
?.filter((f) => f.value?.length > 0)
|
||||||
|
?.forEach((filter: any) => {
|
||||||
|
if (filter.value && filter.value !== '') {
|
||||||
|
head.set(`x-searchop-${filter.operator}-${filter.id}`, `${filter.value}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentRequestIndex = _active_requests?.findIndex((f) => f.page === index) ?? -1;
|
||||||
|
_active_requests?.forEach((r) => {
|
||||||
|
if ((r.page >= 0 && r.page < index - 2) || (index >= 0 && r.page > index + 2)) {
|
||||||
|
r.controller?.abort?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (_active_requests && currentRequestIndex >= 0 && _active_requests[currentRequestIndex]) {
|
||||||
|
//console.log(`Already queued ${index}`, index, s._active_requests);
|
||||||
|
setState('loadingData', false);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
await setStateFN('_active_requests', (cv) => [...(cv ?? []), { controller, page: index }]);
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`${props.url}?x-limit=${String(pageSize ?? 50)}&x-offset=${String((pageSize ?? 50) * index)}`,
|
||||||
|
{
|
||||||
|
headers: head,
|
||||||
|
method: 'GET',
|
||||||
|
signal: controller?.signal,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (colFilters?.length && colFilters.length > 0) {
|
if (res.ok) {
|
||||||
colFilters
|
const cr = res.headers.get('Content-Range')?.split('/');
|
||||||
?.filter((f) => f.value?.length > 0)
|
if (cr?.[1] && parseInt(cr[1], 10) > 0) {
|
||||||
?.forEach((filter: any) => {
|
setState('total_rows', parseInt(cr[1], 10));
|
||||||
if (filter.value && filter.value !== '') {
|
}
|
||||||
head.set(`x-searchop-${filter.operator}-${filter.id}`, `${filter.value}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentRequestIndex = _active_requests?.findIndex((f) => f.page === index) ?? -1;
|
const data = await res.json();
|
||||||
_active_requests?.forEach((r) => {
|
setState('loadingData', false);
|
||||||
if ((r.page >= 0 && r.page < index - 2) || (index >= 0 && r.page > index + 2)) {
|
return data ?? [];
|
||||||
r.controller?.abort?.();
|
|
||||||
}
|
}
|
||||||
});
|
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
||||||
if (_active_requests && currentRequestIndex >= 0 && _active_requests[currentRequestIndex]) {
|
|
||||||
//console.log(`Already queued ${index}`, index, s._active_requests);
|
await setStateFN('_active_requests', (cv) => [
|
||||||
return undefined;
|
...(cv ?? []).filter((f) => f.page !== index),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
const controller = new AbortController();
|
//console.log('APIAdaptorGoLangv2 error', e);
|
||||||
await setStateFN('_active_requests', (cv) => [...(cv ?? []), { controller, page: index }]);
|
addError(`Error: ${e}`, 'api', props.url);
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
`${props.url}?x-limit=${String(pageSize ?? 50)}&x-offset=${String((pageSize ?? 50) * index)}`,
|
|
||||||
{
|
|
||||||
headers: head,
|
|
||||||
method: 'GET',
|
|
||||||
signal: controller?.signal,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
const cr = res.headers.get('Content-Range')?.split('/');
|
|
||||||
if (cr?.[1] && parseInt(cr[1], 10) > 0) {
|
|
||||||
setState('total_rows', parseInt(cr[1], 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
return data ?? [];
|
|
||||||
}
|
|
||||||
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
|
||||||
|
|
||||||
await setStateFN('_active_requests', (cv) => [...(cv ?? []).filter((f) => f.page !== index)]);
|
|
||||||
}
|
}
|
||||||
|
setState('loadingData', false);
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
26
src/Gridler/components/GridlerFormInterface.tsx
Normal file
26
src/Gridler/components/GridlerFormInterface.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import type { FormRequestType } from '../utils/types';
|
||||||
|
|
||||||
|
import { type GridlerProps, useGridlerStore } from './Store';
|
||||||
|
|
||||||
|
export function GlidlerFormInterface(props: {
|
||||||
|
getMenuItems?: GridlerProps['getMenuItems'];
|
||||||
|
onRequestForm: (request: FormRequestType, data: Record<string, any>) => void;
|
||||||
|
}) {
|
||||||
|
// const [getMenuItems, getState, , mounted, setState] = useGridlerStore((s) => [
|
||||||
|
// s.getMenuItems,
|
||||||
|
// s.getState,
|
||||||
|
// s.mounted,
|
||||||
|
// s.setState,
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (mounted) {
|
||||||
|
// setState('getMenuItems', props.getMenuItems);
|
||||||
|
// setState('onRequestForm', props.onRequestForm);
|
||||||
|
// }
|
||||||
|
// }, [props.getMenuItems, props.onRequestForm, mounted, setState]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
23
src/Gridler/components/RightMenuIcon.tsx
Normal file
23
src/Gridler/components/RightMenuIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ActionIcon } from '@mantine/core';
|
||||||
|
import { IconMenu2 } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { useGridlerStore } from './Store';
|
||||||
|
|
||||||
|
export function RightMenuIcon() {
|
||||||
|
const { loadingData, onContextClick } = useGridlerStore((s) => ({
|
||||||
|
loadingData: s.loadingData,
|
||||||
|
onContextClick: s.onContextClick,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionIcon
|
||||||
|
loading={loadingData}
|
||||||
|
mr="xs"
|
||||||
|
mt="2px"
|
||||||
|
onClick={(e) => onContextClick('menu', e)}
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<IconMenu2 />
|
||||||
|
</ActionIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -23,7 +23,7 @@ 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, useMantineBetterMenus } from '../../MantineBetterMenu';
|
||||||
import { type TRequest } 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';
|
||||||
import { SortUpSprite } from './sprites/SortUp';
|
import { SortUpSprite } from './sprites/SortUp';
|
||||||
@ -80,12 +80,14 @@ export interface GridlerProps extends PropsWithChildren {
|
|||||||
value: any,
|
value: any,
|
||||||
store: GridlerState
|
store: GridlerState
|
||||||
) => GridCell;
|
) => GridCell;
|
||||||
request?: TRequest;
|
|
||||||
rowHeight?: number;
|
rowHeight?: number;
|
||||||
sections?: {
|
sections?: {
|
||||||
bottom?: React.ReactNode;
|
bottom?: React.ReactNode;
|
||||||
left?: React.ReactNode;
|
left?: React.ReactNode;
|
||||||
right?: React.ReactNode;
|
right?: React.ReactNode;
|
||||||
|
rightElementEnd?: React.ReactNode;
|
||||||
|
rightElementStart?: React.ReactNode;
|
||||||
top?: React.ReactNode;
|
top?: React.ReactNode;
|
||||||
};
|
};
|
||||||
selectedRow?: number;
|
selectedRow?: number;
|
||||||
@ -115,8 +117,8 @@ export interface GridlerState {
|
|||||||
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;
|
||||||
@ -126,8 +128,9 @@ 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;
|
||||||
loadPage: (page: number, clearMode?: 'all' | 'page') => Promise<void>;
|
loadPage: (page: number, clearMode?: 'all' | 'page') => Promise<void>;
|
||||||
mounted: boolean;
|
mounted: boolean;
|
||||||
onCellEdited: (cell: Item, newVal: EditableGridCell) => void;
|
onCellEdited: (cell: Item, newVal: EditableGridCell) => void;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
import { Checkbox, Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
const [apiKey, setApiKey] = useLocalStorage({ defaultValue: '', key: 'apikey' });
|
const [apiKey, setApiKey] = useLocalStorage({ defaultValue: '', key: 'apikey' });
|
||||||
const [selectRow, setSelectRow] = useState<string | undefined>('');
|
const [selectRow, setSelectRow] = useState<string | undefined>('');
|
||||||
const [values, setValues] = useState<Array<Record<string, any>>>([]);
|
const [values, setValues] = useState<Array<Record<string, any>>>([]);
|
||||||
|
const [sections, setSections] = useState<Record<string, unknown> | undefined>(undefined);
|
||||||
const columns: GridlerColumns = [
|
const columns: GridlerColumns = [
|
||||||
{
|
{
|
||||||
id: 'id_process',
|
id: 'id_process',
|
||||||
@ -49,6 +50,21 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
<TextInput label="API Url" onChange={(e) => setApiUrl(e.target.value)} value={apiUrl} />
|
<TextInput label="API Url" onChange={(e) => setApiUrl(e.target.value)} value={apiUrl} />
|
||||||
<TextInput label="API Key" onChange={(e) => setApiKey(e.target.value)} value={apiKey} />
|
<TextInput label="API Key" onChange={(e) => setApiKey(e.target.value)} value={apiKey} />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Checkbox
|
||||||
|
label="Show Side Sections"
|
||||||
|
checked={!!sections}
|
||||||
|
onChange={(e) => {
|
||||||
|
e.target.checked
|
||||||
|
? setSections({
|
||||||
|
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>bottom</div>,
|
||||||
|
left: <div style={{ backgroundColor: 'orange', width: '20px' }}>L</div>,
|
||||||
|
right: <div style={{ backgroundColor: 'green', width: '20px' }}>R</div>,
|
||||||
|
top: <div style={{ backgroundColor: 'purple', height: '20px' }}>top</div>,
|
||||||
|
})
|
||||||
|
: setSections(undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
<Gridler
|
<Gridler
|
||||||
height="100%"
|
height="100%"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@ -69,12 +85,7 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
//console.log('GridlerGoAPIExampleEventlog onChange', v);
|
//console.log('GridlerGoAPIExampleEventlog onChange', v);
|
||||||
setValues(v);
|
setValues(v);
|
||||||
}}
|
}}
|
||||||
sections={{
|
sections={sections}
|
||||||
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>bottom</div>,
|
|
||||||
left: <div style={{ backgroundColor: 'orange', width: '20px' }}>L</div>,
|
|
||||||
right: <div style={{ backgroundColor: 'green', width: '20px' }}>R</div>,
|
|
||||||
top: <div style={{ backgroundColor: 'purple', height: '20px' }}>top</div>,
|
|
||||||
}}
|
|
||||||
selectedRow={selectRow ? parseInt(selectRow, 10) : undefined}
|
selectedRow={selectRow ? parseInt(selectRow, 10) : undefined}
|
||||||
selectMode="row"
|
selectMode="row"
|
||||||
uniqueid="gridtest"
|
uniqueid="gridtest"
|
||||||
|
|||||||
@ -15,4 +15,4 @@ export interface APIOptions {
|
|||||||
url?: string;
|
url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TRequest = 'change' | 'delete' | 'insert' | 'select' ;
|
export type FormRequestType = 'change' | 'delete' | 'insert' | 'select' ;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user