mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-16 14:08:12 +00:00
Optimize/view rename (#6878)
* optimize view rename ux * update * optimize ux * optimize code --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
parent
021af2bc4c
commit
7e1e349d34
@ -1,4 +1,3 @@
|
|||||||
.sf-metadata-rename-view-popover .popover,
|
|
||||||
.sf-metadata-addview-popover .popover {
|
.sf-metadata-addview-popover .popover {
|
||||||
min-width: 280px;
|
min-width: 280px;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
@ -23,18 +22,6 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sf-metadata-rename-view-popover .sf-metadata-rename-view-popover-header {
|
|
||||||
width: 100%;
|
|
||||||
height: 1.5rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left;
|
|
||||||
color: #666;
|
|
||||||
opacity: 1;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sf-metadata-addview-popover
|
.sf-metadata-addview-popover
|
||||||
.sf-metadata-addview-popover-body {
|
.sf-metadata-addview-popover-body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -58,10 +45,6 @@
|
|||||||
fill: #666;
|
fill: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sf-metadata-rename-view-popover-body {
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item:hover .metadata-view-icon {
|
.dropdown-item:hover .metadata-view-icon {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
fill: #fff;
|
fill: #fff;
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export { default as Rename } from './rename';
|
|
||||||
export { default as AddView } from './add-view';
|
export { default as AddView } from './add-view';
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
import React, { useCallback, useRef, useState } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Alert, Input } from 'reactstrap';
|
|
||||||
import { CustomizePopover } from '@seafile/sf-metadata-ui-component';
|
|
||||||
import { gettext } from '../../../../../utils/constants';
|
|
||||||
import { isValidViewName } from '../../../../utils/validate';
|
|
||||||
import { isEnter } from '../../../../utils/hotkey';
|
|
||||||
|
|
||||||
import '../index.css';
|
|
||||||
|
|
||||||
const Rename = ({ value, target, otherViewsName, toggle, onSubmit }) => {
|
|
||||||
const [inputValue, setInputValue] = useState(value || '');
|
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const inputRef = useRef(null);
|
|
||||||
|
|
||||||
const onChange = useCallback((e) => {
|
|
||||||
setInputValue(e.target.value);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onToggle = useCallback(() => {
|
|
||||||
toggle();
|
|
||||||
}, [toggle]);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback((event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
const { isValid, message } = isValidViewName(inputValue, otherViewsName);
|
|
||||||
if (!isValid) {
|
|
||||||
setErrorMessage(message);
|
|
||||||
inputRef.current.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message === value) {
|
|
||||||
onToggle();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSubmit(message);
|
|
||||||
}, [value, inputValue, otherViewsName, onSubmit, onToggle]);
|
|
||||||
|
|
||||||
const onKeyDown = useCallback((event) => {
|
|
||||||
if (isEnter(event)) {
|
|
||||||
handleSubmit(event);
|
|
||||||
}
|
|
||||||
}, [handleSubmit]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomizePopover
|
|
||||||
className='sf-metadata-rename-view-popover'
|
|
||||||
target={target}
|
|
||||||
placement='right-start'
|
|
||||||
hideArrow={true}
|
|
||||||
fade={false}
|
|
||||||
modifiers={{ preventOverflow: { boundariesElement: document.body } }}
|
|
||||||
canHide={!errorMessage}
|
|
||||||
hide={onToggle}
|
|
||||||
hideWithEsc={onToggle}
|
|
||||||
>
|
|
||||||
<div className='sf-metadata-rename-view-popover-header'>
|
|
||||||
{gettext('Rename view')}
|
|
||||||
</div>
|
|
||||||
<div className="seafile-divider dropdown-divider"></div>
|
|
||||||
<div className='sf-metadata-rename-view-popover-body'>
|
|
||||||
<Input
|
|
||||||
innerRef={inputRef}
|
|
||||||
className="sf-metadata-view-rename-input"
|
|
||||||
value={inputValue}
|
|
||||||
onChange={onChange}
|
|
||||||
autoFocus={true}
|
|
||||||
onBlur={handleSubmit}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
/>
|
|
||||||
{errorMessage && (<Alert color="danger" className="mt-2 mb-0">{errorMessage}</Alert>)}
|
|
||||||
</div>
|
|
||||||
</CustomizePopover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Rename.propTypes = {
|
|
||||||
value: PropTypes.string,
|
|
||||||
target: PropTypes.string.isRequired,
|
|
||||||
otherViewsName: PropTypes.array,
|
|
||||||
toggle: PropTypes.func.isRequired,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Rename;
|
|
@ -59,11 +59,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.metadata-tree-view .sf-metadata-view-input {
|
.metadata-tree-view .sf-metadata-view-input {
|
||||||
width: 100%;
|
width: 95%;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadata-tree-view .metadata-views-icon {
|
.metadata-tree-view .metadata-views-icon {
|
||||||
|
@ -149,6 +149,13 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
|
|||||||
}
|
}
|
||||||
}, [handleInputSubmit]);
|
}, [handleInputSubmit]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showInput && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
inputRef.current.select();
|
||||||
|
}
|
||||||
|
}, [showInput]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="tree-view tree metadata-tree-view">
|
<div className="tree-view tree metadata-tree-view">
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { Input } from 'reactstrap';
|
||||||
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 Icon from '../../../components/icon';
|
import Icon from '../../../components/icon';
|
||||||
import ItemDropdownMenu from '../../../components/dropdown-menu/item-dropdown-menu';
|
import ItemDropdownMenu from '../../../components/dropdown-menu/item-dropdown-menu';
|
||||||
import { Rename } from '../../components/popover/view-popover';
|
|
||||||
import { Utils, isMobile } from '../../../utils/utils';
|
import { Utils, isMobile } from '../../../utils/utils';
|
||||||
import { useMetadata } from '../../hooks';
|
import { useMetadata } from '../../hooks';
|
||||||
import { VIEW_TYPE_ICON } from '../../constants';
|
import { VIEW_TYPE_ICON } from '../../constants';
|
||||||
|
import { isValidViewName } from '../../utils/validate';
|
||||||
|
import { isEnter } from '../../utils/hotkey';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@ -25,7 +28,11 @@ const ViewItem = ({
|
|||||||
const [highlight, setHighlight] = useState(false);
|
const [highlight, setHighlight] = useState(false);
|
||||||
const [freeze, setFreeze] = useState(false);
|
const [freeze, setFreeze] = useState(false);
|
||||||
const [isDropShow, setDropShow] = useState(false);
|
const [isDropShow, setDropShow] = useState(false);
|
||||||
const [isShowRenamePopover, setRenamePopoverShow] = useState(false);
|
const [isRenaming, setRenaming] = useState(false);
|
||||||
|
const [inputValue, setInputValue] = useState(view.name || '');
|
||||||
|
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
const { viewsMap } = useMetadata();
|
const { viewsMap } = useMetadata();
|
||||||
|
|
||||||
const otherViewsName = Object.values(viewsMap).filter(v => v._id !== view._id).map(v => v.name);
|
const otherViewsName = Object.values(viewsMap).filter(v => v._id !== view._id).map(v => v.name);
|
||||||
@ -78,7 +85,7 @@ const ViewItem = ({
|
|||||||
|
|
||||||
const operationClick = useCallback((operationKey) => {
|
const operationClick = useCallback((operationKey) => {
|
||||||
if (operationKey === 'rename') {
|
if (operationKey === 'rename') {
|
||||||
setRenamePopoverShow(true);
|
setRenaming(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,13 +100,9 @@ const ViewItem = ({
|
|||||||
}
|
}
|
||||||
}, [onDelete, onCopy]);
|
}, [onDelete, onCopy]);
|
||||||
|
|
||||||
const closeRenamePopover = useCallback(() => {
|
|
||||||
setRenamePopoverShow(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const renameView = useCallback((name, failCallback) => {
|
const renameView = useCallback((name, failCallback) => {
|
||||||
onUpdate({ name }, () => {
|
onUpdate({ name }, () => {
|
||||||
setRenamePopoverShow(false);
|
setRenaming(false);
|
||||||
document.title = `${name} - Seafile`;
|
document.title = `${name} - Seafile`;
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
failCallback(error);
|
failCallback(error);
|
||||||
@ -143,6 +146,57 @@ const ViewItem = ({
|
|||||||
onMove && onMove(dragData.view_id, view._id);
|
onMove && onMove(dragData.view_id, view._id);
|
||||||
}, [canDrop, view, onMove]);
|
}, [canDrop, view, onMove]);
|
||||||
|
|
||||||
|
const onChange = useCallback((e) => {
|
||||||
|
setInputValue(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const { isValid, message } = isValidViewName(inputValue, otherViewsName);
|
||||||
|
if (!isValid) {
|
||||||
|
toaster.danger(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message === view.name) {
|
||||||
|
setRenaming(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renameView(message);
|
||||||
|
}, [view, inputValue, otherViewsName, renameView]);
|
||||||
|
|
||||||
|
const onKeyDown = useCallback((event) => {
|
||||||
|
if (isEnter(event)) {
|
||||||
|
handleSubmit(event);
|
||||||
|
unfreezeItem();
|
||||||
|
}
|
||||||
|
}, [handleSubmit, unfreezeItem]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isRenaming && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
inputRef.current.select();
|
||||||
|
}
|
||||||
|
}, [isRenaming]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event) => {
|
||||||
|
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
||||||
|
handleSubmit(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isRenaming) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [isRenaming, handleSubmit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -162,7 +216,17 @@ const ViewItem = ({
|
|||||||
onDragOver={onDragMove}
|
onDragOver={onDragMove}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
>
|
>
|
||||||
{view.name}
|
{isRenaming ? (
|
||||||
|
<Input
|
||||||
|
innerRef={inputRef}
|
||||||
|
className="sf-metadata-view-input mt-0"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={onChange}
|
||||||
|
autoFocus={true}
|
||||||
|
onBlur={() => setRenaming(false)}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
/>
|
||||||
|
) : view.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="left-icon">
|
<div className="left-icon">
|
||||||
<div className="tree-node-icon">
|
<div className="tree-node-icon">
|
||||||
@ -183,9 +247,6 @@ const ViewItem = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isShowRenamePopover && (
|
|
||||||
<Rename value={view.name} otherViewsName={otherViewsName} target={`metadata-view-dropdown-item-${view._id}`} toggle={closeRenamePopover} onSubmit={renameView} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user