diff --git a/.prettierrc b/.prettierrc index 94beb1484..764fbde2e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,7 @@ { "singleQuote": true, "trailingComma": "all", - "printWidth": 80, + "printWidth": 120, "overrides": [ { "files": ".prettierrc", diff --git a/packages/client/src/components/admin-layout/Collections/index.tsx b/packages/client/src/components/admin-layout/Collections/index.tsx index eec41a7de..665b8a153 100644 --- a/packages/client/src/components/admin-layout/Collections/index.tsx +++ b/packages/client/src/components/admin-layout/Collections/index.tsx @@ -11,7 +11,8 @@ import { PlusOutlined, DownOutlined } from '@ant-design/icons'; import cls from 'classnames'; import { uid, isValid, clone } from '@formily/shared'; import { Resource } from '../../../resource'; -import { TableRowContext, useTable } from '../../../schemas/table'; +import { TableRowContext } from '../../../schemas/table/context'; +import { useTable } from '../../../schemas/table/hooks/useTable'; import { useRequest } from 'ahooks'; import { VisibleContext } from '../../../context'; import { connect, FormProvider, observer, useForm } from '@formily/react'; diff --git a/packages/client/src/components/admin-layout/Permissions/ActionPermissionField.tsx b/packages/client/src/components/admin-layout/Permissions/ActionPermissionField.tsx index 36153c0d5..12962c1da 100644 --- a/packages/client/src/components/admin-layout/Permissions/ActionPermissionField.tsx +++ b/packages/client/src/components/admin-layout/Permissions/ActionPermissionField.tsx @@ -8,7 +8,8 @@ import { LockOutlined } from '@ant-design/icons'; import cls from 'classnames'; import { uid } from '@formily/shared'; import { Resource } from '../../../resource'; -import { TableRowContext, useTable } from '../../../schemas/table'; +import { TableRowContext } from '../../../schemas/table/context'; +import { useTable } from '../../../schemas/table/hooks/useTable'; import { useRequest } from 'ahooks'; import { VisibleContext } from '../../../context'; import { connect, ISchema, observer, useField } from '@formily/react'; diff --git a/packages/client/src/components/admin-layout/Permissions/index.tsx b/packages/client/src/components/admin-layout/Permissions/index.tsx index 34c9967b1..8beec3a04 100644 --- a/packages/client/src/components/admin-layout/Permissions/index.tsx +++ b/packages/client/src/components/admin-layout/Permissions/index.tsx @@ -8,7 +8,8 @@ import { LockOutlined } from '@ant-design/icons'; import cls from 'classnames'; import { uid, isValid } from '@formily/shared'; import { Resource } from '../../../resource'; -import { TableRowContext, useTable } from '../../../schemas/table'; +import { TableRowContext } from '../../../schemas/table/context'; +import { useTable } from '../../../schemas/table/hooks/useTable'; import { useRequest } from 'ahooks'; import { VisibleContext } from '../../../context'; import { connect, observer, useForm } from '@formily/react'; diff --git a/packages/client/src/components/schema-renderer/index.tsx b/packages/client/src/components/schema-renderer/index.tsx index f7ca5841a..554b8d53d 100644 --- a/packages/client/src/components/schema-renderer/index.tsx +++ b/packages/client/src/components/schema-renderer/index.tsx @@ -52,11 +52,8 @@ import { Menu } from '../../schemas/menu'; import { Password } from '../../schemas/password'; import { Radio } from '../../schemas/radio'; import { Select } from '../../schemas/select'; -import { - CollectionFieldContext, - Table, - TableRowContext, -} from '../../schemas/table'; +import { Table } from '../../schemas/table'; +import { CollectionFieldContext, TableRowContext } from '../../schemas/table/context'; import { Tabs } from '../../schemas/tabs'; import { TimePicker } from '../../schemas/time-picker'; import { Upload } from '../../schemas/upload'; diff --git a/packages/client/src/schemas/calendar/DesignableBar.tsx b/packages/client/src/schemas/calendar/DesignableBar.tsx index d70b39020..eff67ebb6 100644 --- a/packages/client/src/schemas/calendar/DesignableBar.tsx +++ b/packages/client/src/schemas/calendar/DesignableBar.tsx @@ -38,7 +38,7 @@ import { useEffect } from 'react'; import { uid } from '@formily/shared'; import { getSchemaPath } from '../../components/schema-renderer'; import { useCollection, useCollectionContext } from '../../constate'; -import { useTable } from '../table'; +import { useTable } from '../table/hooks/useTable'; import { fieldsToFilterColumns } from './'; import { set } from 'lodash'; import { DragHandle } from '../../components/Sortable'; diff --git a/packages/client/src/schemas/form/DesignableBar.tsx b/packages/client/src/schemas/form/DesignableBar.tsx index 82e3dee41..4046f761c 100644 --- a/packages/client/src/schemas/form/DesignableBar.tsx +++ b/packages/client/src/schemas/form/DesignableBar.tsx @@ -27,7 +27,7 @@ import { useEffect } from 'react'; import { uid } from '@formily/shared'; import { getSchemaPath } from '../../components/schema-renderer'; import { useCollection, useCollectionContext } from '../../constate'; -import { useTable } from '../table'; +import { useTable } from '../table/hooks/useTable'; import { BlockSchemaContext } from '../../context'; import { DragHandle } from '../../components/Sortable'; import { useTranslation } from 'react-i18next'; diff --git a/packages/client/src/schemas/form/index.tsx b/packages/client/src/schemas/form/index.tsx index a17524a61..49ac99f64 100644 --- a/packages/client/src/schemas/form/index.tsx +++ b/packages/client/src/schemas/form/index.tsx @@ -44,7 +44,7 @@ import { import { useResource as useGeneralResource } from '../../hooks/useResource'; import { Resource } from '../../resource'; import { BaseResult } from '@ahooksjs/use-request/lib/types'; -import { CollectionFieldContext } from '../table'; +import { CollectionFieldContext } from '../table/context'; import { useTranslation } from 'react-i18next'; export interface DescriptionsContextProps { diff --git a/packages/client/src/schemas/grid/DesignableBar.tsx b/packages/client/src/schemas/grid/DesignableBar.tsx index 46d14d31e..30ccd0da9 100644 --- a/packages/client/src/schemas/grid/DesignableBar.tsx +++ b/packages/client/src/schemas/grid/DesignableBar.tsx @@ -27,7 +27,7 @@ import { useEffect } from 'react'; import { uid } from '@formily/shared'; import { getSchemaPath } from '../../components/schema-renderer'; import { useCollection, useCollectionContext } from '../../constate'; -import { useTable } from '../table'; +import { useTable } from '../table/hooks/useTable'; import { DragHandle } from '../../components/Sortable'; import { useCompile } from '../../hooks/useCompile'; diff --git a/packages/client/src/schemas/kanban/DesignableBar.tsx b/packages/client/src/schemas/kanban/DesignableBar.tsx index e7d14bda4..14a25d4b1 100644 --- a/packages/client/src/schemas/kanban/DesignableBar.tsx +++ b/packages/client/src/schemas/kanban/DesignableBar.tsx @@ -33,7 +33,7 @@ import { useEffect } from 'react'; import { uid } from '@formily/shared'; import { getSchemaPath } from '../../components/schema-renderer'; import { useCollection, useCollectionContext } from '../../constate'; -import { useTable } from '../table'; +import { useTable } from '../table/hooks/useTable'; import { DragHandle } from '../../components/Sortable'; import { fieldsToFilterColumns } from '../calendar'; import { useTranslation } from 'react-i18next'; diff --git a/packages/client/src/schemas/select/index.tsx b/packages/client/src/schemas/select/index.tsx index 710598aeb..a6479c872 100644 --- a/packages/client/src/schemas/select/index.tsx +++ b/packages/client/src/schemas/select/index.tsx @@ -24,7 +24,7 @@ import { Action } from '../action'; import { BlockSchemaContext, VisibleContext } from '../../context'; import { SchemaRenderer } from '../../components/schema-renderer'; import { uid } from '@formily/shared'; -import { CollectionFieldContext } from '../table'; +import { CollectionFieldContext } from '../table/context'; import { CollectionProvider, useCollectionContext, diff --git a/packages/client/src/schemas/table/Actions.tsx b/packages/client/src/schemas/table/Actions.tsx new file mode 100644 index 000000000..a80a55a61 --- /dev/null +++ b/packages/client/src/schemas/table/Actions.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Space } from 'antd'; +import { RecursionField } from '@formily/react'; +import { useDesignable } from '..'; +import { Droppable, SortableItem } from '../../components/Sortable'; +import { getSchemaPath } from '../../components/schema-renderer'; + +export const Actions = (props: any) => { + const { align = 'left' } = props; + const { schema, designable } = useDesignable(); + return ( + + + {schema.mapProperties((s) => { + const currentAlign = s['x-align'] || 'left'; + if (currentAlign !== align) { + return null; + } + return ( + + + + ); + })} + + + ); +}; diff --git a/packages/client/src/schemas/table/AddActionButton.tsx b/packages/client/src/schemas/table/AddActionButton.tsx new file mode 100644 index 000000000..36ef78ac6 --- /dev/null +++ b/packages/client/src/schemas/table/AddActionButton.tsx @@ -0,0 +1,77 @@ +import React, { useState } from 'react'; +import { Dropdown, Menu, Button } from 'antd'; +import { SettingOutlined } from '@ant-design/icons'; +import { useTranslation } from 'react-i18next'; +import { Schema } from '@formily/react'; +import { useDesignable, ISchema } from '..'; +import { useDisplayedMapContext, useClient } from '../../constate'; +import SwitchMenuItem from '../../components/SwitchMenuItem'; +import { getSchemaPath } from '../../components/schema-renderer'; +import {generateActionSchema} from './utils'; + +export const AddActionButton = () => { + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const displayed = useDisplayedMapContext(); + const { appendChild, remove } = useDesignable(); + const { schema, designable } = useDesignable(); + const { createSchema, removeSchema, updateSchema } = useClient(); + + if (!designable || !schema['x-designable-bar']) { + return null; + } + return ( + + + {[ + { title: t('Filter'), name: 'filter' }, + { title: t('Export'), name: 'export' }, + { title: t('Add new'), name: 'create' }, + { title: t('Delete'), name: 'destroy' }, + ].map((item) => ( + { + if (!checked) { + const s = displayed.get(item.name) as Schema; + const path = getSchemaPath(s); + displayed.remove(item.name); + const removed = remove(path); + await removeSchema(removed); + } else { + const s = generateActionSchema(item.name); + const data = appendChild(s); + await createSchema(data); + } + }} + /> + ))} + + + + {t('Function')} + {t('Popup form')} + {t('Flexible popup')} + + + } + > + + + ); +}; + diff --git a/packages/client/src/schemas/table/AddColumn.tsx b/packages/client/src/schemas/table/AddColumn.tsx new file mode 100644 index 000000000..dcc982557 --- /dev/null +++ b/packages/client/src/schemas/table/AddColumn.tsx @@ -0,0 +1,232 @@ +import React, { useState } from 'react'; +import { Dropdown, Menu, Button } from 'antd'; +import { SettingOutlined } from '@ant-design/icons'; +import { useTranslation } from 'react-i18next'; +import { uid } from '@formily/shared'; +import { FormDialog, FormLayout } from '@formily/antd'; +import { useDesignable, createCollectionField, ISchema } from '..'; +import { useCollectionContext, useCollectionsContext, useDisplayedMapContext, useClient } from '../../constate'; +import { useTable } from './hooks/useTable'; +import SwitchMenuItem from '../../components/SwitchMenuItem'; +import { isAssociation, options } from '../database-field/interfaces'; +import { getSchemaPath, SchemaField } from '../../components/schema-renderer'; + +export function AddColumn() { + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const { appendChild, remove } = useDesignable(); + const { loadCollections } = useCollectionsContext(); + const { collection, fields, refresh } = useCollectionContext(); + const displayed = useDisplayedMapContext(); + const { service } = useTable(); + const { createSchema, removeSchema, updateSchema } = useClient(); + return ( + + + {fields.map((field) => ( + { + if (checked) { + console.log('SwitchMenuItem.field.name', field.dataType, service.params[0]); + const columnSchema: ISchema = { + type: 'void', + 'x-component': 'Table.Column', + 'x-component-props': { + fieldName: field.name, + }, + 'x-designable-bar': 'Table.Column.DesignableBar', + }; + if (field.interface === 'linkTo') { + columnSchema.properties = { + options: { + type: 'void', + 'x-decorator': 'Form', + 'x-component': 'Select.Options.Drawer', + 'x-component-props': { + useOkAction: '{{ Select.useOkAction }}', + }, + title: "{{t('Select record')}}", + properties: { + table: { + type: 'array', + 'x-designable-bar': 'Table.DesignableBar', + 'x-decorator': 'BlockItem', + 'x-decorator-props': { + draggable: false, + }, + 'x-component': 'Table', + default: [], + 'x-component-props': { + rowKey: 'id', + useRowSelection: '{{ Select.useRowSelection }}', + useSelectedRowKeys: '{{ Select.useSelectedRowKeys }}', + onSelect: '{{ Select.useSelect() }}', + collectionName: field.target, + // dragSort: true, + // showIndex: true, + refreshRequestOnChange: true, + pagination: { + pageSize: 10, + }, + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Table.ActionBar', + 'x-designable-bar': 'Table.ActionBar.DesignableBar', + properties: { + [uid()]: { + type: 'void', + title: "{{t('Filter')}}", + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'filter', + }, + 'x-align': 'left', + 'x-component': 'Table.Filter', + 'x-designable-bar': 'Table.Filter.DesignableBar', + 'x-component-props': { + fieldNames: [], + }, + }, + }, + }, + }, + }, + }, + }, + option: { + type: 'void', + 'x-component': 'Select.OptionTag', + properties: { + [uid()]: { + type: 'void', + title: "{{t('View record')}}", + 'x-component': 'Action.Drawer', + 'x-component-props': { + bodyStyle: { + background: '#f0f2f5', + }, + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Tabs', + 'x-designable-bar': 'Tabs.DesignableBar', + properties: { + [uid()]: { + type: 'void', + title: "{{t('Details')}}", + 'x-designable-bar': 'Tabs.TabPane.DesignableBar', + 'x-component': 'Tabs.TabPane', + 'x-component-props': {}, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid', + 'x-component-props': { + addNewComponent: 'AddNew.PaneItem', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + } + const data = appendChild(columnSchema); + await createSchema(data); + if (isAssociation(field)) { + const defaultAppends = service.params[0]?.defaultAppends || []; + defaultAppends.push(field.name); + await service.run({ + ...service.params[0], + defaultAppends, + }); + } + } else { + const s: any = displayed.get(field.name); + const p = getSchemaPath(s); + const removed = remove(p); + await removeSchema(removed); + displayed.remove(field.name); + if (isAssociation(field)) { + const defaultAppends = service.params[0]?.defaultAppends || []; + const index = defaultAppends.indexOf(field.name); + if (index > -1) { + defaultAppends.splice(index, 1); + } + await service.run({ + ...service.params[0], + defaultAppends, + }); + } + } + // service.refresh(); + }} + /> + ))} + + + + {options.map((option) => ( + + {option.children.map((item) => ( + { + setVisible(false); + const values = await FormDialog(t('Add field'), () => { + return ( + + + + ); + }).open({ + initialValues: { + interface: item.name, + ...item.default, + key: uid(), + name: `f_${uid()}`, + }, + }); + await createCollectionField(collection?.name, values); + const data = appendChild({ + type: 'void', + 'x-component': 'Table.Column', + 'x-component-props': { + fieldName: values.name, + }, + 'x-designable-bar': 'Table.Column.DesignableBar', + }); + await createSchema(data); + await refresh(); + }} + > + {item.title} + + ))} + + ))} + + + } + > + + + ); +} diff --git a/packages/client/src/schemas/table/Sortable.tsx b/packages/client/src/schemas/table/Sortable.tsx index 4728a60d8..3fc70dfa6 100644 --- a/packages/client/src/schemas/table/Sortable.tsx +++ b/packages/client/src/schemas/table/Sortable.tsx @@ -1,42 +1,15 @@ -import React, { forwardRef, useState } from 'react'; -import { - SortableContext, - useSortable, - horizontalListSortingStrategy, - verticalListSortingStrategy, - sortableKeyboardCoordinates, -} from '@dnd-kit/sortable'; +import React, { createContext, useContext, forwardRef, useState } from 'react'; +import { SortableContext, useSortable, horizontalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { Table } from 'antd'; -import { createContext } from 'react'; -import { useContext } from 'react'; -import { range } from 'lodash'; -import parse from 'html-react-parser'; import cls from 'classnames'; +import { DndContext, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core'; import { MenuOutlined } from '@ant-design/icons'; - -import { - DndContext, - DragOverlay, - closestCenter, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors, - useDroppable, - useDraggable, -} from '@dnd-kit/core'; import { createPortal } from 'react-dom'; -import { useRef } from 'react'; import { SortableItem } from '../../components/Sortable'; -import { - findPropertyByPath, - getSchemaPath, - useDesignable, -} from '../../components/schema-renderer'; +import { findPropertyByPath, getSchemaPath, useDesignable } from '../../components/schema-renderer'; import { updateSchema } from '..'; import { Schema } from '@formily/react'; -import { isColumn, isColumnComponent } from '.'; +import { isColumn } from './utils'; export const RowDraggableContext = createContext({}); export const ColDraggableContext = createContext(null); @@ -68,9 +41,7 @@ export function SortableColumn(props) { ref={setDroppableNodeRef} // {...attributes} > - + {props.children} {/* @@ -81,22 +52,9 @@ export function SortableColumn(props) { } export function SortableBodyRow(props: any) { - const { - className, - style: prevStyle, - ['data-row-key']: dataRowKey, - ...others - } = props; - const { - isDragging, - attributes, - listeners, - setNodeRef, - setDraggableNodeRef, - overIndex, - transform, - transition, - } = useSortable({ id: dataRowKey }); + const { className, style: prevStyle, ['data-row-key']: dataRowKey, ...others } = props; + const { isDragging, attributes, listeners, setNodeRef, setDraggableNodeRef, overIndex, transform, transition } = + useSortable({ id: dataRowKey }); const style = { ...prevStyle, @@ -105,15 +63,9 @@ export function SortableBodyRow(props: any) { }; return ( - + {React.Children.map(props.children, (child, index) => ( - - {child} - + {child} ))} @@ -264,8 +214,7 @@ export function SortableBodyCell(props) { export function SortableRowHandle(props) { const { className, ...others } = props; - const { setDraggableNodeRef, attributes, listeners } = - useContext(RowDraggableContext); + const { setDraggableNodeRef, attributes, listeners } = useContext(RowDraggableContext); return setDraggableNodeRef ? ( { + const { align = 'top' } = props; + // const { schema, designable } = useDesignable(); + const { root, schema, insertAfter, remove, appendChild } = useDesignable(); + const moveToAfter = (path1, path2, extra = {}) => { + if (!path1 || !path2) { + return; + } + if (path1.join('.') === path2.join('.')) { + return; + } + const data = findPropertyByPath(root, path1); + if (!data) { + return; + } + remove(path1); + return insertAfter( + { + ...data.toJSON(), + ...extra, + }, + path2, + ); + }; + const { createSchema, removeSchema, updateSchema } = useClient(); + + const [dragOverlayContent, setDragOverlayContent] = useState(''); + return ( + { + setDragOverlayContent(event.active.data?.current?.title || ''); + // const previewRef = event.active.data?.current?.previewRef; + // if (previewRef) { + // setDragOverlayContent(previewRef?.current?.innerHTML); + // } else { + // setDragOverlayContent(''); + // } + }} + onDragEnd={async (event) => { + const path1 = event.active?.data?.current?.path; + const path2 = event.over?.data?.current?.path; + const align = event.over?.data?.current?.align; + const draggable = event.over?.data?.current?.draggable; + if (!path1 || !path2) { + return; + } + if (path1.join('.') === path2.join('.')) { + return; + } + if (!draggable) { + console.log('alignalignalignalign', align); + const p = findPropertyByPath(root, path1); + if (!p) { + return; + } + remove(path1); + const data = appendChild( + { + ...p.toJSON(), + 'x-align': align, + }, + path2, + ); + await updateSchema(data); + } else { + const data = moveToAfter(path1, path2, { + 'x-align': align, + }); + await updateSchema(data); + } + }} + > + + {dragOverlayContent} + {/*
*/} +
+ +
+
+ +
+
+ +
+ +
+
+
+ ); +}); diff --git a/packages/client/src/schemas/table/TableActionDesignableBar.tsx b/packages/client/src/schemas/table/TableActionDesignableBar.tsx new file mode 100644 index 000000000..ae458f790 --- /dev/null +++ b/packages/client/src/schemas/table/TableActionDesignableBar.tsx @@ -0,0 +1,143 @@ +import React, { useState } from 'react'; +import { Select, Dropdown, Menu, Switch, Space } from 'antd'; +import { MenuOutlined } from '@ant-design/icons'; +import { useField } from '@formily/react'; +import { FormDialog, FormLayout } from '@formily/antd'; +import cls from 'classnames'; +import { Trans, useTranslation } from 'react-i18next'; +import { useDesignable } from '..'; +import { useDisplayedMapContext, useClient } from '../../constate'; +import { DragHandle } from '../../components/Sortable'; +import { SchemaField } from '../../components/schema-renderer'; + +export const TableActionDesignableBar = () => { + const { t } = useTranslation(); + const { schema, remove, refresh, insertAfter } = useDesignable(); + const [visible, setVisible] = useState(false); + const isPopup = Object.keys(schema.properties || {}).length > 0; + const popupSchema = Object.values(schema.properties || {}).shift(); + const inActionBar = schema.parent['x-component'] === 'Table.ActionBar'; + const displayed = useDisplayedMapContext(); + const field = useField(); + const { createSchema, removeSchema, updateSchema } = useClient(); + const popupComponent = popupSchema?.['x-component'] || 'Action.Drawer'; + return ( +
+ { + e.stopPropagation(); + }} + className={cls('designable-bar-actions', { active: visible })} + > + + + { + setVisible(visible); + }} + overlay={ + + { + setVisible(false); + const values = await FormDialog(t('Edit button'), () => { + return ( + + + + ); + }).open({ + initialValues: { + title: schema['title'], + icon: schema['x-component-props']?.['icon'], + }, + }); + schema['title'] = values.title; + schema['x-component-props']['icon'] = values.icon; + field.componentProps.icon = values.icon; + field.title = values.title; + updateSchema(schema); + refresh(); + }} + > + {t('Edit button')} + + {isPopup && ( + + + Open in + + + + )} + {!inActionBar && ( + + {t('Triggered when the row is clicked')}    + + + )} + + { + const displayName = schema?.['x-decorator-props']?.['displayName']; + const data = remove(); + await removeSchema(data); + if (displayName) { + displayed.remove(displayName); + } + setVisible(false); + }} + > + {t('Hide')} + + + } + > + + + + +
+ ); +}; diff --git a/packages/client/src/schemas/table/TableCell.tsx b/packages/client/src/schemas/table/TableCell.tsx new file mode 100644 index 000000000..1c44f9579 --- /dev/null +++ b/packages/client/src/schemas/table/TableCell.tsx @@ -0,0 +1,54 @@ +import React, { useContext } from 'react'; +import { observer, RecursionField, Schema } from '@formily/react'; +import { cloneDeepWith, set } from 'lodash'; +import { uid, merge } from '@formily/shared'; +import { CollectionFieldContext,TableRowContext } from './context'; +import { Table } from '.'; + +export const TableCell = observer((props: any) => { + const ctx = useContext(TableRowContext); + const schema = props.schema; + const collectionField = useContext(CollectionFieldContext); + if (schema['x-component'] === 'Table.Operation') { + return ; + } + let uiSchema = collectionField?.uiSchema as Schema; + if (uiSchema?.['x-component'] === 'Upload.Attachment') { + uiSchema = cloneDeepWith(uiSchema); + set(uiSchema, 'x-component-props.size', 'small'); + } + const componentProps = merge(uiSchema?.['x-component-props'] || {}, schema?.['x-component-props'] || {}, { + arrayMerge: (t, s) => s, + }); + console.log('Table.Cell', collectionField?.interface, componentProps); + return ( +
+ +
+ ); +}); diff --git a/packages/client/src/schemas/table/TableColumn.tsx b/packages/client/src/schemas/table/TableColumn.tsx new file mode 100644 index 000000000..43287130c --- /dev/null +++ b/packages/client/src/schemas/table/TableColumn.tsx @@ -0,0 +1,24 @@ +import React, { useContext, useEffect } from 'react'; +import { observer } from '@formily/react'; +import { useDesignable } from '..'; +import { useDisplayedMapContext } from '../../constate'; +import { useCompile } from '../../hooks/useCompile'; +import { CollectionFieldContext } from './context'; + +export const TableColumn = observer((props: any) => { + const collectionField = useContext(CollectionFieldContext); + const { schema, DesignableBar } = useDesignable(); + const compile = useCompile(); + const displayed = useDisplayedMapContext(); + useEffect(() => { + if (collectionField?.name) { + displayed.set(collectionField.name, schema); + } + }, [collectionField, schema]); + return ( +
+ {compile(schema.title || collectionField?.uiSchema?.title)} + +
+ ); +}); diff --git a/packages/client/src/schemas/table/TableColumnDesignableBar.tsx b/packages/client/src/schemas/table/TableColumnDesignableBar.tsx new file mode 100644 index 000000000..b1964443a --- /dev/null +++ b/packages/client/src/schemas/table/TableColumnDesignableBar.tsx @@ -0,0 +1,158 @@ +import React, { useContext, useState } from 'react'; +import { useField } from '@formily/react'; +import { Select, Dropdown, Menu, Space } from 'antd'; +import { set } from 'lodash'; +import cls from 'classnames'; +import { MenuOutlined } from '@ant-design/icons'; +import { FormDialog, FormLayout, Submit } from '@formily/antd'; +import { useTranslation } from 'react-i18next'; +import { SchemaField } from '../../components/schema-renderer'; +import { useCollectionsContext, useDisplayedMapContext, useClient } from '../../constate'; +import { DragHandle } from '../../components/Sortable'; +import { useDesignable } from '..'; +import { useCompile } from '../../hooks/useCompile'; +import { useTable } from './hooks/useTable'; +import { CollectionFieldContext } from './hooks/useTableColumns'; + +export const TableColumnDesignableBar = () => { + const field = useField(); + const { t } = useTranslation(); + const compile = useCompile(); + const { service, refresh: refreshTable } = useTable(); + // const fieldSchema = useFieldSchema(); + const { schema, remove, refresh, insertAfter } = useDesignable(); + const [visible, setVisible] = useState(false); + const displayed = useDisplayedMapContext(); + const { getFieldsByCollection } = useCollectionsContext(); + const collectionField = useContext(CollectionFieldContext); + const { createSchema, removeSchema, updateSchema } = useClient(); + console.log('displayed.map', displayed.map); + return ( +
+ { + e.stopPropagation(); + }} + className={cls('designable-bar-actions', { active: visible })} + > + + + { + setVisible(visible); + }} + overlay={ + + { + setVisible(false); + const values = await FormDialog(t('Custom name'), () => { + return ( + + + + ); + }).open({ + initialValues: { + fieldName: collectionField?.uiSchema?.title, + title: schema['title'], + }, + }); + const title = values.title || null; + field.title = title; + schema.title = title; + refresh(); + await updateSchema({ + key: schema['key'], + title: title, + }); + }} + > + {t('Custom column name')} + + {collectionField?.interface === 'linkTo' && ( + +
+ {t('Label field')}{' '} + { + const componentProps = schema['x-component-props'] || {}; + set(componentProps, 'pagination.defaultPageSize', value); + set(componentProps, 'pagination.pageSize', value); + schema['x-component-props'] = componentProps; + field.componentProps.pagination.pageSize = value; + field.componentProps.pagination.defaultPageSize = value; + refresh(); + updateSchema(schema); + setVisible(false); + }} + defaultValue={defaultPageSize} + > + 10 + 20 + 50 + 100 + + {' items per page'} + + + + { + Modal.confirm({ + title: t('Delete block'), + content: t('Are you sure you want to delete it?'), + onOk: async () => { + const removed = deepRemove(); + // console.log({ removed }) + const last = removed.pop(); + await removeSchema(last); + setVisible(false); + }, + }); + }} + > + {t('Delete')} + +
+ } + > + +
+
+
+
+ ); +}); diff --git a/packages/client/src/schemas/table/TableExportActionDesignableBar.tsx b/packages/client/src/schemas/table/TableExportActionDesignableBar.tsx new file mode 100644 index 000000000..b00603b60 --- /dev/null +++ b/packages/client/src/schemas/table/TableExportActionDesignableBar.tsx @@ -0,0 +1,134 @@ +import React, { useState } from 'react'; +import { Dropdown, Menu, Space } from 'antd'; +import { useField } from '@formily/react'; +import { FormDialog, FormLayout } from '@formily/antd'; +import { MenuOutlined } from '@ant-design/icons'; +import cls from 'classnames'; +import { Trans, useTranslation } from 'react-i18next'; +import { DragHandle } from '../../components/Sortable'; +import { useDesignable } from '..'; +import { useCollectionContext, useDisplayedMapContext, useClient } from '../../constate'; +import SwitchMenuItem from '../../components/SwitchMenuItem'; +import { SchemaField } from '../../components/schema-renderer'; + +export const TableExportActionDesignableBar = () => { + const { t } = useTranslation(); + const { schema, remove, refresh, insertAfter } = useDesignable(); + const [visible, setVisible] = useState(false); + const displayed = useDisplayedMapContext(); + const { fields } = useCollectionContext(); + const field = useField(); + const { createSchema, removeSchema, updateSchema } = useClient(); + let fieldNames = field.componentProps.fieldNames || []; + if (fieldNames.length === 0) { + fieldNames = fields.map((field) => field.name); + } + return ( +
+ { + e.stopPropagation(); + }} + className={cls('designable-bar-actions', { active: visible })} + > + + + { + setVisible(visible); + }} + overlay={ + + + {fields.map((collectionField) => ( + { + if (checked) { + fieldNames.push(collectionField.name); + } else { + const index = fieldNames.indexOf(collectionField.name); + if (index > -1) { + fieldNames.splice(index, 1); + } + } + console.log({ fieldNames, field }); + schema['x-component-props']['fieldNames'] = fieldNames; + field.componentProps.fieldNames = fieldNames; + updateSchema(schema); + }} + /> + ))} + + + { + setVisible(false); + const values = await FormDialog(t('Edit button'), () => { + return ( + + + + ); + }).open({ + initialValues: { + title: schema['title'], + icon: schema['x-component-props']?.['icon'], + }, + }); + schema['title'] = values.title; + schema['x-component-props']['icon'] = values.icon; + field.componentProps.icon = values.icon; + field.title = values.title; + updateSchema(schema); + refresh(); + }} + > + {t('Edit button')} + + + { + const displayName = schema?.['x-decorator-props']?.['displayName']; + const data = remove(); + await removeSchema(data); + if (displayName) { + displayed.remove(displayName); + } + setVisible(false); + }} + > + {t('Hide')} + + + } + > + + + + +
+ ); +}; diff --git a/packages/client/src/schemas/table/TableFilter.tsx b/packages/client/src/schemas/table/TableFilter.tsx new file mode 100644 index 000000000..42416e7bd --- /dev/null +++ b/packages/client/src/schemas/table/TableFilter.tsx @@ -0,0 +1,92 @@ +import React, { useMemo, useState } from 'react'; +import { Popover, Button, } from 'antd'; +import { useTranslation } from 'react-i18next'; +import flatten from 'flat'; +import { createForm } from '@formily/core'; +import { FormProvider, observer } from '@formily/react'; +import { FormButtonGroup, Submit } from '@formily/antd'; +import { interfaces } from '../database-field/interfaces'; +import { useDesignable } from '..'; +import { useCollectionContext, } from '../../constate'; +import { SchemaField } from '../../components/schema-renderer'; +import { useTable } from './hooks/useTable'; +import { useCompile } from '../../hooks/useCompile'; +import IconPicker from '../../components/icon-picker'; + +export const TableFilter = observer((props: any) => { + const { service } = useTable(); + const { fieldNames = [] } = props; + const compile = useCompile(); + const { t } = useTranslation(); + const { schema, DesignableBar } = useDesignable(); + const form = useMemo(() => createForm(), []); + const { fields = [] } = useCollectionContext(); + const [visible, setVisible] = useState(false); + const obj = flatten(form.values.filter || {}); + console.log('flatten', obj, Object.values(obj)); + const count = Object.values(obj).filter((i) => (Array.isArray(i) ? i.length : i)).length; + const icon = props.icon || 'FilterOutlined'; + const properties = fieldsToFilterColumns(fields, { fieldNames }); + schema.mapProperties((p) => { + properties[p.name] = p; + }); + return ( + + + + + + { + const { filter } = form.values; + console.log('Table.Filter', form.values); + setVisible(false); + return service.run({ + ...service.params[0], + filter, + }); + }} + > + {t('Submit')} + + + + + } + > + + + ); +}); diff --git a/packages/client/src/schemas/table/TableFilterDesignableBar.tsx b/packages/client/src/schemas/table/TableFilterDesignableBar.tsx new file mode 100644 index 000000000..d95b21757 --- /dev/null +++ b/packages/client/src/schemas/table/TableFilterDesignableBar.tsx @@ -0,0 +1,140 @@ +import React, { useState } from 'react'; +import { Dropdown, Menu, Space } from 'antd'; +import { useField } from '@formily/react'; +import { FormDialog, FormLayout } from '@formily/antd'; +import { MenuOutlined } from '@ant-design/icons'; +import cls from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { DragHandle } from '../../components/Sortable'; +import { interfaces } from '../database-field/interfaces'; +import { useDesignable } from '..'; +import { useCollectionContext, useDisplayedMapContext, useClient } from '../../constate'; +import SwitchMenuItem from '../../components/SwitchMenuItem'; +import { SchemaField } from '../../components/schema-renderer'; + +export const TableFilterDesignableBar = () => { + const { t } = useTranslation(); + const { schema, remove, refresh, insertAfter } = useDesignable(); + const [visible, setVisible] = useState(false); + const displayed = useDisplayedMapContext(); + const { fields } = useCollectionContext(); + const field = useField(); + const { createSchema, removeSchema, updateSchema } = useClient(); + let fieldNames = field.componentProps.fieldNames || []; + if (fieldNames.length === 0) { + fieldNames = fields.map((field) => field.name); + } + return ( +
+ { + e.stopPropagation(); + }} + className={cls('designable-bar-actions', { active: visible })} + > + + + { + setVisible(visible); + }} + overlay={ + + + {fields + .filter((collectionField) => { + const option = interfaces.get(collectionField.interface); + return option?.operations?.length; + }) + .map((collectionField) => ( + { + if (checked) { + fieldNames.push(collectionField.name); + } else { + const index = fieldNames.indexOf(collectionField.name); + if (index > -1) { + fieldNames.splice(index, 1); + } + } + console.log({ fieldNames, field }); + schema['x-component-props']['fieldNames'] = fieldNames; + field.componentProps.fieldNames = fieldNames; + updateSchema(schema); + }} + /> + ))} + + + { + setVisible(false); + const values = await FormDialog(t('Edit button'), () => { + return ( + + + + ); + }).open({ + initialValues: { + title: schema['title'], + icon: schema['x-component-props']?.['icon'], + }, + }); + schema['title'] = values.title; + schema['x-component-props']['icon'] = values.icon; + field.componentProps.icon = values.icon; + field.title = values.title; + updateSchema(schema); + refresh(); + }} + > + {t('Edit button')} + + + { + const displayName = schema?.['x-decorator-props']?.['displayName']; + const data = remove(); + await removeSchema(data); + if (displayName) { + displayed.remove(displayName); + } + setVisible(false); + }} + > + {t('Hide')} + + + } + > + + + + +
+ ); +}; diff --git a/packages/client/src/schemas/table/TableIndex.tsx b/packages/client/src/schemas/table/TableIndex.tsx new file mode 100644 index 000000000..f7dbc944a --- /dev/null +++ b/packages/client/src/schemas/table/TableIndex.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { observer } from '@formily/react'; +import { useTableIndex } from './hooks/useTableIndex'; + +export const TableIndex = observer(() => { + const index = useTableIndex(); + return {index + 1}; +}); diff --git a/packages/client/src/schemas/table/TableMain.tsx b/packages/client/src/schemas/table/TableMain.tsx new file mode 100644 index 000000000..faa0723d0 --- /dev/null +++ b/packages/client/src/schemas/table/TableMain.tsx @@ -0,0 +1,139 @@ +import React, { DndContext } from '@dnd-kit/core'; +import { useState } from 'react'; +import { Table as AntdTable } from 'antd'; +import { findIndex } from 'lodash'; +import cls from 'classnames'; +import { RecursionField, Schema } from '@formily/react'; +import { isValid } from '@formily/shared'; +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; +import { SortableBodyRow, SortableHeaderCell, SortableHeaderRow } from './Sortable'; +import { TableRowContext } from './context'; +import { useDataSource } from './hooks/useDataSource'; +import { useDefaultRowSelection } from './hooks/useDefaultRowSelection'; +import { useTable } from './hooks/useTable'; +import { useTableActionBars } from './hooks/useTableActionBars'; +import { useTableColumns } from './hooks/useTableColumns'; +import { Table } from '.'; + +export const TableMain = () => { + const { + resource, + selectedRowKeys, + setSelectedRowKeys, + service, + field, + props: { rowKey, dragSort, showIndex, onSelect, useRowSelection = useDefaultRowSelection }, + refresh, + } = useTable(); + const columns = useTableColumns(); + const dataSource = useDataSource(); + const actionBars = useTableActionBars(); + const [html, setHtml] = useState(''); + const { type } = useRowSelection(); + return ( +
+ { + const fromId = event.active?.id as any; + const toId = event.over?.id as any; + if (isValid(fromId) && isValid(toId)) { + const fromIndex = findIndex(field.value, (item) => item[rowKey] === fromId); + const toIndex = findIndex(field.value, (item) => item[rowKey] === toId); + console.log({ fromId, toId, fromIndex, toIndex }); + field.move(fromIndex, toIndex); + refresh(); + await resource.sort({ + resourceKey: fromId, + target: { + [rowKey]: toId, + }, + }); + await service.refresh(); + } + }} + > + {actionBars.map((actionBar) => ( + + ))} + + {}} + loading={service?.loading} + rowKey={rowKey} + dataSource={dataSource} + columns={columns} + // components={{ + // body: { + // row: DragableBodyRow, + // }, + // }} + components={{ + header: { + row: SortableHeaderRow, + cell: SortableHeaderCell, + }, + body: { + // wrapper: (props) => { + // return ( + // + // + //
+ // + // {props.children} + // + // ); + // }, + row: SortableBodyRow, + // cell: SortableBodyCell, + }, + }} + rowSelection={{ + type: type || 'checkbox', + selectedRowKeys, + onChange: (rowKeys, rows) => { + setSelectedRowKeys(rowKeys); + onSelect && onSelect(rowKeys, rows); + }, + renderCell: (checked, record, _, originNode) => { + const index = findIndex(field.value, (item) => item[rowKey] === record[rowKey]); + return ( + +
+ {dragSort && } + {showIndex && } + {originNode} +
+
+ ); + }, + }} + /> + + + +
+ ); +}; diff --git a/packages/client/src/schemas/table/TableOperation.tsx b/packages/client/src/schemas/table/TableOperation.tsx new file mode 100644 index 000000000..4d202cf8a --- /dev/null +++ b/packages/client/src/schemas/table/TableOperation.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { observer } from '@formily/react'; +import { useTranslation } from 'react-i18next'; +import { useDesignable } from '..'; +import { Table } from '.'; + +export const TableOperation = observer((props: any) => { + const { designable, schema } = useDesignable(); + const { t } = useTranslation(); + return ( +
+ {t('Operations')} + +
+ ); +}); diff --git a/packages/client/src/schemas/table/TableOperationCell.tsx b/packages/client/src/schemas/table/TableOperationCell.tsx new file mode 100644 index 000000000..22fced4f0 --- /dev/null +++ b/packages/client/src/schemas/table/TableOperationCell.tsx @@ -0,0 +1,13 @@ +import React, { useContext } from 'react'; +import { observer, RecursionField } from '@formily/react'; +import { TableRowContext } from './context'; + +export const TableOperationCell = observer((props: any) => { + const ctx = useContext(TableRowContext); + const schema = props.schema; + return ( +
+ +
+ ); +}); diff --git a/packages/client/src/schemas/table/TableOperationDesignableBar.tsx b/packages/client/src/schemas/table/TableOperationDesignableBar.tsx new file mode 100644 index 000000000..5a0c97996 --- /dev/null +++ b/packages/client/src/schemas/table/TableOperationDesignableBar.tsx @@ -0,0 +1,194 @@ +import React, { useState } from 'react'; +import { Dropdown, Menu, Space } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { uid, merge } from '@formily/shared'; +import cls from 'classnames'; +import { MenuOutlined } from '@ant-design/icons'; +import { useDesignable, ISchema } from '..'; +import { getSchemaPath } from '../../components/schema-renderer'; +import { useClient } from '../../constate'; +import SwitchMenuItem from '../../components/SwitchMenuItem'; +import { DragHandle } from '../../components/Sortable'; + +export const TableOperationDesignableBar = () => { + const { t } = useTranslation(); + const { schema: columnSchema } = useDesignable(); + const groupSchema = Object.values(columnSchema.properties || {}).shift(); + const groupPath = getSchemaPath(groupSchema); + const { schema, remove, refresh, appendChild } = useDesignable(groupPath); + const [visible, setVisible] = useState(false); + const { createSchema, removeSchema, updateSchema } = useClient(); + + const map = new Map(); + schema.mapProperties((s) => { + if (!s['x-action-type']) { + return; + } + map.set(s['x-action-type'], s.name); + }); + const path = getSchemaPath(schema); + return ( +
+ { + e.stopPropagation(); + }} + className={cls('designable-bar-actions', { active: visible })} + > + + + { + setVisible(visible); + }} + overlay={ + + + {[ + { title: t('View'), name: 'view' }, + { title: t('Edit'), name: 'update' }, + { title: t('Delete'), name: 'destroy' }, + ].map((item) => ( + { + if (checked) { + const s = generateMenuActionSchema(item.name); + const data = appendChild(s); + await createSchema(data); + } else if (map.get(item.name)) { + const removed = remove([...path, map.get(item.name)]); + await removeSchema(removed); + } + }} + /> + ))} + + + + {t('Function')} + {t('Popup form')} + {t('Flexible popup')} + + + } + > + + + + +
+ ); +}; +function generateMenuActionSchema(type) { + const actions: { [key: string]: ISchema } = { + view: { + key: uid(), + name: uid(), + type: 'void', + title: "{{ t('View') }}", + 'x-component': 'Action', + 'x-component-props': { + type: 'link', + }, + 'x-designable-bar': 'Table.Action.DesignableBar', + 'x-action-type': 'view', + properties: { + [uid()]: { + type: 'void', + title: "{{ t('View record') }}", + 'x-component': 'Action.Drawer', + 'x-component-props': { + bodyStyle: { + background: '#f0f2f5', + // paddingTop: 0, + }, + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Tabs', + 'x-designable-bar': 'Tabs.DesignableBar', + properties: { + [uid()]: { + type: 'void', + title: "{{ t('Details') }}", + 'x-designable-bar': 'Tabs.TabPane.DesignableBar', + 'x-component': 'Tabs.TabPane', + 'x-component-props': {}, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid', + 'x-component-props': { + addNewComponent: 'AddNew.PaneItem', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + update: { + key: uid(), + name: uid(), + type: 'void', + title: "{{ t('Edit') }}", + 'x-component': 'Action', + 'x-component-props': { + type: 'link', + }, + 'x-designable-bar': 'Table.Action.DesignableBar', + 'x-action-type': 'update', + properties: { + [uid()]: { + type: 'void', + title: "{{ t('Edit record') }}", + 'x-decorator': 'Form', + 'x-decorator-props': { + useResource: '{{ Table.useResource }}', + useValues: '{{ Table.useTableRowRecord }}', + }, + 'x-component': 'Action.Drawer', + 'x-component-props': { + useOkAction: '{{ Table.useTableUpdateAction }}', + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid', + 'x-component-props': { + addNewComponent: 'AddNew.FormItem', + }, + }, + }, + }, + }, + }, + destroy: { + key: uid(), + name: uid(), + type: 'void', + title: "{{ t('Delete') }}", + 'x-component': 'Action', + 'x-designable-bar': 'Table.Action.DesignableBar', + 'x-action-type': 'destroy', + 'x-component-props': { + useAction: '{{ Table.useTableDestroyAction }}', + type: 'link', + confirm: { + title: "{{ t('Delete record') }}", + content: "{{ t('Are you sure you want to delete it?') }}", + }, + }, + }, + }; + return actions[type]; +} diff --git a/packages/client/src/schemas/table/TablePagination.tsx b/packages/client/src/schemas/table/TablePagination.tsx new file mode 100644 index 000000000..f22eada11 --- /dev/null +++ b/packages/client/src/schemas/table/TablePagination.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Pagination } from 'antd'; +import { observer } from '@formily/react'; +import { useTable } from './hooks/useTable'; +import { useTotal } from './hooks/useTotal'; + +export const TablePagination = observer(() => { + const { service, pagination, setPagination, props } = useTable(); + if (!pagination || Object.keys(pagination).length === 0) { + return null; + } + const { clientSidePagination } = props; + const total = useTotal(); + const { page = 1 } = pagination; + return ( +
+ { + const page = pagination.pageSize !== pageSize ? 1 : current; + setPagination({ + page, + pageSize, + }); + // if (clientSidePagination) { + // return; + // } + // service.run({ + // ...service.params[0], + // page, + // pageSize, + // }); + }} + /> +
+ ); +}); diff --git a/packages/client/src/schemas/table/TableProvider.tsx b/packages/client/src/schemas/table/TableProvider.tsx new file mode 100644 index 000000000..47cecc4b4 --- /dev/null +++ b/packages/client/src/schemas/table/TableProvider.tsx @@ -0,0 +1,103 @@ +import React, { useState } from 'react'; +import { ArrayField } from '@formily/core'; +import { useField } from '@formily/react'; +import { uid } from '@formily/shared'; +import useRequest from '@ahooksjs/use-request'; +import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect'; +import { useResource as useGeneralResource } from '../../hooks/useResource'; +import { useDesignable } from '..'; +import { useCollectionContext } from '../../constate'; +import { useDefaultSelectedRowKeys } from './hooks/useDefaultSelectedRowKeys'; +import { usePagination } from './hooks/usePagination'; +import { useCollectionFields } from './hooks/useCollectionFields'; +import {TableContext} from './context'; +import {TableMain} from './TableMain'; + +export const TableProvider = (props: any) => { + const { + rowKey = 'id', + dataRequest, + useResource = useGeneralResource, + defaultSelectedRowKeys, + useSelectedRowKeys = useDefaultSelectedRowKeys, + ...others + } = props; + const { schema } = useDesignable(); + const field = useField(); + const [pagination, setPagination] = usePagination(); + const { selectedRowKeys, setSelectedRowKeys } = useSelectedRowKeys(); + console.log('props.useSelectedRowKeys', selectedRowKeys); + const [, refresh] = useState(uid()); + const { resource } = useResource(); + const { sortableField } = useCollectionContext(); + const dragSort = props.dragSort; + const collectionFields = useCollectionFields(schema); + // console.log({ collectionFields, pagination }); + const getDefaultParams = () => { + const defaultParams = { ...pagination }; + if (dragSort) { + defaultParams['sort'] = [sortableField || 'sort']; + } else { + defaultParams['sort'] = (props.defaultSort || []).join(','); + } + defaultParams['defaultAppends'] = [...(props.defaultAppends || []), ...collectionFields]; + if (props.defaultFilter) { + defaultParams['defaultFilter'] = props.defaultFilter; + } + console.log({ defaultParams }); + return defaultParams; + }; + const service = useRequest( + (params?: any) => { + if (!resource) { + return Promise.resolve({ + list: field.value, + total: field?.value?.length, + }); + } + return resource.list(params).then((res) => { + return { + list: res?.data || [], + total: res?.meta?.count || res?.data?.length, + }; + }); + }, + { + onSuccess(data: any) { + field.setValue(data?.list || []); + }, + manual: true, + // defaultParams: [getDefaultParams()], + }, + ); + useDeepCompareEffectNoCheck(() => { + service.run(getDefaultParams()); + }, [pagination?.pageSize, pagination?.page, props.dragSort, props.defaultSort, props.defaultFilter]); + return ( + { + const { page = 1, pageSize } = pagination; + const total = props.clientSidePagination ? field?.value?.length : service?.data?.total; + const maxPage = Math.ceil(total / pageSize); + if (page > maxPage) { + setPagination({ page: maxPage }); + } else { + refresh(uid()); + } + }, + selectedRowKeys, + setSelectedRowKeys, + pagination, + setPagination, + service, + field, + schema, + props: { ...others, rowKey, dataRequest }, + }} + > + + + ); +}; diff --git a/packages/client/src/schemas/table/TableSortHandle.tsx b/packages/client/src/schemas/table/TableSortHandle.tsx new file mode 100644 index 000000000..226874d13 --- /dev/null +++ b/packages/client/src/schemas/table/TableSortHandle.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { observer } from '@formily/react'; +import { SortableRowHandle } from './Sortable'; + +export const TableSortHandle = observer((props: any) => { + return ; +}); diff --git a/packages/client/src/schemas/table/context.ts b/packages/client/src/schemas/table/context.ts new file mode 100644 index 000000000..4e6e56764 --- /dev/null +++ b/packages/client/src/schemas/table/context.ts @@ -0,0 +1,6 @@ +import {createContext} from 'react'; +import {ITableContext,ITableRowContext} from './types'; + +export const TableContext = createContext({} as any); +export const CollectionFieldContext = createContext(null); +export const TableRowContext = createContext(null); diff --git a/packages/client/src/schemas/table/hooks/useActionLogDetailsResource.ts b/packages/client/src/schemas/table/hooks/useActionLogDetailsResource.ts new file mode 100644 index 000000000..d0ecefd2c --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useActionLogDetailsResource.ts @@ -0,0 +1,38 @@ +import { useContext, useEffect } from 'react'; +import { useTable } from './useTable'; +import useRequest from '@ahooksjs/use-request'; +import { useCollectionContext, useResourceRequest } from '../../../constate'; +import { VisibleContext } from '../../../context'; +import { TableRowContext } from '../context'; + +export const useActionLogDetailsResource = ({ onSuccess }) => { + const { props } = useTable(); + const { collection } = useCollectionContext(); + const ctx = useContext(TableRowContext); + const resource = useResourceRequest({ + resourceName: 'action_logs', + resourceKey: ctx.record[props.rowKey], + }); + const service = useRequest( + (params?: any) => { + return resource.get({ + ...params, + appends: ['changes', 'user', 'collection'], + }); + }, + { + formatResult: (result) => result?.data, + onSuccess, + manual: true, + }, + ); + const [visible] = useContext(VisibleContext); + + useEffect(() => { + if (visible) { + service.run({}); + } + }, [visible]); + + return { resource, service, initialValues: service.data, ...service }; +}; diff --git a/packages/client/src/schemas/table/hooks/useActionLogsResource.ts b/packages/client/src/schemas/table/hooks/useActionLogsResource.ts new file mode 100644 index 000000000..aa6a5f1bd --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useActionLogsResource.ts @@ -0,0 +1,35 @@ +import React, { useContext, createContext, useEffect, useState } from 'react'; +import { useTable } from './useTable'; +import { ListOptions, Resource } from '../../../resource'; +import { TableRowContext } from '../context'; +import { useResourceRequest } from '../../../constate'; + +export const useActionLogsResource = (options: any = {}) => { + const { props } = useTable(); + const ctx = useContext(TableRowContext); + + class ActionLogoResource extends Resource { + list(options?: ListOptions) { + console.log({ options }); + let defaultFilter = options?.defaultFilter; + if (ctx?.record) { + const extra = { + index: ctx?.record?.id, + collection_name: props.collectionName, + }; + if (defaultFilter) { + defaultFilter = { and: [defaultFilter, extra] }; + } else { + defaultFilter = extra; + } + } + return super.list({ ...options, defaultFilter }); + } + } + + const resource = useResourceRequest('action_logs', ActionLogoResource); + + return { + resource, + }; +}; diff --git a/packages/client/src/schemas/table/hooks/useCollectionFields.ts b/packages/client/src/schemas/table/hooks/useCollectionFields.ts new file mode 100644 index 000000000..bf55b475d --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useCollectionFields.ts @@ -0,0 +1,24 @@ +import { Schema } from '@formily/react'; +import { isColumn } from '../utils'; + +export const useCollectionFields = (schema: Schema) => { + const columns = schema.reduceProperties((columns, current) => { + if (isColumn(current)) { + if (current['x-hidden']) { + return columns; + } + if (current['x-display'] && current['x-display'] !== 'visible') { + return columns; + } + return [...columns, current]; + } + return [...columns]; + }, []); + + return columns + .map((column) => { + const columnProps = column['x-component-props'] || {}; + return columnProps.fieldName; + }) + .filter(Boolean); +}; diff --git a/packages/client/src/schemas/table/hooks/useDataSource.ts b/packages/client/src/schemas/table/hooks/useDataSource.ts new file mode 100644 index 000000000..a4d10b82b --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useDataSource.ts @@ -0,0 +1,17 @@ +import { useTable } from './useTable'; + +export const useDataSource = () => { + const { + pagination, + field, + props: { clientSidePagination, dataRequest }, + } = useTable(); + let dataSource = field.value; + // if (pagination && (clientSidePagination || !dataRequest)) { + // const { page = 1, pageSize } = pagination; + // const startIndex = (page - 1) * pageSize; + // const endIndex = startIndex + pageSize - 1; + // dataSource = field.value?.slice(startIndex, endIndex + 1); + // } + return dataSource; +}; diff --git a/packages/client/src/schemas/table/hooks/useDefaultRowSelection.ts b/packages/client/src/schemas/table/hooks/useDefaultRowSelection.ts new file mode 100644 index 000000000..6b0deb511 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useDefaultRowSelection.ts @@ -0,0 +1,5 @@ +export const useDefaultRowSelection = () => { + return { + type: 'checkbox', + }; +}; diff --git a/packages/client/src/schemas/table/hooks/useDefaultSelectedRowKeys.ts b/packages/client/src/schemas/table/hooks/useDefaultSelectedRowKeys.ts new file mode 100644 index 000000000..cda961352 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useDefaultSelectedRowKeys.ts @@ -0,0 +1,6 @@ +import { useState } from "react"; + +export const useDefaultSelectedRowKeys = () => { + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + return { selectedRowKeys, setSelectedRowKeys }; +}; diff --git a/packages/client/src/schemas/table/hooks/usePagination.ts b/packages/client/src/schemas/table/hooks/usePagination.ts new file mode 100644 index 000000000..66a245afb --- /dev/null +++ b/packages/client/src/schemas/table/hooks/usePagination.ts @@ -0,0 +1,33 @@ +import { ArrayField } from '@formily/core'; +import { useField } from '@formily/react'; + +export const usePagination = () => { + const field = useField(); + const paginationProps = field.componentProps.pagination; + + let pagination = paginationProps; + + // const [pagination, setPagination] = useState(() => { + // if (!paginationProps) { + // return false; + // } + // const { defaultPageSize = 10, ...others } = paginationProps; + // return { page: 1, pageSize: defaultPageSize, ...others }; + // }); + + // useEffect(() => { + // if (!paginationProps) { + // return setPagination(false); + // } + // const { defaultPageSize = 10, ...others } = paginationProps; + // setPagination({ page: 1, pageSize: defaultPageSize, ...others }); + // }, [paginationProps]); + + return [ + pagination, + (params) => { + const defaults = field.componentProps.pagination; + field.componentProps.pagination = { ...defaults, ...params }; + }, + ]; +}; diff --git a/packages/client/src/schemas/table/hooks/useResource.ts b/packages/client/src/schemas/table/hooks/useResource.ts new file mode 100644 index 000000000..24b405302 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useResource.ts @@ -0,0 +1,51 @@ +import { useContext } from 'react'; +import { Schema } from '@formily/react'; +import { useTable } from './useTable'; +import { TableRowContext } from '../context'; +import useRequest from '@ahooksjs/use-request'; +import { useCollectionContext, useResourceRequest } from '../../../constate'; +import { useDesignable } from '../..'; + +export const useResource = ({ onSuccess, manual = true }) => { + const { props } = useTable(); + const { collection } = useCollectionContext(); + const ctx = useContext(TableRowContext); + const resource = useResourceRequest({ + resourceName: collection?.name || props.collectionName, + resourceKey: ctx.record[props.rowKey], + }); + const { schema } = useDesignable(); + const fieldFields = (schema: Schema) => { + const names = []; + schema.reduceProperties((buf, current) => { + if (current['x-component'] === 'Form.Field') { + const fieldName = current['x-component-props']?.['fieldName']; + if (fieldName) { + buf.push(fieldName); + } + } else { + const fieldNames = fieldFields(current); + buf.push(...fieldNames); + } + return buf; + }, names); + return names; + }; + console.log( + 'collection?.name || props.collectionName', + collection?.name || props.collectionName, + // fieldFields(schema), + ); + const service = useRequest( + (params?: any) => { + console.log('Table.useResource', params); + return resource.get({ ...params, appends: fieldFields(schema) }); + }, + { + formatResult: (result) => result?.data, + onSuccess, + manual, + }, + ); + return { resource, service, initialValues: service.data, ...service }; +}; diff --git a/packages/client/src/schemas/table/hooks/useTable.ts b/packages/client/src/schemas/table/hooks/useTable.ts new file mode 100644 index 000000000..d9407cc74 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTable.ts @@ -0,0 +1,6 @@ +import React, { useContext,createContext, useEffect, useState } from 'react'; +import {TableContext} from '../context'; + +export const useTable = () => { + return useContext(TableContext); +}; diff --git a/packages/client/src/schemas/table/hooks/useTableActionBars.ts b/packages/client/src/schemas/table/hooks/useTableActionBars.ts new file mode 100644 index 000000000..6e078898b --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableActionBars.ts @@ -0,0 +1,18 @@ +import { useTable } from './useTable'; + +export const useTableActionBars = () => { + const { + field, + schema, + props: { rowKey }, + } = useTable(); + + const bars = schema.reduceProperties((bars, current) => { + if (current['x-component'] === 'Table.ActionBar') { + return [...bars, current]; + } + return [...bars]; + }, []); + + return bars; +}; diff --git a/packages/client/src/schemas/table/hooks/useTableColumns.tsx b/packages/client/src/schemas/table/hooks/useTableColumns.tsx new file mode 100644 index 000000000..a5ed31ea7 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableColumns.tsx @@ -0,0 +1,69 @@ +import React,{ createContext } from 'react'; +import { findIndex } from 'lodash'; +import { RecursionField, Schema } from '@formily/react'; +import { useDesignable } from '../..'; +import { useTable } from './useTable'; +import {TableRowContext} from '../context'; +import { useCollectionContext } from '../../../constate'; +import {Table, } from '..'; +import {isColumn} from '../utils' +import {AddColumn } from '../AddColumn'; +import {CollectionFieldContext}from '../context'; + +export const useTableColumns = () => { + const { + field, + schema, + props: { rowKey }, + } = useTable(); + const { designable } = useDesignable(); + + const { getField } = useCollectionContext(); + + const columnSchemas = schema.reduceProperties((columns, current) => { + if (isColumn(current)) { + if (current['x-hidden']) { + return columns; + } + if (current['x-display'] && current['x-display'] !== 'visible') { + return columns; + } + return [...columns, current]; + } + return [...columns]; + }, []); + + const columns: any[] = [].concat( + columnSchemas.map((column: Schema) => { + const columnProps = column['x-component-props'] || {}; + const collectionField = getField(columnProps.fieldName); + return { + title: ( + + + + ), + dataIndex: column.name, + ...columnProps, + render: (_: any, record: any) => { + const index = findIndex(field.value, (item) => item[rowKey] === record[rowKey]); + return ( + + + + + + ); + }, + }; + }), + ); + + if (designable && schema['x-designable-bar'] && schema['x-designable-bar'] !== 'Table.SimpleDesignableBar') { + columns.push({ + title: , + dataIndex: 'addnew', + }); + } + return columns; +}; diff --git a/packages/client/src/schemas/table/hooks/useTableCreateAction.ts b/packages/client/src/schemas/table/hooks/useTableCreateAction.ts new file mode 100644 index 000000000..77525ae03 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableCreateAction.ts @@ -0,0 +1,24 @@ +import { useForm } from '@formily/react'; +import { useTable } from './useTable'; + +export function useTableCreateAction() { + const { + field, + service, + resource, + refresh, + props: { refreshRequestOnChange }, + } = useTable(); + const form = useForm(); + return { + async run() { + console.log('refreshRequestOnChange', refreshRequestOnChange); + if (refreshRequestOnChange) { + await resource.create(form.values); + await form.reset(); + return service.refresh(); + } + field.unshift(form.values); + }, + }; +} diff --git a/packages/client/src/schemas/table/hooks/useTableDestroyAction.ts b/packages/client/src/schemas/table/hooks/useTableDestroyAction.ts new file mode 100644 index 000000000..096beff48 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableDestroyAction.ts @@ -0,0 +1,49 @@ +import { useContext } from "react"; +import { useTable } from "./useTable"; +import { TableRowContext } from "../context"; +import { VisibleContext } from '../../../context'; +import { findIndex } from 'lodash'; + +export const useTableDestroyAction = () => { + const { + resource, + field, + service, + selectedRowKeys, + setSelectedRowKeys, + refresh, + props: { refreshRequestOnChange, rowKey }, + } = useTable(); + const ctx = useContext(TableRowContext); + const [, setVisible] = useContext(VisibleContext); + return { + async run() { + if (refreshRequestOnChange) { + const rowKeys = selectedRowKeys || []; + if (ctx) { + rowKeys.push(ctx.record[rowKey]); + } + await resource.destroy({ + [`${rowKey}.in`]: rowKeys, + }); + setSelectedRowKeys([]); + setVisible && setVisible(false); + return service.refresh(); + } + if (ctx) { + console.log('ctx.index', ctx.index); + field.remove(ctx.index); + refresh(); + } + const rowKeys = [...selectedRowKeys]; + while (rowKeys.length) { + const key = rowKeys.shift(); + const index = findIndex(field.value, (item) => item[rowKey] === key); + field.remove(index); + } + setSelectedRowKeys([]); + refresh(); + return; + }, + }; +}; diff --git a/packages/client/src/schemas/table/hooks/useTableExportAction.ts b/packages/client/src/schemas/table/hooks/useTableExportAction.ts new file mode 100644 index 000000000..e701c4fb7 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableExportAction.ts @@ -0,0 +1,51 @@ +import { useField } from '@formily/react'; +import { useContext } from 'react'; +import { useTable } from './useTable'; +import { TableRowContext } from '../context'; +import { useCollectionContext } from '../../../constate'; + +export const useTableExportAction = () => { + const { + resource, + field, + service, + selectedRowKeys, + setSelectedRowKeys, + refresh, + schema, + props: { refreshRequestOnChange, rowKey }, + } = useTable(); + const ctx = useContext(TableRowContext); + + const actionField = useField(); + const fieldNames = actionField.componentProps.fieldNames || []; + const { getField } = useCollectionContext(); + + const columns = fieldNames + .map((name) => { + const f = getField(name); + return { + title: f?.uiSchema.title, + name, + sort: f?.sort, + }; + }) + .sort((a, b) => a.sort - b.sort); + + return { + async run() { + const rowKeys = selectedRowKeys || []; + const { filter = {}, ...others } = service.params[0]; + if (rowKeys.length) { + filter[`${rowKey}.in`] = rowKeys; + } + await resource.export({ + ...others, + columns, + perPage: -1, + page: 1, + filter, + }); + }, + }; +}; diff --git a/packages/client/src/schemas/table/hooks/useTableFilterAction.ts b/packages/client/src/schemas/table/hooks/useTableFilterAction.ts new file mode 100644 index 000000000..6307d65ed --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableFilterAction.ts @@ -0,0 +1,23 @@ +import { useTable } from './useTable'; +import { useForm } from '@formily/react'; + +export function useTableFilterAction() { + const { + field, + service, + refresh, + props: { refreshRequestOnChange }, + } = useTable(); + const form = useForm(); + return { + async run() { + console.log('useTableFilterAction', form.values); + if (refreshRequestOnChange) { + return service.run({ + ...service.params[0], + // filter, + }); + } + }, + }; +} diff --git a/packages/client/src/schemas/table/hooks/useTableIndex.ts b/packages/client/src/schemas/table/hooks/useTableIndex.ts new file mode 100644 index 000000000..513477183 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableIndex.ts @@ -0,0 +1,16 @@ +import { useContext } from 'react'; +import { useTable } from './useTable'; +import { TableRowContext } from '../context'; + +export const useTableIndex = () => { + const { pagination, props } = useTable(); + const ctx = useContext(TableRowContext); + const { pageSize, page = 1 } = pagination; + console.log({ pageSize, page }, ctx.index); + return ctx.index + (page - 1) * pageSize; + if (pagination && !props.clientSidePagination) { + const { pageSize, page = 1 } = pagination; + return ctx.index + (page - 1) * pageSize; + } + return ctx.index; +}; diff --git a/packages/client/src/schemas/table/hooks/useTableRow.ts b/packages/client/src/schemas/table/hooks/useTableRow.ts new file mode 100644 index 000000000..4eada4723 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableRow.ts @@ -0,0 +1,7 @@ +import React, { useContext } from 'react'; +import {TableRowContext} from '../context'; + + +export const useTableRow = () => { + return useContext(TableRowContext); +}; diff --git a/packages/client/src/schemas/table/hooks/useTableRowRecord.ts b/packages/client/src/schemas/table/hooks/useTableRowRecord.ts new file mode 100644 index 000000000..524ca1118 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableRowRecord.ts @@ -0,0 +1,7 @@ +import { useContext } from "react"; +import { TableRowContext } from "../context"; + +export const useTableRowRecord = () => { + const ctx = useContext(TableRowContext); + return ctx.record; +}; diff --git a/packages/client/src/schemas/table/hooks/useTableUpdateAction.ts b/packages/client/src/schemas/table/hooks/useTableUpdateAction.ts new file mode 100644 index 000000000..dc1f417b5 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTableUpdateAction.ts @@ -0,0 +1,35 @@ +import { useForm } from "@formily/react"; +import { useContext } from "react"; +import { useTable } from "./useTable"; +import { TableRowContext } from "../context"; +import { DescriptionsContext } from '../../form'; + +export const useTableUpdateAction = () => { + const { + resource, + field, + service, + refresh, + props: { refreshRequestOnChange, rowKey }, + } = useTable(); + const ctx = useContext(TableRowContext); + const form = useForm(); + const { service: formService } = useContext(DescriptionsContext); + + return { + async run() { + if (refreshRequestOnChange) { + await resource.save(form.values, { + resourceKey: ctx.record[rowKey], + }); + if (formService) { + await formService.refresh(); + } + await service.refresh(); + return; + } + field.value[ctx.index] = form.values; + // refresh(); + }, + }; +}; diff --git a/packages/client/src/schemas/table/hooks/useTotal.ts b/packages/client/src/schemas/table/hooks/useTotal.ts new file mode 100644 index 000000000..eb7c29851 --- /dev/null +++ b/packages/client/src/schemas/table/hooks/useTotal.ts @@ -0,0 +1,10 @@ +import { useTable } from './useTable'; + +export const useTotal = () => { + const { + field, + service, + props: { clientSidePagination }, + } = useTable(); + return clientSidePagination ? field?.value?.length : service?.data?.total; +}; diff --git a/packages/client/src/schemas/table/index.tsx b/packages/client/src/schemas/table/index.tsx index 75ef11bc5..d1449ced3 100644 --- a/packages/client/src/schemas/table/index.tsx +++ b/packages/client/src/schemas/table/index.tsx @@ -1,950 +1,31 @@ -import { - FormProvider, - observer, - RecursionField, - Schema, - useField, - useForm, -} from '@formily/react'; -import { Modal, Pagination, Popover, Table as AntdTable } from 'antd'; -import { cloneDeep, cloneDeepWith, findIndex, forIn, range, set } from 'lodash'; -import React, { Fragment, useEffect, useState } from 'react'; -import { useContext } from 'react'; -import { createContext } from 'react'; -import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect'; -import { useDesignable, createCollectionField, ISchema } from '..'; -import { uid, merge } from '@formily/shared'; -import useRequest from '@ahooksjs/use-request'; -import { BaseResult } from '@ahooksjs/use-request/lib/types'; -import cls from 'classnames'; -import { MenuOutlined, DragOutlined, FilterOutlined } from '@ant-design/icons'; -import { DndContext, DragOverlay } from '@dnd-kit/core'; -import { - SortableContext, - useSortable, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; -import { Select, Dropdown, Menu, Switch, Button, Space } from 'antd'; -import { PlusOutlined, SettingOutlined } from '@ant-design/icons'; -import './style.less'; -import { - findPropertyByPath, - getSchemaPath, - SchemaField, - SchemaRenderer, -} from '../../components/schema-renderer'; -import { - interfaces, - isAssociation, - options, -} from '../database-field/interfaces'; -import { DraggableBlockContext } from '../../components/drag-and-drop'; -import AddNew from '../add-new'; -import { isGridRowOrCol } from '../grid'; -import { ListOptions, Resource } from '../../resource'; -import { - CollectionProvider, - DisplayedMapProvider, - useCollection, - useCollectionContext, - useCollectionsContext, - useDisplayedMapContext, - useClient, - useResourceRequest, -} from '../../constate'; -import { useResource as useGeneralResource } from '../../hooks/useResource'; -import SwitchMenuItem from '../../components/SwitchMenuItem'; -import { useMemo } from 'react'; -import { ArrayField, createForm,LifeCycleTypes } from '@formily/core'; -import { - ColDraggableContext, - SortableBodyCell, - SortableBodyRow, - SortableColumn, - SortableHeaderCell, - SortableHeaderRow, - SortableRowHandle, -} from './Sortable'; -import { DragHandle, Droppable, SortableItem } from '../../components/Sortable'; -import { isValid } from '@formily/shared'; -import { FormButtonGroup, FormDialog, FormLayout, Submit } from '@formily/antd'; -import flatten from 'flat'; -import IconPicker from '../../components/icon-picker'; -import { DescriptionsContext } from '../form'; -import { VisibleContext } from '../../context'; +import React, { useState } from 'react'; +import { observer } from '@formily/react'; +import { CollectionProvider, DisplayedMapProvider } from '../../constate'; import { SimpleDesignableBar } from './SimpleDesignableBar'; -import { Trans, useTranslation } from 'react-i18next'; -import { useCompile } from '../../hooks/useCompile'; - -export interface ITableContext { - props: any; - field: ArrayField; - schema: Schema; - service: BaseResult; - selectedRowKeys?: any; - setSelectedRowKeys?: any; - pagination?: any; - setPagination?: any; - refresh?: any; - resource?: Resource; -} - -export interface ITableRowContext { - index: number; - record: any; -} - -const TableContext = createContext({} as any); -export const TableRowContext = createContext(null); -export const CollectionFieldContext = createContext(null); - -export const useTable = () => { - return useContext(TableContext); -}; - -const useTableRow = () => { - return useContext(TableRowContext); -}; - -function useTableFilterAction() { - const { - field, - service, - refresh, - props: { refreshRequestOnChange }, - } = useTable(); - const form = useForm(); - return { - async run() { - console.log('useTableFilterAction', form.values); - if (refreshRequestOnChange) { - return service.run({ - ...service.params[0], - // filter, - }); - } - }, - }; -} - -function useTableCreateAction() { - const { - field, - service, - resource, - refresh, - props: { refreshRequestOnChange }, - } = useTable(); - const form = useForm(); - return { - async run() { - console.log('refreshRequestOnChange', refreshRequestOnChange); - if (refreshRequestOnChange) { - await resource.create(form.values); - await form.reset(); - return service.refresh(); - } - field.unshift(form.values); - }, - }; -} - -const useTableUpdateAction = () => { - const { - resource, - field, - service, - refresh, - props: { refreshRequestOnChange, rowKey }, - } = useTable(); - const ctx = useContext(TableRowContext); - const form = useForm(); - const { service: formService } = useContext(DescriptionsContext); - - return { - async run() { - if (refreshRequestOnChange) { - await resource.save(form.values, { - resourceKey: ctx.record[rowKey], - }); - if (formService) { - await formService.refresh(); - } - await service.refresh(); - return; - } - field.value[ctx.index] = form.values; - // refresh(); - }, - }; -}; - -const useTableDestroyAction = () => { - const { - resource, - field, - service, - selectedRowKeys, - setSelectedRowKeys, - refresh, - props: { refreshRequestOnChange, rowKey }, - } = useTable(); - const ctx = useContext(TableRowContext); - const [, setVisible] = useContext(VisibleContext); - return { - async run() { - if (refreshRequestOnChange) { - const rowKeys = selectedRowKeys || []; - if (ctx) { - rowKeys.push(ctx.record[rowKey]); - } - await resource.destroy({ - [`${rowKey}.in`]: rowKeys, - }); - setSelectedRowKeys([]); - setVisible && setVisible(false); - return service.refresh(); - } - if (ctx) { - console.log('ctx.index', ctx.index); - field.remove(ctx.index); - refresh(); - } - const rowKeys = [...selectedRowKeys]; - while (rowKeys.length) { - const key = rowKeys.shift(); - const index = findIndex(field.value, (item) => item[rowKey] === key); - field.remove(index); - } - setSelectedRowKeys([]); - refresh(); - return; - }, - }; -}; - -const useTableExportAction = () => { - const { - resource, - field, - service, - selectedRowKeys, - setSelectedRowKeys, - refresh, - schema, - props: { refreshRequestOnChange, rowKey }, - } = useTable(); - const ctx = useContext(TableRowContext); - - const actionField = useField(); - const fieldNames = actionField.componentProps.fieldNames || []; - const { getField } = useCollectionContext(); - - const columns = fieldNames - .map((name) => { - const f = getField(name); - return { - title: f?.uiSchema.title, - name, - sort: f?.sort, - }; - }) - .sort((a, b) => a.sort - b.sort); - - return { - async run() { - const rowKeys = selectedRowKeys || []; - const { filter = {}, ...others } = service.params[0]; - if (rowKeys.length) { - filter[`${rowKey}.in`] = rowKeys; - } - await resource.export({ - ...others, - columns, - perPage: -1, - page: 1, - filter, - }); - }, - }; -}; - -const useTableRowRecord = () => { - const ctx = useContext(TableRowContext); - return ctx.record; -}; - -const useTableIndex = () => { - const { pagination, props } = useTable(); - const ctx = useContext(TableRowContext); - const { pageSize, page = 1 } = pagination; - console.log({ pageSize, page }, ctx.index); - return ctx.index + (page - 1) * pageSize; - if (pagination && !props.clientSidePagination) { - const { pageSize, page = 1 } = pagination; - return ctx.index + (page - 1) * pageSize; - } - return ctx.index; -}; - -const useTableActionBars = () => { - const { - field, - schema, - props: { rowKey }, - } = useTable(); - - const bars = schema.reduceProperties((bars, current) => { - if (current['x-component'] === 'Table.ActionBar') { - return [...bars, current]; - } - return [...bars]; - }, []); - - return bars; -}; - -export function isOperationColumn(schema: Schema) { - return ['Table.Operation'].includes(schema['x-component']); -} - -export function isColumn(schema: Schema) { - return ['Table.Column'].includes(schema['x-component']); -} - -export function isColumnComponent(component: string) { - return ['Table.Operation', 'Table.Column'].includes(component); -} - -const useCollectionFields = (schema: Schema) => { - const columns = schema.reduceProperties((columns, current) => { - if (isColumn(current)) { - if (current['x-hidden']) { - return columns; - } - if (current['x-display'] && current['x-display'] !== 'visible') { - return columns; - } - return [...columns, current]; - } - return [...columns]; - }, []); - - return columns - .map((column) => { - const columnProps = column['x-component-props'] || {}; - return columnProps.fieldName; - }) - .filter(Boolean); -}; - -const useTableColumns = () => { - const { - field, - schema, - props: { rowKey }, - } = useTable(); - const { designable } = useDesignable(); - - const { getField } = useCollectionContext(); - - const columnSchemas = schema.reduceProperties((columns, current) => { - if (isColumn(current)) { - if (current['x-hidden']) { - return columns; - } - if (current['x-display'] && current['x-display'] !== 'visible') { - return columns; - } - return [...columns, current]; - } - return [...columns]; - }, []); - - const columns: any[] = [].concat( - columnSchemas.map((column: Schema) => { - const columnProps = column['x-component-props'] || {}; - const collectionField = getField(columnProps.fieldName); - return { - title: ( - - - - ), - dataIndex: column.name, - ...columnProps, - render: (_: any, record: any) => { - const index = findIndex( - field.value, - (item) => item[rowKey] === record[rowKey], - ); - return ( - - - - - - ); - }, - }; - }), - ); - - if ( - designable && - schema['x-designable-bar'] && - schema['x-designable-bar'] !== 'Table.SimpleDesignableBar' - ) { - columns.push({ - title: , - dataIndex: 'addnew', - }); - } - return columns; -}; - -function AddColumn() { - const { t } = useTranslation(); - const [visible, setVisible] = useState(false); - const { appendChild, remove } = useDesignable(); - const { loadCollections } = useCollectionsContext(); - const { collection, fields, refresh } = useCollectionContext(); - const displayed = useDisplayedMapContext(); - const { service } = useTable(); - const { createSchema, removeSchema, updateSchema } = useClient(); - return ( - - - {fields.map((field) => ( - { - if (checked) { - console.log( - 'SwitchMenuItem.field.name', - field.dataType, - service.params[0], - ); - const columnSchema: ISchema = { - type: 'void', - 'x-component': 'Table.Column', - 'x-component-props': { - fieldName: field.name, - }, - 'x-designable-bar': 'Table.Column.DesignableBar', - }; - if (field.interface === 'linkTo') { - columnSchema.properties = { - options: { - type: 'void', - 'x-decorator': 'Form', - 'x-component': 'Select.Options.Drawer', - 'x-component-props': { - useOkAction: '{{ Select.useOkAction }}', - }, - title: "{{t('Select record')}}", - properties: { - table: { - type: 'array', - 'x-designable-bar': 'Table.DesignableBar', - 'x-decorator': 'BlockItem', - 'x-decorator-props': { - draggable: false, - }, - 'x-component': 'Table', - default: [], - 'x-component-props': { - rowKey: 'id', - useRowSelection: '{{ Select.useRowSelection }}', - useSelectedRowKeys: - '{{ Select.useSelectedRowKeys }}', - onSelect: '{{ Select.useSelect() }}', - collectionName: field.target, - // dragSort: true, - // showIndex: true, - refreshRequestOnChange: true, - pagination: { - pageSize: 10, - }, - }, - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Table.ActionBar', - 'x-designable-bar': - 'Table.ActionBar.DesignableBar', - properties: { - [uid()]: { - type: 'void', - title: "{{t('Filter')}}", - 'x-decorator': 'AddNew.Displayed', - 'x-decorator-props': { - displayName: 'filter', - }, - 'x-align': 'left', - 'x-component': 'Table.Filter', - 'x-designable-bar': - 'Table.Filter.DesignableBar', - 'x-component-props': { - fieldNames: [], - }, - }, - }, - }, - }, - }, - }, - }, - option: { - type: 'void', - 'x-component': 'Select.OptionTag', - properties: { - [uid()]: { - type: 'void', - title: "{{t('View record')}}", - 'x-component': 'Action.Drawer', - 'x-component-props': { - bodyStyle: { - background: '#f0f2f5', - }, - }, - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Tabs', - 'x-designable-bar': 'Tabs.DesignableBar', - properties: { - [uid()]: { - type: 'void', - title: "{{t('Details')}}", - 'x-designable-bar': - 'Tabs.TabPane.DesignableBar', - 'x-component': 'Tabs.TabPane', - 'x-component-props': {}, - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid', - 'x-component-props': { - addNewComponent: 'AddNew.PaneItem', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }; - } - const data = appendChild(columnSchema); - await createSchema(data); - if (isAssociation(field)) { - const defaultAppends = - service.params[0]?.defaultAppends || []; - defaultAppends.push(field.name); - await service.run({ - ...service.params[0], - defaultAppends, - }); - } - } else { - const s: any = displayed.get(field.name); - const p = getSchemaPath(s); - const removed = remove(p); - await removeSchema(removed); - displayed.remove(field.name); - if (isAssociation(field)) { - const defaultAppends = - service.params[0]?.defaultAppends || []; - const index = defaultAppends.indexOf(field.name); - if (index > -1) { - defaultAppends.splice(index, 1); - } - await service.run({ - ...service.params[0], - defaultAppends, - }); - } - } - // service.refresh(); - }} - /> - ))} - - - - {options.map((option) => ( - - {option.children.map((item) => ( - { - setVisible(false); - const values = await FormDialog(t('Add field'), () => { - return ( - - - - ); - }).open({ - initialValues: { - interface: item.name, - ...item.default, - key: uid(), - name: `f_${uid()}`, - }, - }); - await createCollectionField(collection?.name, values); - const data = appendChild({ - type: 'void', - 'x-component': 'Table.Column', - 'x-component-props': { - fieldName: values.name, - }, - 'x-designable-bar': 'Table.Column.DesignableBar', - }); - await createSchema(data); - await refresh(); - }} - > - {item.title} - - ))} - - ))} - - - } - > - - - ); -} - -const useDefaultRowSelection = () => { - return { - type: 'checkbox', - }; -}; - -const useDataSource = () => { - const { - pagination, - field, - props: { clientSidePagination, dataRequest }, - } = useTable(); - let dataSource = field.value; - // if (pagination && (clientSidePagination || !dataRequest)) { - // const { page = 1, pageSize } = pagination; - // const startIndex = (page - 1) * pageSize; - // const endIndex = startIndex + pageSize - 1; - // dataSource = field.value?.slice(startIndex, endIndex + 1); - // } - return dataSource; -}; - -const TableMain = () => { - const { - resource, - selectedRowKeys, - setSelectedRowKeys, - service, - field, - props: { - rowKey, - dragSort, - showIndex, - onSelect, - useRowSelection = useDefaultRowSelection, - }, - refresh, - } = useTable(); - const columns = useTableColumns(); - const dataSource = useDataSource(); - const actionBars = useTableActionBars(); - const [html, setHtml] = useState(''); - const { type } = useRowSelection(); - return ( -
- { - const fromId = event.active?.id as any; - const toId = event.over?.id as any; - if (isValid(fromId) && isValid(toId)) { - const fromIndex = findIndex( - field.value, - (item) => item[rowKey] === fromId, - ); - const toIndex = findIndex( - field.value, - (item) => item[rowKey] === toId, - ); - console.log({ fromId, toId, fromIndex, toIndex }); - field.move(fromIndex, toIndex); - refresh(); - await resource.sort({ - resourceKey: fromId, - target: { - [rowKey]: toId, - }, - }); - await service.refresh(); - } - }} - > - {actionBars.map((actionBar) => ( - - ))} - - {}} - loading={service?.loading} - rowKey={rowKey} - dataSource={dataSource} - columns={columns} - // components={{ - // body: { - // row: DragableBodyRow, - // }, - // }} - components={{ - header: { - row: SortableHeaderRow, - cell: SortableHeaderCell, - }, - body: { - // wrapper: (props) => { - // return ( - // - // - //
- // - // {props.children} - // - // ); - // }, - row: SortableBodyRow, - // cell: SortableBodyCell, - }, - }} - rowSelection={{ - type: type || 'checkbox', - selectedRowKeys, - onChange: (rowKeys, rows) => { - setSelectedRowKeys(rowKeys); - onSelect && onSelect(rowKeys, rows); - }, - renderCell: (checked, record, _, originNode) => { - const index = findIndex( - field.value, - (item) => item[rowKey] === record[rowKey], - ); - return ( - -
- {dragSort && } - {showIndex && } - {originNode} -
-
- ); - }, - }} - /> - - - -
- ); -}; - -const usePagination = () => { - const field = useField(); - const paginationProps = field.componentProps.pagination; - - let pagination = paginationProps; - - // const [pagination, setPagination] = useState(() => { - // if (!paginationProps) { - // return false; - // } - // const { defaultPageSize = 10, ...others } = paginationProps; - // return { page: 1, pageSize: defaultPageSize, ...others }; - // }); - - // useEffect(() => { - // if (!paginationProps) { - // return setPagination(false); - // } - // const { defaultPageSize = 10, ...others } = paginationProps; - // setPagination({ page: 1, pageSize: defaultPageSize, ...others }); - // }, [paginationProps]); - - return [ - pagination, - (params) => { - const defaults = field.componentProps.pagination; - field.componentProps.pagination = { ...defaults, ...params }; - }, - ]; -}; - -const useDefaultSelectedRowKeys = () => { - const [selectedRowKeys, setSelectedRowKeys] = useState([]); - return { selectedRowKeys, setSelectedRowKeys }; -}; - -const TableProvider = (props: any) => { - const { - rowKey = 'id', - dataRequest, - useResource = useGeneralResource, - defaultSelectedRowKeys, - useSelectedRowKeys = useDefaultSelectedRowKeys, - ...others - } = props; - const { schema } = useDesignable(); - const field = useField(); - const [pagination, setPagination] = usePagination(); - const { selectedRowKeys, setSelectedRowKeys } = useSelectedRowKeys(); - console.log('props.useSelectedRowKeys', selectedRowKeys); - const [, refresh] = useState(uid()); - const { resource } = useResource(); - const { sortableField } = useCollectionContext(); - const dragSort = props.dragSort; - const collectionFields = useCollectionFields(schema); - // console.log({ collectionFields, pagination }); - const getDefaultParams = () => { - const defaultParams = { ...pagination }; - if (dragSort) { - defaultParams['sort'] = [sortableField || 'sort']; - } else { - defaultParams['sort'] = (props.defaultSort || []).join(','); - } - defaultParams['defaultAppends'] = [ - ...(props.defaultAppends || []), - ...collectionFields, - ]; - if (props.defaultFilter) { - defaultParams['defaultFilter'] = props.defaultFilter; - } - console.log({ defaultParams }); - return defaultParams; - }; - const service = useRequest( - (params?: any) => { - if (!resource) { - return Promise.resolve({ - list: field.value, - total: field?.value?.length, - }); - } - return resource.list(params).then((res) => { - return { - list: res?.data || [], - total: res?.meta?.count || res?.data?.length, - }; - }); - }, - { - onSuccess(data: any) { - field.setValue(data?.list || []); - }, - manual: true, - // defaultParams: [getDefaultParams()], - }, - ); - useDeepCompareEffectNoCheck(() => { - service.run(getDefaultParams()); - }, [ - pagination?.pageSize, - pagination?.page, - props.dragSort, - props.defaultSort, - props.defaultFilter, - ]); - return ( - { - const { page = 1, pageSize } = pagination; - const total = props.clientSidePagination - ? field?.value?.length - : service?.data?.total; - const maxPage = Math.ceil(total / pageSize); - if (page > maxPage) { - setPagination({ page: maxPage }); - } else { - refresh(uid()); - } - }, - selectedRowKeys, - setSelectedRowKeys, - pagination, - setPagination, - service, - field, - schema, - props: { ...others, rowKey, dataRequest }, - }} - > - - - ); -}; +import { useActionLogsResource } from './hooks/useActionLogsResource'; +import { useTableDestroyAction } from './hooks/useTableDestroyAction'; +import { useActionLogDetailsResource } from './hooks/useActionLogDetailsResource'; +import { TableActionBar } from './TableActionBar'; +import { TablePagination } from './TablePagination'; +import { TableFilter } from './TableFilter'; +import { TableFilterDesignableBar } from './TableFilterDesignableBar'; +import { TableExportActionDesignableBar } from './TableExportActionDesignableBar'; +import { useResource } from './hooks/useResource'; +import { TableDesignableBar } from './TableDesignableBar'; +import { useTableFilterAction } from './hooks/useTableFilterAction'; +import { useTableCreateAction } from './hooks/useTableCreateAction'; +import { useTableUpdateAction } from './hooks/useTableUpdateAction'; +import { useTableExportAction } from './hooks/useTableExportAction'; +import { useTableIndex } from './hooks/useTableIndex'; +import { useTableRowRecord } from './hooks/useTableRowRecord'; +import { TableSortHandle } from './TableSortHandle'; +import { TableOperation } from './TableOperation'; +import { TableOperationCell } from './TableOperationCell'; +import { TableCell } from './TableCell'; +import { TableColumn } from './TableColumn'; +import { TableActionDesignableBar } from './TableActionDesignableBar'; +import { TableOperationDesignableBar } from './TableOperationDesignableBar'; +import { TableProvider } from './TableProvider'; export const Table: any = observer((props: any) => { const [visible, setVisible] = useState(false); @@ -957,1703 +38,24 @@ export const Table: any = observer((props: any) => { ); }); -const useTotal = () => { - const { - field, - service, - props: { clientSidePagination }, - } = useTable(); - return clientSidePagination ? field?.value?.length : service?.data?.total; -}; - -Table.Pagination = observer(() => { - const { service, pagination, setPagination, props } = useTable(); - if (!pagination || Object.keys(pagination).length === 0) { - return null; - } - const { clientSidePagination } = props; - const total = useTotal(); - const { page = 1 } = pagination; - return ( -
- { - const page = pagination.pageSize !== pageSize ? 1 : current; - setPagination({ - page, - pageSize, - }); - // if (clientSidePagination) { - // return; - // } - // service.run({ - // ...service.params[0], - // page, - // pageSize, - // }); - }} - /> -
- ); -}); - -function generateActionSchema(type) { - const actions: { [key: string]: ISchema } = { - filter: { - key: uid(), - name: uid(), - type: 'void', - title: "{{ t('Filter') }}", - 'x-align': 'left', - 'x-decorator': 'AddNew.Displayed', - 'x-decorator-props': { - displayName: 'filter', - }, - 'x-component': 'Table.Filter', - 'x-designable-bar': 'Table.Filter.DesignableBar', - 'x-component-props': { - fieldNames: [], - }, - }, - export: { - key: uid(), - type: 'void', - name: uid(), - title: "{{ t('Export') }}", - 'x-align': 'right', - 'x-decorator': 'AddNew.Displayed', - 'x-decorator-props': { - displayName: 'export', - }, - 'x-action-type': 'export', - 'x-component': 'Action', - 'x-designable-bar': 'Table.ExportActionDesignableBar', - 'x-component-props': { - fieldNames: [], - icon: 'ExportOutlined', - useAction: '{{ Table.useTableExportAction }}', - }, - }, - create: { - key: uid(), - type: 'void', - name: uid(), - title: "{{ t('Add new') }}", - 'x-align': 'right', - 'x-decorator': 'AddNew.Displayed', - 'x-decorator-props': { - displayName: 'create', - }, - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - icon: 'PlusOutlined', - }, - 'x-designable-bar': 'Table.Action.DesignableBar', - properties: { - modal: { - type: 'void', - title: "{{ t('Add record') }}", - 'x-decorator': 'Form', - 'x-component': 'Action.Drawer', - 'x-component-props': { - useOkAction: '{{ Table.useTableCreateAction }}', - }, - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid', - 'x-component-props': { - addNewComponent: 'AddNew.FormItem', - }, - }, - }, - }, - }, - }, - destroy: { - key: uid(), - type: 'void', - name: uid(), - title: "{{ t('Delete') }}", - 'x-align': 'right', - 'x-decorator': 'AddNew.Displayed', - 'x-decorator-props': { - displayName: 'destroy', - }, - 'x-action-type': 'destroy', - 'x-component': 'Action', - 'x-designable-bar': 'Table.Action.DesignableBar', - 'x-component-props': { - confirm: { - title: "{{ t('Delete record') }}", - content: "{{ t('Are you sure you want to delete it?') }}", - }, - useAction: '{{ Table.useTableDestroyAction }}', - }, - }, - view: {}, - update: {}, - }; - return actions[type]; -} - -function generateMenuActionSchema(type) { - const actions: { [key: string]: ISchema } = { - view: { - key: uid(), - name: uid(), - type: 'void', - title: "{{ t('View') }}", - 'x-component': 'Action', - 'x-component-props': { - type: 'link', - }, - 'x-designable-bar': 'Table.Action.DesignableBar', - 'x-action-type': 'view', - properties: { - [uid()]: { - type: 'void', - title: "{{ t('View record') }}", - 'x-component': 'Action.Drawer', - 'x-component-props': { - bodyStyle: { - background: '#f0f2f5', - // paddingTop: 0, - }, - }, - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Tabs', - 'x-designable-bar': 'Tabs.DesignableBar', - properties: { - [uid()]: { - type: 'void', - title: "{{ t('Details') }}", - 'x-designable-bar': 'Tabs.TabPane.DesignableBar', - 'x-component': 'Tabs.TabPane', - 'x-component-props': {}, - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid', - 'x-component-props': { - addNewComponent: 'AddNew.PaneItem', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - update: { - key: uid(), - name: uid(), - type: 'void', - title: "{{ t('Edit') }}", - 'x-component': 'Action', - 'x-component-props': { - type: 'link', - }, - 'x-designable-bar': 'Table.Action.DesignableBar', - 'x-action-type': 'update', - properties: { - [uid()]: { - type: 'void', - title: "{{ t('Edit record') }}", - 'x-decorator': 'Form', - 'x-decorator-props': { - useResource: '{{ Table.useResource }}', - useValues: '{{ Table.useTableRowRecord }}', - }, - 'x-component': 'Action.Drawer', - 'x-component-props': { - useOkAction: '{{ Table.useTableUpdateAction }}', - }, - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid', - 'x-component-props': { - addNewComponent: 'AddNew.FormItem', - }, - }, - }, - }, - }, - }, - destroy: { - key: uid(), - name: uid(), - type: 'void', - title: "{{ t('Delete') }}", - 'x-component': 'Action', - 'x-designable-bar': 'Table.Action.DesignableBar', - 'x-action-type': 'destroy', - 'x-component-props': { - useAction: '{{ Table.useTableDestroyAction }}', - type: 'link', - confirm: { - title: "{{ t('Delete record') }}", - content: "{{ t('Are you sure you want to delete it?') }}", - }, - }, - }, - }; - return actions[type]; -} - -function AddActionButton() { - const { t } = useTranslation(); - const [visible, setVisible] = useState(false); - const displayed = useDisplayedMapContext(); - const { appendChild, remove } = useDesignable(); - const { schema, designable } = useDesignable(); - const { createSchema, removeSchema, updateSchema } = useClient(); - - if (!designable || !schema['x-designable-bar']) { - return null; - } - return ( - - - {[ - { title: t('Filter'), name: 'filter' }, - { title: t('Export'), name: 'export' }, - { title: t('Add new'), name: 'create' }, - { title: t('Delete'), name: 'destroy' }, - ].map((item) => ( - { - if (!checked) { - const s = displayed.get(item.name) as Schema; - const path = getSchemaPath(s); - displayed.remove(item.name); - const removed = remove(path); - await removeSchema(removed); - } else { - const s = generateActionSchema(item.name); - const data = appendChild(s); - await createSchema(data); - } - }} - /> - ))} - - - - {t('Function')} - {t('Popup form')} - {t('Flexible popup')} - - - } - > - - - ); -} - -function Actions(props: any) { - const { align = 'left' } = props; - const { schema, designable } = useDesignable(); - return ( - - - {schema.mapProperties((s) => { - const currentAlign = s['x-align'] || 'left'; - if (currentAlign !== align) { - return null; - } - return ( - - - - ); - })} - - - ); -} - -Table.ActionBar = observer((props: any) => { - const { align = 'top' } = props; - // const { schema, designable } = useDesignable(); - const { root, schema, insertAfter, remove, appendChild } = useDesignable(); - const moveToAfter = (path1, path2, extra = {}) => { - if (!path1 || !path2) { - return; - } - if (path1.join('.') === path2.join('.')) { - return; - } - const data = findPropertyByPath(root, path1); - if (!data) { - return; - } - remove(path1); - return insertAfter( - { - ...data.toJSON(), - ...extra, - }, - path2, - ); - }; - const { createSchema, removeSchema, updateSchema } = useClient(); - - const [dragOverlayContent, setDragOverlayContent] = useState(''); - return ( - { - setDragOverlayContent(event.active.data?.current?.title || ''); - // const previewRef = event.active.data?.current?.previewRef; - // if (previewRef) { - // setDragOverlayContent(previewRef?.current?.innerHTML); - // } else { - // setDragOverlayContent(''); - // } - }} - onDragEnd={async (event) => { - const path1 = event.active?.data?.current?.path; - const path2 = event.over?.data?.current?.path; - const align = event.over?.data?.current?.align; - const draggable = event.over?.data?.current?.draggable; - if (!path1 || !path2) { - return; - } - if (path1.join('.') === path2.join('.')) { - return; - } - if (!draggable) { - console.log('alignalignalignalign', align); - const p = findPropertyByPath(root, path1); - if (!p) { - return; - } - remove(path1); - const data = appendChild( - { - ...p.toJSON(), - 'x-align': align, - }, - path2, - ); - await updateSchema(data); - } else { - const data = moveToAfter(path1, path2, { - 'x-align': align, - }); - await updateSchema(data); - } - }} - > - - {dragOverlayContent} - {/*
*/} -
- -
-
- -
-
- -
- -
-
-
- ); -}); - -const fieldsToFilterColumns = (fields: any[], options: any = {}) => { - const { fieldNames = [] } = options; - const properties = {}; - fields.forEach((field, index) => { - if (fieldNames?.length && !fieldNames.includes(field.name)) { - return; - } - const fieldOption = interfaces.get(field.interface); - if (!fieldOption?.operations) { - return; - } - properties[`column${index}`] = { - type: 'void', - title: field?.uiSchema?.title, - 'x-component': 'Filter.Column', - 'x-component-props': { - operations: fieldOption.operations, - }, - properties: { - [field.name]: { - ...field.uiSchema, - 'x-decorator': 'FormilyFormItem', - title: null, - }, - }, - }; - }); - return properties; -}; - -const fieldsToSortColumns = (fields: any[]) => { - const dataSource = []; - - fields.forEach((field) => { - const fieldOption = interfaces.get(field.interface); - if (!fieldOption?.sortable) { - return; - } - dataSource.push({ - value: field.name, - label: field?.uiSchema?.title, - }); - }); - - return dataSource; -}; - -Table.Filter = observer((props: any) => { - const { service } = useTable(); - const { fieldNames = [] } = props; - const compile = useCompile(); - const { t } = useTranslation(); - const { schema, DesignableBar } = useDesignable(); - const form = useMemo(() => createForm(), []); - const { fields = [] } = useCollectionContext(); - const [visible, setVisible] = useState(false); - const obj = flatten(form.values.filter || {}); - console.log('flatten', obj, Object.values(obj)); - const count = Object.values(obj).filter((i) => - Array.isArray(i) ? i.length : i, - ).length; - const icon = props.icon || 'FilterOutlined'; - const properties = fieldsToFilterColumns(fields, { fieldNames }); - schema.mapProperties((p) => { - properties[p.name] = p; - }); - return ( - - - - - - { - const { filter } = form.values; - console.log('Table.Filter', form.values); - setVisible(false); - return service.run({ - ...service.params[0], - filter, - }); - }} - > - {t('Submit')} - - - -
- } - > - - - ); -}); - -Table.Filter.DesignableBar = () => { - const { t } = useTranslation(); - const { schema, remove, refresh, insertAfter } = useDesignable(); - const [visible, setVisible] = useState(false); - const displayed = useDisplayedMapContext(); - const { fields } = useCollectionContext(); - const field = useField(); - const { createSchema, removeSchema, updateSchema } = useClient(); - let fieldNames = field.componentProps.fieldNames || []; - if (fieldNames.length === 0) { - fieldNames = fields.map((field) => field.name); - } - return ( -
- { - e.stopPropagation(); - }} - className={cls('designable-bar-actions', { active: visible })} - > - - - { - setVisible(visible); - }} - overlay={ - - - {fields - .filter((collectionField) => { - const option = interfaces.get(collectionField.interface); - return option?.operations?.length; - }) - .map((collectionField) => ( - { - if (checked) { - fieldNames.push(collectionField.name); - } else { - const index = fieldNames.indexOf( - collectionField.name, - ); - if (index > -1) { - fieldNames.splice(index, 1); - } - } - console.log({ fieldNames, field }); - schema['x-component-props']['fieldNames'] = - fieldNames; - field.componentProps.fieldNames = fieldNames; - updateSchema(schema); - }} - /> - ))} - - - { - setVisible(false); - const values = await FormDialog(t('Edit button'), () => { - return ( - - - - ); - }).open({ - initialValues: { - title: schema['title'], - icon: schema['x-component-props']?.['icon'], - }, - }); - schema['title'] = values.title; - schema['x-component-props']['icon'] = values.icon; - field.componentProps.icon = values.icon; - field.title = values.title; - updateSchema(schema); - refresh(); - }} - > - {t('Edit button')} - - - { - const displayName = - schema?.['x-decorator-props']?.['displayName']; - const data = remove(); - await removeSchema(data); - if (displayName) { - displayed.remove(displayName); - } - setVisible(false); - }} - > - {t('Hide')} - - - } - > - - - - -
- ); -}; - -Table.ExportActionDesignableBar = () => { - const { t } = useTranslation(); - const { schema, remove, refresh, insertAfter } = useDesignable(); - const [visible, setVisible] = useState(false); - const displayed = useDisplayedMapContext(); - const { fields } = useCollectionContext(); - const field = useField(); - const { createSchema, removeSchema, updateSchema } = useClient(); - let fieldNames = field.componentProps.fieldNames || []; - if (fieldNames.length === 0) { - fieldNames = fields.map((field) => field.name); - } - return ( -
- { - e.stopPropagation(); - }} - className={cls('designable-bar-actions', { active: visible })} - > - - - { - setVisible(visible); - }} - overlay={ - - - {fields.map((collectionField) => ( - { - if (checked) { - fieldNames.push(collectionField.name); - } else { - const index = fieldNames.indexOf( - collectionField.name, - ); - if (index > -1) { - fieldNames.splice(index, 1); - } - } - console.log({ fieldNames, field }); - schema['x-component-props']['fieldNames'] = fieldNames; - field.componentProps.fieldNames = fieldNames; - updateSchema(schema); - }} - /> - ))} - - - { - setVisible(false); - const values = await FormDialog(t('Edit button'), () => { - return ( - - - - ); - }).open({ - initialValues: { - title: schema['title'], - icon: schema['x-component-props']?.['icon'], - }, - }); - schema['title'] = values.title; - schema['x-component-props']['icon'] = values.icon; - field.componentProps.icon = values.icon; - field.title = values.title; - updateSchema(schema); - refresh(); - }} - > - {t('Edit button')} - - - { - const displayName = - schema?.['x-decorator-props']?.['displayName']; - const data = remove(); - await removeSchema(data); - if (displayName) { - displayed.remove(displayName); - } - setVisible(false); - }} - > - {t('Hide')} - - - } - > - - - - -
- ); -}; - -Table.Operation = observer((props: any) => { - const { designable, schema } = useDesignable(); - const { t } = useTranslation(); - return ( -
- {t('Operations')} - -
- ); -}); - -Table.Operation.Cell = observer((props: any) => { - const ctx = useContext(TableRowContext); - const schema = props.schema; - return ( -
- -
- ); -}); - -Table.Operation.DesignableBar = () => { - const { t } = useTranslation(); - const { schema: columnSchema } = useDesignable(); - const groupSchema = Object.values(columnSchema.properties || {}).shift(); - const groupPath = getSchemaPath(groupSchema); - const { schema, remove, refresh, appendChild } = useDesignable(groupPath); - const [visible, setVisible] = useState(false); - const { createSchema, removeSchema, updateSchema } = useClient(); - - const map = new Map(); - schema.mapProperties((s) => { - if (!s['x-action-type']) { - return; - } - map.set(s['x-action-type'], s.name); - }); - const path = getSchemaPath(schema); - return ( -
- { - e.stopPropagation(); - }} - className={cls('designable-bar-actions', { active: visible })} - > - - - { - setVisible(visible); - }} - overlay={ - - - {[ - { title: t('View'), name: 'view' }, - { title: t('Edit'), name: 'update' }, - { title: t('Delete'), name: 'destroy' }, - ].map((item) => ( - { - if (checked) { - const s = generateMenuActionSchema(item.name); - const data = appendChild(s); - await createSchema(data); - } else if (map.get(item.name)) { - const removed = remove([...path, map.get(item.name)]); - await removeSchema(removed); - } - }} - /> - ))} - - - - - {t('Function')} - - {t('Popup form')} - {t('Flexible popup')} - - - } - > - - - - -
- ); -}; - +Table.Pagination = TablePagination; +Table.ActionBar = TableActionBar; +Table.Filter = TableFilter; +Table.Filter.DesignableBar = TableFilterDesignableBar; +Table.ExportActionDesignableBar = TableExportActionDesignableBar; +Table.Operation = TableOperation; +Table.Operation.Cell = TableOperationCell; +Table.Operation.DesignableBar = TableOperationDesignableBar; Table.Action = () => null; +Table.Action.DesignableBar = TableActionDesignableBar; +Table.Cell = TableCell; +Table.Column = TableColumn; +Table.SortHandle = TableSortHandle; +Table.DesignableBar = TableDesignableBar; +Table.SimpleDesignableBar = SimpleDesignableBar; -Table.Action.DesignableBar = () => { - const { t } = useTranslation(); - const { schema, remove, refresh, insertAfter } = useDesignable(); - const [visible, setVisible] = useState(false); - const isPopup = Object.keys(schema.properties || {}).length > 0; - const popupSchema = Object.values(schema.properties || {}).shift(); - const inActionBar = schema.parent['x-component'] === 'Table.ActionBar'; - const displayed = useDisplayedMapContext(); - const field = useField(); - const { createSchema, removeSchema, updateSchema } = useClient(); - const popupComponent = popupSchema?.['x-component'] || 'Action.Drawer'; - return ( -
- { - e.stopPropagation(); - }} - className={cls('designable-bar-actions', { active: visible })} - > - - - { - setVisible(visible); - }} - overlay={ - - { - setVisible(false); - const values = await FormDialog(t('Edit button'), () => { - return ( - - - - ); - }).open({ - initialValues: { - title: schema['title'], - icon: schema['x-component-props']?.['icon'], - }, - }); - schema['title'] = values.title; - schema['x-component-props']['icon'] = values.icon; - field.componentProps.icon = values.icon; - field.title = values.title; - updateSchema(schema); - refresh(); - }} - > - {t('Edit button')} - - {isPopup && ( - - - Open in - - - - )} - {!inActionBar && ( - - {t('Triggered when the row is clicked')}    - - - )} - - { - const displayName = - schema?.['x-decorator-props']?.['displayName']; - const data = remove(); - await removeSchema(data); - if (displayName) { - displayed.remove(displayName); - } - setVisible(false); - }} - > - {t('Hide')} - - - } - > - - - - -
- ); -}; - -Table.Cell = observer((props: any) => { - const ctx = useContext(TableRowContext); - const schema = props.schema; - const collectionField = useContext(CollectionFieldContext); - if (schema['x-component'] === 'Table.Operation') { - return ; - } - let uiSchema = collectionField?.uiSchema as Schema; - if (uiSchema?.['x-component'] === 'Upload.Attachment') { - uiSchema = cloneDeepWith(uiSchema); - set(uiSchema, 'x-component-props.size', 'small'); - } - const componentProps = merge( - uiSchema?.['x-component-props'] || {}, - schema?.['x-component-props'] || {}, - { - arrayMerge: (t, s) => s, - }, - ); - console.log('Table.Cell', collectionField?.interface, componentProps); - return ( -
- -
- ); -}); - -Table.Column = observer((props: any) => { - const collectionField = useContext(CollectionFieldContext); - const { schema, DesignableBar } = useDesignable(); - const compile = useCompile(); - const displayed = useDisplayedMapContext(); - useEffect(() => { - if (collectionField?.name) { - displayed.set(collectionField.name, schema); - } - }, [collectionField, schema]); - return ( -
- {compile(schema.title || collectionField?.uiSchema?.title)} - -
- ); -}); - -Table.Column.DesignableBar = () => { - const field = useField(); - const { t } = useTranslation(); - const compile = useCompile(); - const { service, refresh: refreshTable } = useTable(); - // const fieldSchema = useFieldSchema(); - const { schema, remove, refresh, insertAfter } = useDesignable(); - const [visible, setVisible] = useState(false); - const displayed = useDisplayedMapContext(); - const { getFieldsByCollection } = useCollectionsContext(); - const collectionField = useContext(CollectionFieldContext); - const { createSchema, removeSchema, updateSchema } = useClient(); - console.log('displayed.map', displayed.map); - return ( -
- { - e.stopPropagation(); - }} - className={cls('designable-bar-actions', { active: visible })} - > - - - { - setVisible(visible); - }} - overlay={ - - { - setVisible(false); - const values = await FormDialog(t('Custom name'), () => { - return ( - - - - ); - }).open({ - initialValues: { - fieldName: collectionField?.uiSchema?.title, - title: schema['title'], - }, - }); - const title = values.title || null; - field.title = title; - schema.title = title; - refresh(); - await updateSchema({ - key: schema['key'], - title: title, - }); - }} - > - {t('Custom column name')} - - {collectionField?.interface === 'linkTo' && ( - -
- {t('Label field')}{' '} - { - const componentProps = - schema['x-component-props'] || {}; - set( - componentProps, - 'pagination.defaultPageSize', - value, - ); - set(componentProps, 'pagination.pageSize', value); - schema['x-component-props'] = componentProps; - field.componentProps.pagination.pageSize = value; - field.componentProps.pagination.defaultPageSize = value; - refresh(); - updateSchema(schema); - setVisible(false); - }} - defaultValue={defaultPageSize} - > - 10 - 20 - 50 - 100 - - {' items per page'} - - - - { - Modal.confirm({ - title: t('Delete block'), - content: t('Are you sure you want to delete it?'), - onOk: async () => { - const removed = deepRemove(); - // console.log({ removed }) - const last = removed.pop(); - await removeSchema(last); - setVisible(false); - }, - }); - }} - > - {t('Delete')} - -
- } - > - -
-
-
-
- ); -}); - -Table.useResource = ({ onSuccess, manual = true }) => { - const { props } = useTable(); - const { collection } = useCollectionContext(); - const ctx = useContext(TableRowContext); - const resource = useResourceRequest({ - resourceName: collection?.name || props.collectionName, - resourceKey: ctx.record[props.rowKey], - }); - const { schema } = useDesignable(); - const fieldFields = (schema: Schema) => { - const names = []; - schema.reduceProperties((buf, current) => { - if (current['x-component'] === 'Form.Field') { - const fieldName = current['x-component-props']?.['fieldName']; - if (fieldName) { - buf.push(fieldName); - } - } else { - const fieldNames = fieldFields(current); - buf.push(...fieldNames); - } - return buf; - }, names); - return names; - }; - console.log( - 'collection?.name || props.collectionName', - collection?.name || props.collectionName, - // fieldFields(schema), - ); - const service = useRequest( - (params?: any) => { - console.log('Table.useResource', params); - return resource.get({ ...params, appends: fieldFields(schema) }); - }, - { - formatResult: (result) => result?.data, - onSuccess, - manual, - }, - ); - return { resource, service, initialValues: service.data, ...service }; -}; - -Table.useActionLogDetailsResource = ({ onSuccess }) => { - const { props } = useTable(); - const { collection } = useCollectionContext(); - const ctx = useContext(TableRowContext); - const resource = useResourceRequest({ - resourceName: 'action_logs', - resourceKey: ctx.record[props.rowKey], - }); - const service = useRequest( - (params?: any) => { - return resource.get({ - ...params, - appends: ['changes', 'user', 'collection'], - }); - }, - { - formatResult: (result) => result?.data, - onSuccess, - manual: true, - }, - ); - const [visible] = useContext(VisibleContext); - - useEffect(() => { - if (visible) { - service.run({}); - } - }, [visible]); - - return { resource, service, initialValues: service.data, ...service }; -}; - -const useActionLogsResource = (options: any = {}) => { - const { props } = useTable(); - const ctx = useContext(TableRowContext); - - class ActionLogoResource extends Resource { - list(options?: ListOptions) { - console.log({ options }); - let defaultFilter = options?.defaultFilter; - if (ctx?.record) { - const extra = { - index: ctx?.record?.id, - collection_name: props.collectionName, - }; - if (defaultFilter) { - defaultFilter = { and: [defaultFilter, extra] }; - } else { - defaultFilter = extra; - } - } - return super.list({ ...options, defaultFilter }); - } - } - - const resource = useResourceRequest('action_logs', ActionLogoResource); - - return { - resource, - }; -}; - +Table.useResource = useResource; +Table.useActionLogDetailsResource = useActionLogDetailsResource; Table.useActionLogsResource = useActionLogsResource; Table.useTableFilterAction = useTableFilterAction; Table.useTableCreateAction = useTableCreateAction; @@ -2662,4 +64,3 @@ Table.useTableDestroyAction = useTableDestroyAction; Table.useTableExportAction = useTableExportAction; Table.useTableIndex = useTableIndex; Table.useTableRowRecord = useTableRowRecord; -Table.SimpleDesignableBar = SimpleDesignableBar; diff --git a/packages/client/src/schemas/table/types.ts b/packages/client/src/schemas/table/types.ts new file mode 100644 index 000000000..e14abd146 --- /dev/null +++ b/packages/client/src/schemas/table/types.ts @@ -0,0 +1,22 @@ +import { ArrayField } from '@formily/core'; +import { FormProvider, observer, RecursionField, Schema, useField, useForm } from '@formily/react'; +import { BaseResult } from '@ahooksjs/use-request/lib/types'; +import { ListOptions, Resource } from '../../resource'; + +export interface ITableContext { + props: any; + field: ArrayField; + schema: Schema; + service: BaseResult; + selectedRowKeys?: any; + setSelectedRowKeys?: any; + pagination?: any; + setPagination?: any; + refresh?: any; + resource?: Resource; +} + +export interface ITableRowContext { + index: number; + record: any; +} diff --git a/packages/client/src/schemas/table/utils.ts b/packages/client/src/schemas/table/utils.ts new file mode 100644 index 000000000..b59e8f10e --- /dev/null +++ b/packages/client/src/schemas/table/utils.ts @@ -0,0 +1,164 @@ +import { Schema } from '@formily/react'; +import { uid } from '@formily/shared'; +import { interfaces } from '../database-field/interfaces'; +import { ISchema } from '..'; + +export const fieldsToSortColumns = (fields: any[]) => { + const dataSource = []; + + fields.forEach((field) => { + const fieldOption = interfaces.get(field.interface); + if (!fieldOption?.sortable) { + return; + } + dataSource.push({ + value: field.name, + label: field?.uiSchema?.title, + }); + }); + + return dataSource; +}; + +export const fieldsToFilterColumns = (fields: any[], options: any = {}) => { + const { fieldNames = [] } = options; + const properties = {}; + fields.forEach((field, index) => { + if (fieldNames?.length && !fieldNames.includes(field.name)) { + return; + } + const fieldOption = interfaces.get(field.interface); + if (!fieldOption?.operations) { + return; + } + properties[`column${index}`] = { + type: 'void', + title: field?.uiSchema?.title, + 'x-component': 'Filter.Column', + 'x-component-props': { + operations: fieldOption.operations, + }, + properties: { + [field.name]: { + ...field.uiSchema, + 'x-decorator': 'FormilyFormItem', + title: null, + }, + }, + }; + }); + return properties; +}; + +export const generateActionSchema = (type) => { + const actions: { [key: string]: ISchema } = { + filter: { + key: uid(), + name: uid(), + type: 'void', + title: "{{ t('Filter') }}", + 'x-align': 'left', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'filter', + }, + 'x-component': 'Table.Filter', + 'x-designable-bar': 'Table.Filter.DesignableBar', + 'x-component-props': { + fieldNames: [], + }, + }, + export: { + key: uid(), + type: 'void', + name: uid(), + title: "{{ t('Export') }}", + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'export', + }, + 'x-action-type': 'export', + 'x-component': 'Action', + 'x-designable-bar': 'Table.ExportActionDesignableBar', + 'x-component-props': { + fieldNames: [], + icon: 'ExportOutlined', + useAction: '{{ Table.useTableExportAction }}', + }, + }, + create: { + key: uid(), + type: 'void', + name: uid(), + title: "{{ t('Add new') }}", + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'create', + }, + 'x-component': 'Action', + 'x-component-props': { + type: 'primary', + icon: 'PlusOutlined', + }, + 'x-designable-bar': 'Table.Action.DesignableBar', + properties: { + modal: { + type: 'void', + title: "{{ t('Add record') }}", + 'x-decorator': 'Form', + 'x-component': 'Action.Drawer', + 'x-component-props': { + useOkAction: '{{ Table.useTableCreateAction }}', + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid', + 'x-component-props': { + addNewComponent: 'AddNew.FormItem', + }, + }, + }, + }, + }, + }, + destroy: { + key: uid(), + type: 'void', + name: uid(), + title: "{{ t('Delete') }}", + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'destroy', + }, + 'x-action-type': 'destroy', + 'x-component': 'Action', + 'x-designable-bar': 'Table.Action.DesignableBar', + 'x-component-props': { + confirm: { + title: "{{ t('Delete record') }}", + content: "{{ t('Are you sure you want to delete it?') }}", + }, + useAction: '{{ Table.useTableDestroyAction }}', + }, + }, + view: {}, + update: {}, + }; + return actions[type]; +}; + +export function isColumn(schema: Schema) { + return ['Table.Column'].includes(schema['x-component']); +} + +export function isColumnComponent(component: string) { + return ['Table.Operation', 'Table.Column'].includes(component); +} + +export function isOperationColumn(schema: Schema) { + return ['Table.Operation'].includes(schema['x-component']); +}