mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-31 14:42:10 +00:00
optimize add column interaction (#7546)
* fix icon props error * optimize add column interaction --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle, Input } from 'reactstrap';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from '../../../../components/icon';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { CellType, COLUMNS_ICON_CONFIG, DEFAULT_DATE_FORMAT, DEFAULT_RATE_DATA, DEFAULT_SHOOTING_TIME_FORMAT, PRIVATE_COLUMN_KEY } from '../../../constants';
|
||||
import { getColumnDisplayName } from '../../../utils/column';
|
||||
|
||||
const COLUMNS = [
|
||||
{
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_COLLABORATORS),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_COLLABORATORS,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_REVIEWER),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_REVIEWER,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.OWNER),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.OWNER,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
|
||||
type: CellType.DATE,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME,
|
||||
canChangeName: false,
|
||||
data: { format: DEFAULT_DATE_FORMAT },
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
|
||||
type: CellType.LONG_TEXT,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_DESCRIPTION),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_DESCRIPTION,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
|
||||
type: CellType.SINGLE_SELECT,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_STATUS),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_STATUS,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
|
||||
type: CellType.DATE,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.CAPTURE_TIME),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.CAPTURE_TIME,
|
||||
canChangeName: false,
|
||||
data: { format: DEFAULT_SHOOTING_TIME_FORMAT },
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
|
||||
type: CellType.RATE,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_RATE),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_RATE,
|
||||
canChangeName: false,
|
||||
data: DEFAULT_RATE_DATA,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.TEXT],
|
||||
type: CellType.TEXT,
|
||||
name: gettext('Text'),
|
||||
canChangeName: true,
|
||||
key: CellType.TEXT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
|
||||
type: CellType.LONG_TEXT,
|
||||
name: gettext('Long text'),
|
||||
canChangeName: true,
|
||||
key: CellType.LONG_TEXT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.NUMBER],
|
||||
type: CellType.NUMBER,
|
||||
name: gettext('Number'),
|
||||
canChangeName: true,
|
||||
key: CellType.NUMBER,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: gettext('Collaborator'),
|
||||
canChangeName: true,
|
||||
key: CellType.COLLABORATOR,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.CHECKBOX],
|
||||
type: CellType.CHECKBOX,
|
||||
name: gettext('Checkbox'),
|
||||
canChangeName: true,
|
||||
key: CellType.CHECKBOX,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
|
||||
type: CellType.DATE,
|
||||
name: gettext('Date'),
|
||||
canChangeName: true,
|
||||
key: CellType.DATE,
|
||||
data: { format: DEFAULT_DATE_FORMAT },
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
|
||||
type: CellType.SINGLE_SELECT,
|
||||
name: gettext('Single select'),
|
||||
canChangeName: true,
|
||||
key: CellType.SINGLE_SELECT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.MULTIPLE_SELECT],
|
||||
type: CellType.MULTIPLE_SELECT,
|
||||
name: gettext('Multiple select'),
|
||||
canChangeName: true,
|
||||
key: CellType.MULTIPLE_SELECT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
|
||||
type: CellType.RATE,
|
||||
name: gettext('Rate'),
|
||||
canChangeName: true,
|
||||
key: CellType.RATE,
|
||||
data: DEFAULT_RATE_DATA,
|
||||
groupby: 'basics',
|
||||
},
|
||||
];
|
||||
|
||||
const CustomDropdownMenu = ({ column, modifiers, onSelect }) => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [isCustomPropertiesOpen, setCustomPropertiesOpen] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const displayColumns = useMemo(() => {
|
||||
const validValue = searchValue.trim().toLocaleLowerCase();
|
||||
return COLUMNS.filter(item => {
|
||||
const columnName = item.name.toLocaleLowerCase();
|
||||
return columnName.indexOf(validValue) > -1;
|
||||
});
|
||||
}, [searchValue]);
|
||||
|
||||
const basicsColumns = useMemo(() => {
|
||||
return displayColumns.filter(item => item.groupby === 'basics');
|
||||
}, [displayColumns]);
|
||||
|
||||
const predefinedColumns = useMemo(() => {
|
||||
return displayColumns.filter(item => item.groupby === 'predefined');
|
||||
}, [displayColumns]);
|
||||
|
||||
const toggleCustomProperties = useCallback((e) => {
|
||||
e?.stopPropagation();
|
||||
setCustomPropertiesOpen(prev => !prev);
|
||||
setSearchValue('');
|
||||
}, []);
|
||||
|
||||
const onSearchColumn = useCallback((event) => {
|
||||
const value = event.target.value;
|
||||
if (value === searchValue) return;
|
||||
setSearchValue(value);
|
||||
}, [searchValue]);
|
||||
|
||||
const onSearchClick = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
const handleSelect = useCallback((column) => {
|
||||
onSelect(column);
|
||||
setSearchValue('');
|
||||
}, [onSelect]);
|
||||
|
||||
return (
|
||||
<DropdownMenu modifiers={modifiers}>
|
||||
<div className="search-column-container">
|
||||
<Input onChange={onSearchColumn} placeholder={gettext('Search properties')} value={searchValue} onClick={onSearchClick} ref={inputRef} />
|
||||
</div>
|
||||
{displayColumns.length > 0 && predefinedColumns.length > 0 && (
|
||||
<>
|
||||
{predefinedColumns.map(item => (
|
||||
<DropdownItem
|
||||
key={item.key}
|
||||
className={classnames('column-type-item text-truncate', { 'active': item.key === column?.key })}
|
||||
onMouseEnter={() => setCustomPropertiesOpen(false)}
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
<Icon symbol={item.icon} className="sf-metadata-icon" />
|
||||
<span>{item.name}</span>
|
||||
</DropdownItem>
|
||||
))}
|
||||
{basicsColumns.length > 0 && (
|
||||
<>
|
||||
<DropdownItem className="w-100" divider />
|
||||
<Dropdown
|
||||
className="w-100"
|
||||
direction="end"
|
||||
isOpen={isCustomPropertiesOpen}
|
||||
toggle={toggleCustomProperties}
|
||||
onMouseEnter={() => setCustomPropertiesOpen(true)}
|
||||
onMouseMove={(e) => {e.stopPropagation();}}
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='span'
|
||||
className="column-type-item dropdown-item text-truncate"
|
||||
>
|
||||
<Icon symbol="edit" className="sf-metadata-icon" />
|
||||
<span className="mr-auto">{gettext('Custom properties')}</span>
|
||||
<i className="sf3-font-down sf3-font rotate-270"></i>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
{basicsColumns.map((item, index) => (
|
||||
<DropdownItem
|
||||
key={index}
|
||||
className={classnames('column-type-item text-truncate', { 'active': item.key === column?.key })}
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
<Icon symbol={item.icon} className="sf-metadata-icon" />
|
||||
<span>{item.name}</span>
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
CustomDropdownMenu.propTypes = {
|
||||
column: PropTypes.object,
|
||||
modifiers: PropTypes.array,
|
||||
onSelect: PropTypes.func,
|
||||
};
|
||||
|
||||
export default CustomDropdownMenu;
|
@@ -12,13 +12,11 @@ const Data = forwardRef(({ column }, ref) => {
|
||||
const [value, setValue] = useState(column.data || {});
|
||||
const [popoverShow, setPopoverShow] = useState(false);
|
||||
const columnKey = useRef(null);
|
||||
// const [error, setError] = useState('');
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getValue: () => value,
|
||||
setValue: (value) => setValue(value),
|
||||
getIsPopoverShow: () => popoverShow,
|
||||
// setError: (error) => setError(error),
|
||||
}), [popoverShow, value]);
|
||||
|
||||
const onChange = useCallback((value) => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, UncontrolledPopover } from 'reactstrap';
|
||||
import { Button, Popover } from 'reactstrap';
|
||||
import classnames from 'classnames';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { useMetadataView } from '../../../hooks/metadata-view';
|
||||
@@ -18,9 +18,7 @@ import './index.css';
|
||||
const DEFAULT_POPOVER_INNER_WIDTH = 350;
|
||||
const COLUMN_TYPE_POPOVER_INNER_WIDTH = {};
|
||||
|
||||
const ColumnPopover = ({ target, onChange }) => {
|
||||
const [column, setColumn] = useState({});
|
||||
const popoverRef = useRef(null);
|
||||
const ColumnPopover = ({ target, column, onSelect, onCancel, onSubmit }) => {
|
||||
const popoverInnerRef = useRef(null);
|
||||
const nameRef = useRef(null);
|
||||
const typeRef = useRef(null);
|
||||
@@ -31,23 +29,17 @@ const ColumnPopover = ({ target, onChange }) => {
|
||||
return COLUMN_TYPE_POPOVER_INNER_WIDTH[column.type] || DEFAULT_POPOVER_INNER_WIDTH;
|
||||
}, [column]);
|
||||
|
||||
const toggle = useCallback((event) => {
|
||||
if (typeRef.current?.getIsPopoverShow()) return;
|
||||
if (dataRef.current?.getIsPopoverShow()) return;
|
||||
popoverRef.current.toggle();
|
||||
}, [typeRef, dataRef]);
|
||||
|
||||
const onColumnChange = useCallback((newColumn) => {
|
||||
setTimeout(() => {
|
||||
typeRef.current.setPopoverState(false);
|
||||
}, 100);
|
||||
if (ObjectUtils.isSameObject(column, newColumn)) return;
|
||||
setColumn(newColumn);
|
||||
onSelect(newColumn);
|
||||
if (newColumn.type === column.type) return;
|
||||
dataRef.current.setValue({});
|
||||
}, [typeRef, column]);
|
||||
}, [typeRef, column, onSelect]);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const handleSubmit = useCallback(() => {
|
||||
nameRef.current.setError('');
|
||||
typeRef.current.setError('');
|
||||
let flag = 1;
|
||||
@@ -80,19 +72,16 @@ const ColumnPopover = ({ target, onChange }) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
onChange(column.unique ? column.key : columnName, column.type, { key: column.unique ? column.key : '', data });
|
||||
toggle();
|
||||
}, [nameRef, column, metadata, onChange, toggle]);
|
||||
onSubmit(column.unique ? column.key : columnName, column.type, { key: column.unique ? column.key : '', data });
|
||||
}, [nameRef, column, metadata, onSubmit]);
|
||||
|
||||
return (
|
||||
<UncontrolledPopover
|
||||
<Popover
|
||||
target={target}
|
||||
trigger="legacy"
|
||||
isOpen={true}
|
||||
placement="bottom-end"
|
||||
hideArrow={true}
|
||||
toggle={toggle}
|
||||
fade={false}
|
||||
ref={popoverRef}
|
||||
className="sf-metadata-column-popover"
|
||||
>
|
||||
<div className="sf-metadata-column-popover-inner" ref={popoverInnerRef} style={{ width: popoverInnerWidth }}>
|
||||
@@ -102,17 +91,20 @@ const ColumnPopover = ({ target, onChange }) => {
|
||||
<Data ref={dataRef} column={column} />
|
||||
</div>
|
||||
<div className={classnames('sf-metadata-column-popover-footer', { 'sf-metadata-number-column-popover-footer': column.type === CellType.NUMBER })}>
|
||||
<Button color="secondary" className="mr-4" onClick={toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={onSubmit}>{gettext('Submit')}</Button>
|
||||
<Button color="secondary" className="mr-4" onClick={onCancel}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={handleSubmit}>{gettext('Submit')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</UncontrolledPopover>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
ColumnPopover.propTypes = {
|
||||
target: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
column: PropTypes.object.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ColumnPopover;
|
||||
|
@@ -22,6 +22,7 @@ const Name = forwardRef(({ readOnly, value }, ref) => {
|
||||
|
||||
useEffect(() => {
|
||||
setName(value);
|
||||
setError('');
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
|
@@ -1,204 +1,24 @@
|
||||
import React, { forwardRef, useImperativeHandle, useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
||||
import { FormGroup, FormFeedback, Label, Dropdown, DropdownToggle, DropdownMenu, Input, DropdownItem } from 'reactstrap';
|
||||
import React, { forwardRef, useImperativeHandle, useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { FormGroup, FormFeedback, Label, Dropdown, DropdownToggle } from 'reactstrap';
|
||||
import Icon from '@/components/icon';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { gettext } from '../../../../../utils/constants';
|
||||
import { getColumnDisplayName } from '../../../../utils/column';
|
||||
import { CellType, COLUMNS_ICON_CONFIG, DEFAULT_DATE_FORMAT, DEFAULT_SHOOTING_TIME_FORMAT, PRIVATE_COLUMN_KEY, DEFAULT_RATE_DATA } from '../../../../constants';
|
||||
import CustomDropdownMenu from '../custom-dropdown-menu';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const COLUMNS = [
|
||||
{
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_COLLABORATORS),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_COLLABORATORS,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_REVIEWER),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_REVIEWER,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.OWNER),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.OWNER,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
|
||||
type: CellType.DATE,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME,
|
||||
canChangeName: false,
|
||||
data: { format: DEFAULT_DATE_FORMAT },
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
|
||||
type: CellType.LONG_TEXT,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_DESCRIPTION),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_DESCRIPTION,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
|
||||
type: CellType.SINGLE_SELECT,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_STATUS),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_STATUS,
|
||||
canChangeName: false,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
|
||||
type: CellType.DATE,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.CAPTURE_TIME),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.CAPTURE_TIME,
|
||||
canChangeName: false,
|
||||
data: { format: DEFAULT_SHOOTING_TIME_FORMAT },
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
|
||||
type: CellType.RATE,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_RATE),
|
||||
unique: true,
|
||||
key: PRIVATE_COLUMN_KEY.FILE_RATE,
|
||||
canChangeName: false,
|
||||
data: DEFAULT_RATE_DATA,
|
||||
groupby: 'predefined'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.TEXT],
|
||||
type: CellType.TEXT,
|
||||
name: gettext('Text'),
|
||||
canChangeName: true,
|
||||
key: CellType.TEXT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
|
||||
type: CellType.LONG_TEXT,
|
||||
name: gettext('Long text'),
|
||||
canChangeName: true,
|
||||
key: CellType.LONG_TEXT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.NUMBER],
|
||||
type: CellType.NUMBER,
|
||||
name: gettext('Number'),
|
||||
canChangeName: true,
|
||||
key: CellType.NUMBER,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
|
||||
type: CellType.COLLABORATOR,
|
||||
name: gettext('Collaborator'),
|
||||
canChangeName: true,
|
||||
key: CellType.COLLABORATOR,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.CHECKBOX],
|
||||
type: CellType.CHECKBOX,
|
||||
name: gettext('Checkbox'),
|
||||
canChangeName: true,
|
||||
key: CellType.CHECKBOX,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
|
||||
type: CellType.DATE,
|
||||
name: gettext('Date'),
|
||||
canChangeName: true,
|
||||
key: CellType.DATE,
|
||||
data: { format: DEFAULT_DATE_FORMAT },
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
|
||||
type: CellType.SINGLE_SELECT,
|
||||
name: gettext('Single select'),
|
||||
canChangeName: true,
|
||||
key: CellType.SINGLE_SELECT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.MULTIPLE_SELECT],
|
||||
type: CellType.MULTIPLE_SELECT,
|
||||
name: gettext('Multiple select'),
|
||||
canChangeName: true,
|
||||
key: CellType.MULTIPLE_SELECT,
|
||||
groupby: 'basics'
|
||||
}, {
|
||||
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
|
||||
type: CellType.RATE,
|
||||
name: gettext('Rate'),
|
||||
canChangeName: true,
|
||||
key: CellType.RATE,
|
||||
data: DEFAULT_RATE_DATA,
|
||||
groupby: 'basics',
|
||||
},
|
||||
];
|
||||
|
||||
const Type = forwardRef(({ column, onChange }, ref) => {
|
||||
const [error, setError] = useState('');
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [isPredefinedPropertiesOpen, setPredefinedPropertiesOpen] = useState(false);
|
||||
const [isCustomPropertiesOpen, setCustomPropertiesOpen] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const displayColumns = useMemo(() => {
|
||||
const validValue = searchValue.trim().toLocaleLowerCase();
|
||||
return COLUMNS.filter(item => {
|
||||
const columnName = item.name.toLocaleLowerCase();
|
||||
return columnName.indexOf(validValue) > -1;
|
||||
});
|
||||
}, [searchValue]);
|
||||
|
||||
const basicsColumns = useMemo(() => {
|
||||
return displayColumns.filter(item => item.groupby === 'basics');
|
||||
}, [displayColumns]);
|
||||
|
||||
const predefinedColumns = useMemo(() => {
|
||||
return displayColumns.filter(item => item.groupby === 'predefined');
|
||||
}, [displayColumns]);
|
||||
|
||||
const togglePredefinedProperties = useCallback((e) => {
|
||||
e?.stopPropagation();
|
||||
setPredefinedPropertiesOpen(prev => !prev);
|
||||
}, []);
|
||||
|
||||
const toggleCustomProperties = useCallback((e) => {
|
||||
e?.stopPropagation();
|
||||
setCustomPropertiesOpen(prev => !prev);
|
||||
setSearchValue('');
|
||||
}, []);
|
||||
|
||||
const onSelectPredefinedColumn = useCallback((column) => {
|
||||
onChange(column);
|
||||
setSearchValue('');
|
||||
}, [onChange]);
|
||||
|
||||
const onSelectCustomColumn = useCallback((column) => {
|
||||
onChange(column);
|
||||
togglePredefinedProperties();
|
||||
}, [onChange, togglePredefinedProperties]);
|
||||
|
||||
const onSearchColumn = useCallback((event) => {
|
||||
const value = event.target.value;
|
||||
if (value === searchValue) return;
|
||||
setSearchValue(value);
|
||||
}, [searchValue]);
|
||||
|
||||
const onSearchClick = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setError: (error) => setError(error),
|
||||
getIsPopoverShow: () => isPredefinedPropertiesOpen || isCustomPropertiesOpen,
|
||||
@@ -213,8 +33,7 @@ const Type = forwardRef(({ column, onChange }, ref) => {
|
||||
}), [isPredefinedPropertiesOpen, isCustomPropertiesOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(COLUMNS.find(c => c.groupby === 'basics') || COLUMNS[0]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
setError('');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -231,70 +50,20 @@ const Type = forwardRef(({ column, onChange }, ref) => {
|
||||
tag="span"
|
||||
className="sf-metadata-column-type-info"
|
||||
>
|
||||
<Icon iconName={column.icon} className="mr-2" />
|
||||
<Icon symbol={column.icon} className="sf-metadata-icon mr-2" />
|
||||
<span className="mr-auto">{column.name}</span>
|
||||
<i className="sf3-font sf3-font-down" aria-hidden="true"></i>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
<CustomDropdownMenu
|
||||
column={column}
|
||||
modifiers={[{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 17],
|
||||
}
|
||||
}]}>
|
||||
<div className="search-column-container">
|
||||
<Input onChange={onSearchColumn} placeholder={gettext('Search properties')} value={searchValue} onClick={onSearchClick} ref={inputRef} />
|
||||
</div>
|
||||
{displayColumns.length > 0 && predefinedColumns.length > 0 && (
|
||||
<>
|
||||
{predefinedColumns.map(item => (
|
||||
<DropdownItem
|
||||
key={item.key}
|
||||
className={classnames('column-type-item text-truncate', { 'active': item.key === column.key })}
|
||||
onMouseEnter={() => setCustomPropertiesOpen(false)}
|
||||
onClick={() => onSelectPredefinedColumn(item)}
|
||||
>
|
||||
<Icon iconName={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
</DropdownItem>
|
||||
))}
|
||||
{basicsColumns.length > 0 && (
|
||||
<>
|
||||
<DropdownItem className="w-100" divider />
|
||||
<Dropdown
|
||||
className="w-100"
|
||||
direction="end"
|
||||
isOpen={isCustomPropertiesOpen}
|
||||
toggle={toggleCustomProperties}
|
||||
onMouseEnter={() => setCustomPropertiesOpen(true)}
|
||||
onMouseMove={(e) => {e.stopPropagation();}}
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='span'
|
||||
className="column-type-item dropdown-item text-truncate"
|
||||
>
|
||||
<Icon iconName="edit" />
|
||||
<span className="mr-auto">{gettext('Custom properties')}</span>
|
||||
<i className="sf3-font-down sf3-font rotate-270"></i>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
{basicsColumns.map((item, index) => (
|
||||
<DropdownItem
|
||||
key={index}
|
||||
className={classnames('column-type-item text-truncate', { 'active': item.key === column.key })}
|
||||
onClick={() => onSelectCustomColumn(item)}
|
||||
>
|
||||
<Icon iconName={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
}]}
|
||||
onSelect={onChange}
|
||||
/>
|
||||
</Dropdown>
|
||||
{error && (<FormFeedback>{error}</FormFeedback>)}
|
||||
</FormGroup>
|
||||
|
@@ -1,14 +1,20 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Dropdown, DropdownToggle } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import ColumnPopover from '../../../../../components/popover/column-popover';
|
||||
import Icon from '../../../../../../components/icon';
|
||||
import { isEnter } from '../../../../../../utils/hotkey';
|
||||
import { getEventClassName } from '../../../../../../utils/dom';
|
||||
import CustomDropdownMenu from '../../../../../components/popover/column-popover/custom-dropdown-menu';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const InsertColumn = ({ lastColumn, height, groupOffsetLeft, insertColumn: insertColumnAPI }) => {
|
||||
const [isColumnMenuOpen, setColumnMenuOpen] = useState(false);
|
||||
const [isColumnPopoverShow, setColumnPopoverShow] = useState(false);
|
||||
const [selectedColumn, setSelectedColumn] = useState(null);
|
||||
|
||||
const id = useMemo(() => 'sf-metadata-add-column', []);
|
||||
const ref = useRef(null);
|
||||
|
||||
const style = useMemo(() => {
|
||||
return {
|
||||
height: height,
|
||||
@@ -20,36 +26,73 @@ const InsertColumn = ({ lastColumn, height, groupOffsetLeft, insertColumn: inser
|
||||
};
|
||||
}, [lastColumn, height, groupOffsetLeft]);
|
||||
|
||||
const openPopover = useCallback(() => {
|
||||
ref?.current?.click();
|
||||
}, [ref]);
|
||||
|
||||
const insertColumn = useCallback((name, type, { key, data }) => {
|
||||
const toggleAddColumn = useCallback(() => {
|
||||
setColumnMenuOpen(!isColumnMenuOpen);
|
||||
}, [isColumnMenuOpen]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
setColumnMenuOpen(false);
|
||||
setColumnPopoverShow(false);
|
||||
setSelectedColumn(null);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = useCallback((name, type, { key, data }) => {
|
||||
insertColumnAPI(name, type, { key, data });
|
||||
setColumnPopoverShow(false);
|
||||
}, [insertColumnAPI]);
|
||||
|
||||
const onHotKey = useCallback((event) => {
|
||||
if (isEnter(event) && document.activeElement && document.activeElement.id === id) {
|
||||
openPopover();
|
||||
}
|
||||
}, [id, openPopover]);
|
||||
const handleSelect = useCallback((column) => {
|
||||
setSelectedColumn(column);
|
||||
setColumnMenuOpen(false);
|
||||
setColumnPopoverShow(true);
|
||||
}, []);
|
||||
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
if (!isColumnPopoverShow) return;
|
||||
if (!event.target) return;
|
||||
const className = getEventClassName(event);
|
||||
if (className.indexOf('column-type-item') > -1) return;
|
||||
const popover = document.querySelector('.sf-metadata-column-popover');
|
||||
if ((popover && popover.contains(event.target))) return;
|
||||
setColumnPopoverShow(false);
|
||||
}, [isColumnPopoverShow]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', onHotKey);
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onHotKey);
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sf-metadata-record-header-cell">
|
||||
<div className="sf-metadata-result-table-cell column insert-column" style={style} id={id} ref={ref}>
|
||||
<Dropdown
|
||||
id={id}
|
||||
className="sf-metadata-record-header-cell sf-metadata-column-type"
|
||||
isOpen={isColumnMenuOpen}
|
||||
direction="down"
|
||||
toggle={toggleAddColumn}
|
||||
style={style}
|
||||
>
|
||||
<DropdownToggle
|
||||
tag="span"
|
||||
className="sf-metadata-result-table-cell column insert-column"
|
||||
>
|
||||
<Icon symbol="add-table" />
|
||||
</div>
|
||||
</div>
|
||||
<ColumnPopover target={id} onChange={insertColumn} />
|
||||
</DropdownToggle>
|
||||
<CustomDropdownMenu onSelect={handleSelect} />
|
||||
</Dropdown>
|
||||
{isColumnPopoverShow && !isColumnMenuOpen && (
|
||||
<ColumnPopover
|
||||
target={id}
|
||||
column={selectedColumn}
|
||||
onSelect={handleSelect}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user