mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-19 10:26:17 +00:00
Improve add view UI (#6529)
* improve add view and rename view ui update view popover format code remove redundant code improve input ui in view popover remove redundant code update rename view ui * update icon size and color * update icon color --------- Co-authored-by: Michael An <2331806369@qq.com>
This commit is contained in:
@@ -22,9 +22,10 @@
|
||||
}
|
||||
|
||||
.metadata-tree-view .metadata-views-icon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
line-height: 1.5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.metadata-tree-view .sf-metadata-add-view {
|
||||
@@ -56,3 +57,11 @@
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.metadata-tree-view .sf-metadata-view-input {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CustomizeAddTool } from '@seafile/sf-metadata-ui-component';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||
import ViewItem from './view-item';
|
||||
import NameDialog from './name-dialog';
|
||||
import { useMetadata } from '../hooks';
|
||||
import { Form, Input } from 'reactstrap';
|
||||
import Icon from '../../components/icon';
|
||||
import { AddView } from '../metadata-view/components/popover/view-popover';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@@ -14,7 +16,6 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
|
||||
if (userPerm !== 'rw' && userPerm !== 'admin') return false;
|
||||
return true;
|
||||
}, [userPerm]);
|
||||
const [showAddViewDialog, setSowAddViewDialog] = useState(false);
|
||||
const [, setState] = useState(0);
|
||||
const {
|
||||
showFirstView,
|
||||
@@ -27,6 +28,11 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
|
||||
moveView
|
||||
} = useMetadata();
|
||||
|
||||
const [showAddViewPopover, setShowAddViewPopover] = useState(false);
|
||||
const [showInput, setShowInput] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
const [inputValue, setInputValue] = useState('Untitled');
|
||||
|
||||
useEffect(() => {
|
||||
const { origin, pathname, search } = window.location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
@@ -49,14 +55,6 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const openAddView = useCallback(() => {
|
||||
setSowAddViewDialog(true);
|
||||
}, []);
|
||||
|
||||
const closeAddView = useCallback(() => {
|
||||
setSowAddViewDialog(false);
|
||||
}, []);
|
||||
|
||||
const onUpdateView = useCallback((viewId, update, successCallback, failCallback) => {
|
||||
updateView(viewId, update, () => {
|
||||
setState(n => n + 1);
|
||||
@@ -64,6 +62,46 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
|
||||
}, failCallback);
|
||||
}, [updateView]);
|
||||
|
||||
const togglePopover = (event) => {
|
||||
event.stopPropagation();
|
||||
setShowAddViewPopover(!showAddViewPopover);
|
||||
};
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
setInputValue(event.target.value);
|
||||
};
|
||||
|
||||
const handlePopoverOptionClick = () => {
|
||||
setShowInput(true);
|
||||
setShowAddViewPopover(false);
|
||||
};
|
||||
|
||||
const handleInputSubmit = useCallback((event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
addView(inputValue);
|
||||
setShowInput(false);
|
||||
setInputValue('Untitled');
|
||||
}, [inputValue, addView]);
|
||||
|
||||
const handleClickOutsideInput = useCallback((event) => {
|
||||
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
||||
setShowInput(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (showInput) {
|
||||
inputRef.current.select();
|
||||
document.addEventListener('click', handleClickOutsideInput);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutsideInput);
|
||||
};
|
||||
}, [showInput, inputRef, handleClickOutsideInput]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tree-view tree metadata-tree-view">
|
||||
@@ -86,18 +124,41 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
|
||||
onMove={moveView}
|
||||
/>);
|
||||
})}
|
||||
{canAdd &&
|
||||
<CustomizeAddTool
|
||||
className="sf-metadata-add-view"
|
||||
callBack={openAddView}
|
||||
footerName={gettext('Add view')}
|
||||
addIconClassName="sf-metadata-add-view-icon"
|
||||
/>
|
||||
}
|
||||
{showInput && (
|
||||
<Form onSubmit={handleInputSubmit} className='tree-view-inner sf-metadata-view-form'>
|
||||
<div className="left-icon">
|
||||
<div className="tree-node-icon">
|
||||
<Icon symbol="table" className="metadata-views-icon" />
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
className='sf-metadata-view-input'
|
||||
innerRef={inputRef}
|
||||
type='text'
|
||||
id='add-view-input'
|
||||
name='add-view'
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
{canAdd && (
|
||||
<div id="sf-metadata-view-popover">
|
||||
<CustomizeAddTool
|
||||
className="sf-metadata-add-view"
|
||||
callBack={togglePopover}
|
||||
footerName={gettext('Add view')}
|
||||
addIconClassName="sf-metadata-add-view-icon"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showAddViewDialog && (<NameDialog title={gettext('Add view')} onSubmit={addView} onToggle={closeAddView} />)}
|
||||
{showAddViewPopover && (
|
||||
<AddView target='sf-metadata-view-popover' toggle={togglePopover} onOptionClick={handlePopoverOptionClick} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -4,12 +4,11 @@ import classnames from 'classnames';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import Icon from '../../../components/icon';
|
||||
import ItemDropdownMenu from '../../../components/dropdown-menu/item-dropdown-menu';
|
||||
import NameDialog from '../name-dialog';
|
||||
import { Rename } from '../../metadata-view/components/popover/view-popover';
|
||||
import { Utils, isMobile } from '../../../utils/utils';
|
||||
|
||||
import './index.css';
|
||||
|
||||
|
||||
const ViewItem = ({
|
||||
canDelete,
|
||||
userPerm,
|
||||
@@ -22,8 +21,8 @@ const ViewItem = ({
|
||||
}) => {
|
||||
const [highlight, setHighlight] = useState(false);
|
||||
const [freeze, setFreeze] = useState(false);
|
||||
const [isShowRenameDialog, setRenameDialogShow] = useState(false);
|
||||
const [isDropShow, setDropShow] = useState(false);
|
||||
const [isShowRenamePopover, setRenamePopoverShow] = useState(false);
|
||||
|
||||
const canUpdate = useMemo(() => {
|
||||
if (userPerm !== 'rw' && userPerm !== 'admin') return false;
|
||||
@@ -72,7 +71,7 @@ const ViewItem = ({
|
||||
|
||||
const operationClick = useCallback((operationKey) => {
|
||||
if (operationKey === 'rename') {
|
||||
setRenameDialogShow(true);
|
||||
setRenamePopoverShow(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,13 +81,14 @@ const ViewItem = ({
|
||||
}
|
||||
}, [onDelete]);
|
||||
|
||||
const closeRenameDialog = useCallback(() => {
|
||||
setRenameDialogShow(false);
|
||||
const closeRenamePopover = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
setRenamePopoverShow(false);
|
||||
}, []);
|
||||
|
||||
const renameView = useCallback((name, failCallback) => {
|
||||
onUpdate({ name }, () => {
|
||||
setRenameDialogShow(false);
|
||||
setRenamePopoverShow(false);
|
||||
}, failCallback);
|
||||
}, [onUpdate]);
|
||||
|
||||
@@ -154,7 +154,7 @@ const ViewItem = ({
|
||||
<Icon symbol="table" className="metadata-views-icon" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-icon">
|
||||
<div className="right-icon" id={`metadata-view-dropdown-item-${view._id}`} >
|
||||
{highlight && (
|
||||
<ItemDropdownMenu
|
||||
item={{ name: 'metadata-view' }}
|
||||
@@ -168,11 +168,10 @@ const ViewItem = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isShowRenameDialog && (
|
||||
<NameDialog title={gettext('Rename view')} value={view.name} onSubmit={renameView} onToggle={closeRenameDialog} />
|
||||
{isShowRenamePopover && (
|
||||
<Rename value={view.name} target={`metadata-view-dropdown-item-${view._id}`} toggle={closeRenamePopover} onSubmit={renameView} />
|
||||
)}
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -0,0 +1,62 @@
|
||||
import React, { useRef, useEffect, useCallback } from 'react';
|
||||
import { UncontrolledPopover } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../../utils';
|
||||
import Icon from '../../../../../../components/icon';
|
||||
|
||||
import '../index.css';
|
||||
|
||||
const AddView = ({ target, toggle, onOptionClick }) => {
|
||||
const popoverRef = useRef(null);
|
||||
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
if (popoverRef.current && !popoverRef.current.contains(event.target)) {
|
||||
toggle(event);
|
||||
}
|
||||
}, [toggle]);
|
||||
|
||||
useEffect(() => {
|
||||
if (popoverRef.current) {
|
||||
document.addEventListener('click', handleClickOutside, true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside, true);
|
||||
};
|
||||
}, [handleClickOutside]);
|
||||
|
||||
return (
|
||||
<UncontrolledPopover
|
||||
className='sf-metadata-addview-popover'
|
||||
isOpen={true}
|
||||
toggle={toggle}
|
||||
target={target}
|
||||
placement='right-start'
|
||||
hideArrow={true}
|
||||
fade={false}
|
||||
boundariesElement={document.body}
|
||||
>
|
||||
<div ref={popoverRef}>
|
||||
<div className='sf-metadata-addview-popover-header'>{gettext('New view')}</div>
|
||||
<div className='sf-metadata-addview-popover-body'>
|
||||
<button className='dropdown-item sf-metadata-addview-popover-item' onClick={onOptionClick}>
|
||||
<div className="left-icon">
|
||||
<div className="metadata-view-icon">
|
||||
<Icon symbol="table" />
|
||||
</div>
|
||||
</div>
|
||||
<div>{gettext('Table')}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</UncontrolledPopover>
|
||||
);
|
||||
};
|
||||
|
||||
AddView.propTypes = {
|
||||
target: PropTypes.string.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onOptionClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AddView;
|
@@ -0,0 +1,68 @@
|
||||
.sf-metadata-rename-view-popover .popover,
|
||||
.sf-metadata-addview-popover .popover {
|
||||
max-width: none;
|
||||
min-width: 280px;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.sf-metadata-view-form {
|
||||
display: flex;
|
||||
padding-left: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sf-metadata-addview-popover .sf-metadata-addview-popover-header {
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
color: #666;
|
||||
opacity: 1;
|
||||
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%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dropdown-item.sf-metadata-addview-popover-item {
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-item.sf-metadata-addview-popover-item .metadata-view-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.sf-metadata-rename-view-popover-body {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-item:hover .metadata-view-icon {
|
||||
color: #fff;
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
export { default as Rename } from './rename';
|
||||
export { default as AddView } from './add-view';
|
@@ -0,0 +1,78 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Form, Input, UncontrolledPopover } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../../utils';
|
||||
import '../index.css';
|
||||
|
||||
const Rename = ({ value, target, toggle, onSubmit }) => {
|
||||
const [inputValue, setInputValue] = useState(value || '');
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const onChange = useCallback((e) => {
|
||||
setInputValue(e.target.value);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = useCallback((e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onSubmit(inputValue);
|
||||
}, [inputValue, onSubmit]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutSide = (event) => {
|
||||
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
||||
toggle(event);
|
||||
}
|
||||
};
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.select();
|
||||
document.addEventListener('mousedown', handleClickOutSide);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutSide);
|
||||
};
|
||||
}, [toggle]);
|
||||
|
||||
return (
|
||||
<UncontrolledPopover
|
||||
className='sf-metadata-rename-view-popover'
|
||||
isOpen={true}
|
||||
toggle={toggle}
|
||||
target={target}
|
||||
placement='right-start'
|
||||
hideArrow={true}
|
||||
fade={false}
|
||||
boundariesElement={document.body}
|
||||
>
|
||||
<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'>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Input
|
||||
innerRef={inputRef}
|
||||
className='sf-metadata-view-input'
|
||||
type='text'
|
||||
id="rename-input"
|
||||
name='rename'
|
||||
value={inputValue}
|
||||
onChange={onChange}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</UncontrolledPopover>
|
||||
);
|
||||
};
|
||||
|
||||
Rename.propTypes = {
|
||||
value: PropTypes.string,
|
||||
target: PropTypes.string.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Rename;
|
Reference in New Issue
Block a user