mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-03 07:55:36 +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 [value, setValue] = useState(column.data || {});
|
||||||
const [popoverShow, setPopoverShow] = useState(false);
|
const [popoverShow, setPopoverShow] = useState(false);
|
||||||
const columnKey = useRef(null);
|
const columnKey = useRef(null);
|
||||||
// const [error, setError] = useState('');
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
getValue: () => value,
|
getValue: () => value,
|
||||||
setValue: (value) => setValue(value),
|
setValue: (value) => setValue(value),
|
||||||
getIsPopoverShow: () => popoverShow,
|
getIsPopoverShow: () => popoverShow,
|
||||||
// setError: (error) => setError(error),
|
|
||||||
}), [popoverShow, value]);
|
}), [popoverShow, value]);
|
||||||
|
|
||||||
const onChange = useCallback((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 PropTypes from 'prop-types';
|
||||||
import { Button, UncontrolledPopover } from 'reactstrap';
|
import { Button, Popover } from 'reactstrap';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { gettext } from '../../../../utils/constants';
|
import { gettext } from '../../../../utils/constants';
|
||||||
import { useMetadataView } from '../../../hooks/metadata-view';
|
import { useMetadataView } from '../../../hooks/metadata-view';
|
||||||
@@ -18,9 +18,7 @@ import './index.css';
|
|||||||
const DEFAULT_POPOVER_INNER_WIDTH = 350;
|
const DEFAULT_POPOVER_INNER_WIDTH = 350;
|
||||||
const COLUMN_TYPE_POPOVER_INNER_WIDTH = {};
|
const COLUMN_TYPE_POPOVER_INNER_WIDTH = {};
|
||||||
|
|
||||||
const ColumnPopover = ({ target, onChange }) => {
|
const ColumnPopover = ({ target, column, onSelect, onCancel, onSubmit }) => {
|
||||||
const [column, setColumn] = useState({});
|
|
||||||
const popoverRef = useRef(null);
|
|
||||||
const popoverInnerRef = useRef(null);
|
const popoverInnerRef = useRef(null);
|
||||||
const nameRef = useRef(null);
|
const nameRef = useRef(null);
|
||||||
const typeRef = 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;
|
return COLUMN_TYPE_POPOVER_INNER_WIDTH[column.type] || DEFAULT_POPOVER_INNER_WIDTH;
|
||||||
}, [column]);
|
}, [column]);
|
||||||
|
|
||||||
const toggle = useCallback((event) => {
|
|
||||||
if (typeRef.current?.getIsPopoverShow()) return;
|
|
||||||
if (dataRef.current?.getIsPopoverShow()) return;
|
|
||||||
popoverRef.current.toggle();
|
|
||||||
}, [typeRef, dataRef]);
|
|
||||||
|
|
||||||
const onColumnChange = useCallback((newColumn) => {
|
const onColumnChange = useCallback((newColumn) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
typeRef.current.setPopoverState(false);
|
typeRef.current.setPopoverState(false);
|
||||||
}, 100);
|
}, 100);
|
||||||
if (ObjectUtils.isSameObject(column, newColumn)) return;
|
if (ObjectUtils.isSameObject(column, newColumn)) return;
|
||||||
setColumn(newColumn);
|
onSelect(newColumn);
|
||||||
if (newColumn.type === column.type) return;
|
if (newColumn.type === column.type) return;
|
||||||
dataRef.current.setValue({});
|
dataRef.current.setValue({});
|
||||||
}, [typeRef, column]);
|
}, [typeRef, column, onSelect]);
|
||||||
|
|
||||||
const onSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
nameRef.current.setError('');
|
nameRef.current.setError('');
|
||||||
typeRef.current.setError('');
|
typeRef.current.setError('');
|
||||||
let flag = 1;
|
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 });
|
onSubmit(column.unique ? column.key : columnName, column.type, { key: column.unique ? column.key : '', data });
|
||||||
toggle();
|
}, [nameRef, column, metadata, onSubmit]);
|
||||||
}, [nameRef, column, metadata, onChange, toggle]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UncontrolledPopover
|
<Popover
|
||||||
target={target}
|
target={target}
|
||||||
trigger="legacy"
|
isOpen={true}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
hideArrow={true}
|
hideArrow={true}
|
||||||
toggle={toggle}
|
|
||||||
fade={false}
|
fade={false}
|
||||||
ref={popoverRef}
|
|
||||||
className="sf-metadata-column-popover"
|
className="sf-metadata-column-popover"
|
||||||
>
|
>
|
||||||
<div className="sf-metadata-column-popover-inner" ref={popoverInnerRef} style={{ width: popoverInnerWidth }}>
|
<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} />
|
<Data ref={dataRef} column={column} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classnames('sf-metadata-column-popover-footer', { 'sf-metadata-number-column-popover-footer': column.type === CellType.NUMBER })}>
|
<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="secondary" className="mr-4" onClick={onCancel}>{gettext('Cancel')}</Button>
|
||||||
<Button color="primary" onClick={onSubmit}>{gettext('Submit')}</Button>
|
<Button color="primary" onClick={handleSubmit}>{gettext('Submit')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UncontrolledPopover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ColumnPopover.propTypes = {
|
ColumnPopover.propTypes = {
|
||||||
target: PropTypes.string.isRequired,
|
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;
|
export default ColumnPopover;
|
||||||
|
@@ -22,6 +22,7 @@ const Name = forwardRef(({ readOnly, value }, ref) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(value);
|
setName(value);
|
||||||
|
setError('');
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -1,204 +1,24 @@
|
|||||||
import React, { forwardRef, useImperativeHandle, useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
import React, { forwardRef, useImperativeHandle, useState, useCallback, useRef, useEffect } from 'react';
|
||||||
import { FormGroup, FormFeedback, Label, Dropdown, DropdownToggle, DropdownMenu, Input, DropdownItem } from 'reactstrap';
|
import { FormGroup, FormFeedback, Label, Dropdown, DropdownToggle } from 'reactstrap';
|
||||||
import Icon from '@/components/icon';
|
import Icon from '@/components/icon';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { gettext } from '../../../../../utils/constants';
|
import { gettext } from '../../../../../utils/constants';
|
||||||
import { getColumnDisplayName } from '../../../../utils/column';
|
import CustomDropdownMenu from '../custom-dropdown-menu';
|
||||||
import { CellType, COLUMNS_ICON_CONFIG, DEFAULT_DATE_FORMAT, DEFAULT_SHOOTING_TIME_FORMAT, PRIVATE_COLUMN_KEY, DEFAULT_RATE_DATA } from '../../../../constants';
|
|
||||||
|
|
||||||
import './index.css';
|
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 Type = forwardRef(({ column, onChange }, ref) => {
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [searchValue, setSearchValue] = useState('');
|
|
||||||
const [isPredefinedPropertiesOpen, setPredefinedPropertiesOpen] = useState(false);
|
const [isPredefinedPropertiesOpen, setPredefinedPropertiesOpen] = useState(false);
|
||||||
const [isCustomPropertiesOpen, setCustomPropertiesOpen] = useState(false);
|
const [isCustomPropertiesOpen, setCustomPropertiesOpen] = useState(false);
|
||||||
const inputRef = useRef(null);
|
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) => {
|
const togglePredefinedProperties = useCallback((e) => {
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
setPredefinedPropertiesOpen(prev => !prev);
|
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, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
setError: (error) => setError(error),
|
setError: (error) => setError(error),
|
||||||
getIsPopoverShow: () => isPredefinedPropertiesOpen || isCustomPropertiesOpen,
|
getIsPopoverShow: () => isPredefinedPropertiesOpen || isCustomPropertiesOpen,
|
||||||
@@ -213,8 +33,7 @@ const Type = forwardRef(({ column, onChange }, ref) => {
|
|||||||
}), [isPredefinedPropertiesOpen, isCustomPropertiesOpen]);
|
}), [isPredefinedPropertiesOpen, isCustomPropertiesOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange(COLUMNS.find(c => c.groupby === 'basics') || COLUMNS[0]);
|
setError('');
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -231,70 +50,20 @@ const Type = forwardRef(({ column, onChange }, ref) => {
|
|||||||
tag="span"
|
tag="span"
|
||||||
className="sf-metadata-column-type-info"
|
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>
|
<span className="mr-auto">{column.name}</span>
|
||||||
<i className="sf3-font sf3-font-down" aria-hidden="true"></i>
|
<i className="sf3-font sf3-font-down" aria-hidden="true"></i>
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<DropdownMenu
|
<CustomDropdownMenu
|
||||||
|
column={column}
|
||||||
modifiers={[{
|
modifiers={[{
|
||||||
name: 'offset',
|
name: 'offset',
|
||||||
options: {
|
options: {
|
||||||
offset: [0, 17],
|
offset: [0, 17],
|
||||||
}
|
}
|
||||||
}]}>
|
}]}
|
||||||
<div className="search-column-container">
|
onSelect={onChange}
|
||||||
<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>
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
{error && (<FormFeedback>{error}</FormFeedback>)}
|
{error && (<FormFeedback>{error}</FormFeedback>)}
|
||||||
</FormGroup>
|
</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 PropTypes from 'prop-types';
|
||||||
import ColumnPopover from '../../../../../components/popover/column-popover';
|
import ColumnPopover from '../../../../../components/popover/column-popover';
|
||||||
import Icon from '../../../../../../components/icon';
|
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';
|
import './index.css';
|
||||||
|
|
||||||
const InsertColumn = ({ lastColumn, height, groupOffsetLeft, insertColumn: insertColumnAPI }) => {
|
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 id = useMemo(() => 'sf-metadata-add-column', []);
|
||||||
const ref = useRef(null);
|
|
||||||
const style = useMemo(() => {
|
const style = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
height: height,
|
height: height,
|
||||||
@@ -20,36 +26,73 @@ const InsertColumn = ({ lastColumn, height, groupOffsetLeft, insertColumn: inser
|
|||||||
};
|
};
|
||||||
}, [lastColumn, height, groupOffsetLeft]);
|
}, [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 });
|
insertColumnAPI(name, type, { key, data });
|
||||||
|
setColumnPopoverShow(false);
|
||||||
}, [insertColumnAPI]);
|
}, [insertColumnAPI]);
|
||||||
|
|
||||||
const onHotKey = useCallback((event) => {
|
const handleSelect = useCallback((column) => {
|
||||||
if (isEnter(event) && document.activeElement && document.activeElement.id === id) {
|
setSelectedColumn(column);
|
||||||
openPopover();
|
setColumnMenuOpen(false);
|
||||||
}
|
setColumnPopoverShow(true);
|
||||||
}, [id, openPopover]);
|
}, []);
|
||||||
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('keydown', onHotKey);
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', onHotKey);
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
});
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="sf-metadata-record-header-cell">
|
<Dropdown
|
||||||
<div className="sf-metadata-result-table-cell column insert-column" style={style} id={id} ref={ref}>
|
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" />
|
<Icon symbol="add-table" />
|
||||||
</div>
|
</DropdownToggle>
|
||||||
</div>
|
<CustomDropdownMenu onSelect={handleSelect} />
|
||||||
<ColumnPopover target={id} onChange={insertColumn} />
|
</Dropdown>
|
||||||
|
{isColumnPopoverShow && !isColumnMenuOpen && (
|
||||||
|
<ColumnPopover
|
||||||
|
target={id}
|
||||||
|
column={selectedColumn}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user