diff --git a/src/Form.tsx b/src/Form.tsx index 70f1faf..004d469 100644 --- a/src/Form.tsx +++ b/src/Form.tsx @@ -70,6 +70,7 @@ export interface RenderProps extends FormsDispatchProps { id: string; schema: JsonSchema7; + readOnly?: boolean; } export interface RenderCellProps extends RenderProps { data: any; diff --git a/src/controls/AntdInputControl.tsx b/src/controls/AntdInputControl.tsx index 0464039..d3361a6 100644 --- a/src/controls/AntdInputControl.tsx +++ b/src/controls/AntdInputControl.tsx @@ -38,6 +38,7 @@ export const AntdInputControl: React.FC = (props) const InnerComponent = input; const formatterId = uiOptions.formatter || 'base'; + const readOnly = uiOptions.readOnly; const query = uiOptions.query; const specialProps: any = {}; if (uiOptions.dataToFormatter) { @@ -61,7 +62,7 @@ export const AntdInputControl: React.FC = (props) } validateStatus={validateObj.validateStatus} help={validateObj.help}> - {editing ? ( + {editing && !readOnly ? ( = ({ onEdit, editing, }) => { + const { readOnly } = viewKindElement.options; return ( {({ width, height }: any) => (
onEdit()}> {title} - + {readOnly ? null : }
= ({ schema={{}} enabled={enabled} form={id} + readOnly={readOnly} />
diff --git a/src/layouts/AntdVerticalLayout.tsx b/src/layouts/AntdVerticalLayout.tsx index 3693571..d0f626c 100644 --- a/src/layouts/AntdVerticalLayout.tsx +++ b/src/layouts/AntdVerticalLayout.tsx @@ -26,6 +26,7 @@ export const AntdVerticalLayoutRenderer: React.FC = ({ enabled, visible, form, + readOnly, }) => { const Render: React.FC = ({ idx, viewKind, viewKindElement, viewDescr, enabled }) => { const options = viewKindElement.options || {}; @@ -51,7 +52,7 @@ export const AntdVerticalLayoutRenderer: React.FC = ({ return (
- {renderLayoutElements({ viewKind, viewKindElement, viewDescr, enabled, Render })} + {renderLayoutElements({ viewKind, viewKindElement, viewDescr, enabled, Render, readOnly })}
); diff --git a/src/table/BaseTableControl.tsx b/src/table/BaseTableControl.tsx index 712f16f..15fd2ed 100644 --- a/src/table/BaseTableControl.tsx +++ b/src/table/BaseTableControl.tsx @@ -99,6 +99,7 @@ export const BaseTableControl: React.FC = React.memo( dataSource={dataSource} loadExpandedData={loadExpandedData} tableMenu={tableMenu} + multiSelect={uiSchemaOptions['multiSelect']} isMenu={uiSchemaOptions['columnMenu']} parsedSchema={parsedSchema} onSelect={onSelect} diff --git a/src/table/basetable/BaseTable.scss b/src/table/basetable/BaseTable.scss index a3aa358..0284641 100644 --- a/src/table/basetable/BaseTable.scss +++ b/src/table/basetable/BaseTable.scss @@ -49,7 +49,7 @@ $table-prefix: BaseTable !default; $border: 1px solid #eeeeee !default; $header-background-color: #f8f8f8 !default; $header-font-weight: 700 !default; - $row-hovered-background-color: #ffffff !default; + $row-hovered-background-color: #f3f3f3 !default; $header-cell-hovered-background-color: #f3f3f3 !default; $sort-indicator-hovered-color: #888888 !default; $column-resizer-color: #cccccc !default; diff --git a/src/table/basetable/ReactBaseTable.tsx b/src/table/basetable/ReactBaseTable.tsx index ba3bde7..825eb3c 100644 --- a/src/table/basetable/ReactBaseTable.tsx +++ b/src/table/basetable/ReactBaseTable.tsx @@ -110,13 +110,13 @@ export const EditableTable: React.FC> = React.memo( options = {}, addDataToTarget, onSelect, + multiSelect, ...props }) => { const [data, setData] = useState([]); const [popupVisible, setPopupVisible] = useState(false); const [popupCoords, setPopupCoords] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); const [popupRecord, setPopupRecord] = useState({}); - const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [expandedRowKeys, setExpandedRowKeys] = useState([]); const sortColumns = createSortColumnsObject(sortDir); const [loadingMore, setLoadingMore] = useState(false); @@ -135,30 +135,42 @@ export const EditableTable: React.FC> = React.memo( ), headerRenderer: ({ columns, column, columnIndex, headerIndex, container }: any) => , }; + const onAddDataToTarget = (data: any) => { + setSelection([]); + addDataToTarget(data); + }; const handleSelectChange = ({ selected, rowData, rowIndex }: any) => { - const newSelectedRowKeys = [...selectedRowKeys]; - const key = rowData[rowKey]; - - if (selected) { - if (!newSelectedRowKeys.includes(key)) newSelectedRowKeys.push(key); + let newSelection = [...selection]; + const index = newSelection.indexOf(rowData); + if (multiSelect) { + if (selected) { + if (index === -1) newSelection.push(rowData); + } else { + if (index !== -1) { + newSelection.splice(index, 1); + } + } } else { - const index = newSelectedRowKeys.indexOf(key); - if (index > -1) { - newSelectedRowKeys.splice(index, 1); + if (selected) { + newSelection = [rowData]; + } else { + if (index !== -1) { + newSelection = []; + } } } - setSelectedRowKeys(newSelectedRowKeys); + setSelection(newSelection); }; - const handleSelectHeaderChange = ({ selected }: any) => { - let newSelectedRowKeys; + const handleSelectHeaderChange = ({ selected, data }: any) => { + let newSelection; if (selected) { - newSelectedRowKeys = dataSource.map((e: any) => e[rowKey]); + newSelection = data; } else { - newSelectedRowKeys = []; + newSelection = []; } - setSelectedRowKeys(newSelectedRowKeys); + setSelection(newSelection); }; const selectionColumn = { key: '__selection__', @@ -171,7 +183,8 @@ export const EditableTable: React.FC> = React.memo( cellRenderer: SelectionCell, headerRenderer: SelectionHeaderCell, dataSize: dataSource.length, - selectedRowKeys: selectedRowKeys, + multiSelect, + selection, onChange: handleSelectChange, onHeaderChange: handleSelectHeaderChange, }; @@ -224,17 +237,9 @@ export const EditableTable: React.FC> = React.memo( setPopupRecord(rowData); setPopupCoords({ x: event.clientX, y: event.clientY }); }, - onClick: ({ rowData, event }: any) => { + onClick: ({ rowData, event, ...props }: any) => { const idx = selection.indexOf(rowData); - let newSelection = [...selection]; - if (idx !== -1) { - if (newSelection.length === 1) newSelection = []; - else newSelection.splice(idx, 1); - } else { - newSelection.push(rowData); - } - setSelection(newSelection); - onSelect(newSelection); + handleSelectChange({ selected: idx === -1, rowData, rowIndex: null }); }, }; @@ -313,6 +318,11 @@ export const EditableTable: React.FC> = React.memo( }); }*/ }; + + const rowClassName = ({ rowData, rowIndex }: any): string => { + return selection.includes(rowData) ? 'row-selected' : ''; + }; + const renderOverlay = () => { if (loadingMore) return ( @@ -333,7 +343,6 @@ export const EditableTable: React.FC> = React.memo( setData(newData); }; useEffect(() => { - setSelectedRowKeys([]); setExpandedRowKeys([]); setSelection([]); onSelect([]); @@ -359,6 +368,7 @@ export const EditableTable: React.FC> = React.memo( fixed useIsScrolling rowRenderer={rowRenderer} + rowClassName={rowClassName} width={width} height={height} sortColumns={sortColumns} @@ -396,7 +406,7 @@ export const EditableTable: React.FC> = React.memo( visible={popupVisible} onCreateArtifactBefore={() => {}} target={target} - addDataToTarget={addDataToTarget} + addDataToTarget={onAddDataToTarget} onCreateArtifactAfter={() => {}} onDeleteArtifacts={() => { onDeleteRows(selection); diff --git a/src/table/basetable/TableCellsAndRows.tsx b/src/table/basetable/TableCellsAndRows.tsx index fc6fa14..8444711 100644 --- a/src/table/basetable/TableCellsAndRows.tsx +++ b/src/table/basetable/TableCellsAndRows.tsx @@ -10,7 +10,7 @@ import isEqual from 'lodash-es/isEqual'; import React from 'react'; import styled from 'styled-components'; -import { Checkbox } from 'antd'; +import { Checkbox, Radio } from 'antd'; import { SortableElement } from 'react-sortable-hoc'; const DraggableElement = SortableElement(({ children }: any) => children); @@ -42,11 +42,15 @@ export const rowProps = ({ rowData, ...rest }: any) => ({ export const SelectionCell: React.FC = (props: any) => { const { rowData, rowIndex, column } = props; - const { rowKey, onChange, selectedRowKeys } = column; - const checked = rowData ? selectedRowKeys.includes(rowData[rowKey]) : false; + const { onChange, selection, multiSelect } = column; + const checked = rowData ? selection.includes(rowData) : false; const handleChange = (e: any) => onChange({ selected: e.target.checked, rowData, rowIndex }); - return ; + return multiSelect ? ( + + ) : ( + + ); }; export const Handle = styled.div` diff --git a/src/table/basetable/TableHeader.tsx b/src/table/basetable/TableHeader.tsx index dd0bfd5..fd7df0a 100644 --- a/src/table/basetable/TableHeader.tsx +++ b/src/table/basetable/TableHeader.tsx @@ -13,12 +13,12 @@ import { Checkbox } from 'antd'; import './TableHeader.css'; export const SelectionHeaderCell: React.FC = (props: any) => { - const { column } = props; - const { dataSize, onHeaderChange, selectedRowKeys } = column; - const checked = selectedRowKeys.length === dataSize && dataSize !== 0; - const handleChange = (e: any) => onHeaderChange({ selected: e.target.checked }); + const { column, container } = props; + const { dataSize, onHeaderChange, selection, multiSelect } = column; + const checked = selection.length === dataSize && dataSize !== 0; + const handleChange = (e: any) => onHeaderChange({ selected: e.target.checked, data: container._data }); - return ; + return multiSelect ? : null; }; export const HeaderCell = ({ column, onSort, container }: any): JSX.Element => { diff --git a/src/table/basetable/table.css b/src/table/basetable/table.css index cb2d513..bbe93e0 100644 --- a/src/table/basetable/table.css +++ b/src/table/basetable/table.css @@ -27,3 +27,6 @@ .handler:hover { color: #1890ff; } +.row-selected { + background-color: #e6f7ff; +} diff --git a/src/util/ContextToProps.tsx b/src/util/ContextToProps.tsx index e23d4b1..5947756 100644 --- a/src/util/ContextToProps.tsx +++ b/src/util/ContextToProps.tsx @@ -534,28 +534,31 @@ export const withStoreToArrayProps = (Component: React.FC): React.FC = }); export const withLayoutProps = (Component: React.FC): React.FC => - observer(({ viewKind, viewKindElement, viewDescr, viewDescrElement, schema, enabled, form }) => { - const id = viewKindElement['@id'] || ''; - const enabledLayout = enabled && checkProperty('editable', id, viewKindElement, viewKind); - const visible = checkProperty('visible', id, viewKindElement, viewKind); - const { store } = useContext(MstContext); - if (viewKindElement.options && viewKindElement.options.connections) { - viewKindElement.options.connections.forEach((e: any) => store.setSaveLogic(e.from, e.to)); - } - return ( - - ); - }); + observer( + ({ viewKind, viewKindElement, viewDescr, viewDescrElement, schema, enabled, form, readOnly }) => { + const id = viewKindElement['@id'] || ''; + const enabledLayout = enabled && checkProperty('editable', id, viewKindElement, viewKind); + const visible = checkProperty('visible', id, viewKindElement, viewKind); + const { store } = useContext(MstContext); + if (viewKindElement.options && viewKindElement.options.connections) { + viewKindElement.options.connections.forEach((e: any) => store.setSaveLogic(e.from, e.to)); + } + return ( + + ); + }, + ); export const withStoreToSaveButtonProps = (Component: React.FC): React.FC => observer(({ viewKindElement, enabled }) => { diff --git a/src/util/layout.tsx b/src/util/layout.tsx index 20a3c31..07bec8f 100644 --- a/src/util/layout.tsx +++ b/src/util/layout.tsx @@ -19,6 +19,7 @@ export declare type Idx = { export interface RenderLayoutProps extends FormsDispatchProps { viewKindElement: IViewKindElement; Render: React.FC; + readOnly?: boolean; } export const renderLayoutElements = ({ @@ -27,12 +28,16 @@ export const renderLayoutElements = ({ viewDescr, enabled, Render, + readOnly, }: RenderLayoutProps): JSX.Element | JSX.Element[] => { const elements = viewKindElement.elements; //const id = viewKind['@id']; //const sort = id ? viewKind.properties && viewKind.properties[id] && viewKind.properties[id].order : undefined; if (!elements || elements.length === 0) return <>; - return elements.map((el: IViewKindElement, idx: number) => ( - - )); + return elements.map((el: IViewKindElement, idx: number) => { + el = { ...el, options: { ...el.options, readOnly } }; + return ( + + ); + }); }; diff --git a/stories/Form.stories.tsx b/stories/Form.stories.tsx index a56e82d..707fd62 100644 --- a/stories/Form.stories.tsx +++ b/stories/Form.stories.tsx @@ -7,6 +7,7 @@ * * SPDX-License-Identifier: GPL-3.0-only ********************************************************************************/ +import { cloneDeep } from 'lodash'; import moment from 'moment'; import React from 'react'; import { Meta, Story } from '@storybook/react'; @@ -51,6 +52,9 @@ const viewKinds = [ { '@id': 'rm:_83hd7f', '@type': 'aldkg:FormLayout', + options: { + readOnly: false, + }, elements: [ { '@id': 'rm:_17Gj78', @@ -153,7 +157,7 @@ const Template: Story = (args: any) => { 'reqs2', client, rootModelInitialState, - createAdditionalColls(args.viewKinds || viewKinds, args.data), + createAdditionalColls(args.viewKinds, args.data), ); const store: any = asReduxStore(rootStore); // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -169,11 +173,33 @@ const Template: Story = (args: any) => { ); }; -export const RemoteData = Template.bind({}); -RemoteData.args = {}; +export const EditableRemoteData = Template.bind({}); +EditableRemoteData.args = { + viewKinds, +}; + +export const ReadOnlyRemoteData = Template.bind({}); +const readOnlyFormViewKinds = cloneDeep(viewKinds); +readOnlyFormViewKinds[0].elements[0].options.readOnly = true; +ReadOnlyRemoteData.args = { + viewKinds: readOnlyFormViewKinds, +}; + +export const EditableObjectWithNullProperty = Template.bind({}); +EditableObjectWithNullProperty.args = { + viewKinds, + data: [ + { + creator: null, + assetFolder: null, + description: 'TestDescr', + }, + ], +}; -export const ObjectWithNullProperty = Template.bind({}); -ObjectWithNullProperty.args = { +export const ReadOnlyObjectWithNullProperty = Template.bind({}); +ReadOnlyObjectWithNullProperty.args = { + viewKinds: readOnlyFormViewKinds, data: [ { creator: null, @@ -183,12 +209,20 @@ ObjectWithNullProperty.args = { ], }; -export const EmptyObject = Template.bind({}); -EmptyObject.args = { +export const EditableEmptyObject = Template.bind({}); +EditableEmptyObject.args = { + viewKinds, data: [{}], }; -export const NoObject = Template.bind({}); -NoObject.args = { +export const ReadOnlyEmptyObject = Template.bind({}); +ReadOnlyEmptyObject.args = { + readOnlyFormViewKinds, data: [{}], }; + +export const ReadOnlyNoObject = Template.bind({}); +ReadOnlyNoObject.args = { + viewKinds, // form should be read-only even if viewKind is not read-only + data: [], +}; diff --git a/stories/TableRemoteMktp.stories.tsx b/stories/TableRemoteMktp.stories.tsx index 218dca0..87b7cdd 100644 --- a/stories/TableRemoteMktp.stories.tsx +++ b/stories/TableRemoteMktp.stories.tsx @@ -72,6 +72,7 @@ const viewKinds = [ resizeableHeader: true, height: 'all-empty-space', style: { height: '100%' }, + multiSelect: true, order: [ 'imageUrl', 'name', diff --git a/stories/TwoTables.stories.tsx b/stories/TwoTables.stories.tsx index 0449b85..f96cfea 100644 --- a/stories/TwoTables.stories.tsx +++ b/stories/TwoTables.stories.tsx @@ -166,6 +166,7 @@ const viewKinds = [ name: 'правую таблицу', iri: 'mktp:ProductCards_in_Product_Coll', }, + multiSelect: true, draggable: true, resizeableHeader: true, height: 'all-empty-space',