mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-01 23:38:37 +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 {
|
||||
min-width: 280px;
|
||||
padding: 0.5rem 0;
|
||||
@ -23,18 +22,6 @@
|
||||
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-body {
|
||||
width: 100%;
|
||||
@ -58,10 +45,6 @@
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.sf-metadata-rename-view-popover-body {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-item:hover .metadata-view-icon {
|
||||
color: #fff;
|
||||
fill: #fff;
|
||||
|
@ -1,2 +1 @@
|
||||
export { default as Rename } from './rename';
|
||||
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 {
|
||||
width: 100%;
|
||||
width: 95%;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
margin-top: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.metadata-tree-view .metadata-views-icon {
|
||||
|
@ -149,6 +149,13 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
|
||||
}
|
||||
}, [handleInputSubmit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showInput && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [showInput]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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 classnames from 'classnames';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import Icon from '../../../components/icon';
|
||||
import ItemDropdownMenu from '../../../components/dropdown-menu/item-dropdown-menu';
|
||||
import { Rename } from '../../components/popover/view-popover';
|
||||
import { Utils, isMobile } from '../../../utils/utils';
|
||||
import { useMetadata } from '../../hooks';
|
||||
import { VIEW_TYPE_ICON } from '../../constants';
|
||||
import { isValidViewName } from '../../utils/validate';
|
||||
import { isEnter } from '../../utils/hotkey';
|
||||
import toaster from '../../../components/toast';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@ -25,7 +28,11 @@ const ViewItem = ({
|
||||
const [highlight, setHighlight] = useState(false);
|
||||
const [freeze, setFreeze] = 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 otherViewsName = Object.values(viewsMap).filter(v => v._id !== view._id).map(v => v.name);
|
||||
@ -78,7 +85,7 @@ const ViewItem = ({
|
||||
|
||||
const operationClick = useCallback((operationKey) => {
|
||||
if (operationKey === 'rename') {
|
||||
setRenamePopoverShow(true);
|
||||
setRenaming(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -93,13 +100,9 @@ const ViewItem = ({
|
||||
}
|
||||
}, [onDelete, onCopy]);
|
||||
|
||||
const closeRenamePopover = useCallback(() => {
|
||||
setRenamePopoverShow(false);
|
||||
}, []);
|
||||
|
||||
const renameView = useCallback((name, failCallback) => {
|
||||
onUpdate({ name }, () => {
|
||||
setRenamePopoverShow(false);
|
||||
setRenaming(false);
|
||||
document.title = `${name} - Seafile`;
|
||||
}, (error) => {
|
||||
failCallback(error);
|
||||
@ -143,6 +146,57 @@ const ViewItem = ({
|
||||
onMove && onMove(dragData.view_id, view._id);
|
||||
}, [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 (
|
||||
<>
|
||||
<div
|
||||
@ -162,7 +216,17 @@ const ViewItem = ({
|
||||
onDragOver={onDragMove}
|
||||
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 className="left-icon">
|
||||
<div className="tree-node-icon">
|
||||
@ -183,9 +247,6 @@ const ViewItem = ({
|
||||
)}
|
||||
</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