diff --git a/frontend/src/metadata/metadata-tree-view/index.css b/frontend/src/metadata/metadata-tree-view/index.css
index b6b985f187..a94f0befbe 100644
--- a/frontend/src/metadata/metadata-tree-view/index.css
+++ b/frontend/src/metadata/metadata-tree-view/index.css
@@ -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;
+}
diff --git a/frontend/src/metadata/metadata-tree-view/index.js b/frontend/src/metadata/metadata-tree-view/index.js
index 3d1a6cc953..8cd555931b 100644
--- a/frontend/src/metadata/metadata-tree-view/index.js
+++ b/frontend/src/metadata/metadata-tree-view/index.js
@@ -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 (
<>
@@ -86,18 +124,41 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
onMove={moveView}
/>);
})}
- {canAdd &&
-
- }
+ {showInput && (
+
+ )}
+ {canAdd && (
+
+
+
+ )}
- {showAddViewDialog && ()}
+ {showAddViewPopover && (
+
+ )}
>
);
};
diff --git a/frontend/src/metadata/metadata-tree-view/view-item/index.js b/frontend/src/metadata/metadata-tree-view/view-item/index.js
index cd68c6b427..bcca778b52 100644
--- a/frontend/src/metadata/metadata-tree-view/view-item/index.js
+++ b/frontend/src/metadata/metadata-tree-view/view-item/index.js
@@ -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 = ({
-
+
{highlight && (
- {isShowRenameDialog && (
-
+ {isShowRenamePopover && (
+
)}
>
-
);
};
diff --git a/frontend/src/metadata/metadata-view/components/popover/view-popover/add-view/index.js b/frontend/src/metadata/metadata-view/components/popover/view-popover/add-view/index.js
new file mode 100644
index 0000000000..79405bfcd0
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/popover/view-popover/add-view/index.js
@@ -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 (
+
+
+
{gettext('New view')}
+
+
+
+
+
+ );
+};
+
+AddView.propTypes = {
+ target: PropTypes.string.isRequired,
+ toggle: PropTypes.func.isRequired,
+ onOptionClick: PropTypes.func.isRequired,
+};
+
+export default AddView;
diff --git a/frontend/src/metadata/metadata-view/components/popover/view-popover/index.css b/frontend/src/metadata/metadata-view/components/popover/view-popover/index.css
new file mode 100644
index 0000000000..3ccf3925a5
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/popover/view-popover/index.css
@@ -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;
+}
diff --git a/frontend/src/metadata/metadata-view/components/popover/view-popover/index.js b/frontend/src/metadata/metadata-view/components/popover/view-popover/index.js
new file mode 100644
index 0000000000..39ad3c2597
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/popover/view-popover/index.js
@@ -0,0 +1,2 @@
+export { default as Rename } from './rename';
+export { default as AddView } from './add-view';
diff --git a/frontend/src/metadata/metadata-view/components/popover/view-popover/rename/index.js b/frontend/src/metadata/metadata-view/components/popover/view-popover/rename/index.js
new file mode 100644
index 0000000000..1b07b93827
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/popover/view-popover/rename/index.js
@@ -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 (
+
+
+ {gettext('Rename view')}
+
+
+
+
+
+
+ );
+};
+
+Rename.propTypes = {
+ value: PropTypes.string,
+ target: PropTypes.string.isRequired,
+ toggle: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+};
+
+export default Rename;