mirror of
https://github.com/haiwen/seahub.git
synced 2025-05-12 18:05:05 +00:00
refactor(metadata): remove ui-component (#7492)
1. ModalPortal 2. Icon 3. IconBtn 4. Loading 5. CenteredLoading 6. ClickOutside 7. SearchInput 8. Switch 9. CustomizeAddTool 10. SfCalendar 11. SfFilterCalendar 12. CustomizeSelect 13. CustomizePopover 14. FieldDisplaySettings 15. Formatters 16. remove duplicate codes
This commit is contained in:
parent
67083238c2
commit
890880a5c8
frontend
package-lock.jsonpackage.jsonindex.css
src
components
centered-loading
click-outside.jscommon-add-tool
common/group-select
cur-dir-path
customize-popover.jscustomize-select
dialog
department-detail-dialog.js
department-detail-widget
repo-history.jsrepo-share-admin
select-dirent-body.jstransfer-dialog.jsdir-view-mode/extension-prompts
dirent-detail
icon-btn
icon.jsloading.jsloading
popover
search-input.jssf-table
editors
editor-container
tags-editor
searcher
table-main
records-footer
records-header
records
utils
share-link-panel
switch
css
metadata/components
cell-editors
checkbox-editor
collaborator-editor
date-editor.jseditor-container
editor.jsmultiple-select-editor
rate-editor
single-select-editor
tags-editor
cell-formatter
99
frontend/package-lock.json
generated
99
frontend/package-lock.json
generated
@ -14,12 +14,19 @@
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@gatsbyjs/reach-router": "2.0.1",
|
||||
<<<<<<< HEAD
|
||||
"@seafile/react-image-lightbox": "4.0.2",
|
||||
=======
|
||||
"@seafile/react-image-lightbox": "4.0.1",
|
||||
>>>>>>> 1997d1d4b (refactor(metadata): remove ui-component)
|
||||
"@seafile/resumablejs": "1.1.16",
|
||||
"@seafile/sdoc-editor": "2.0.11",
|
||||
"@seafile/seafile-calendar": "0.0.28",
|
||||
"@seafile/seafile-editor": "2.0.0",
|
||||
<<<<<<< HEAD
|
||||
"@seafile/sf-metadata-ui-component": "^1.0.2",
|
||||
=======
|
||||
>>>>>>> 1997d1d4b (refactor(metadata): remove ui-component)
|
||||
"@seafile/stldraw-editor": "1.0.0",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
"@uiw/codemirror-themes": "^4.23.5",
|
||||
@ -3244,23 +3251,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
|
||||
},
|
||||
"node_modules/@formatjs/intl-unified-numberformat": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmmirror.com/@formatjs/intl-unified-numberformat/-/intl-unified-numberformat-3.3.7.tgz",
|
||||
"integrity": "sha512-KnWgLRHzCAgT9eyt3OS34RHoyD7dPDYhRcuKn+/6Kv2knDF8Im43J6vlSW6Hm1w63fNq3ZIT1cFk7RuVO3Psag==",
|
||||
"deprecated": "We have renamed the package to @formatjs/intl-numberformat",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-utils": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-utils": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz",
|
||||
"integrity": "sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==",
|
||||
"deprecated": "the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@gatsbyjs/reach-router": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@gatsbyjs/reach-router/-/reach-router-2.0.1.tgz",
|
||||
@ -5567,6 +5557,7 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
"node_modules/@seafile/sf-metadata-ui-component": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-1.0.2.tgz",
|
||||
@ -5633,6 +5624,8 @@
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
=======
|
||||
>>>>>>> 1997d1d4b (refactor(metadata): remove ui-component)
|
||||
"node_modules/@seafile/slate": {
|
||||
"version": "0.91.8",
|
||||
"resolved": "https://registry.npmmirror.com/@seafile/slate/-/slate-0.91.8.tgz",
|
||||
@ -8849,12 +8842,6 @@
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/bowser": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmmirror.com/bowser/-/bowser-1.9.4.tgz",
|
||||
"integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -10235,16 +10222,6 @@
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-in-js-utils": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
|
||||
"integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hyphenate-style-name": "^1.0.2",
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/css-loader": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz",
|
||||
@ -11729,6 +11706,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
@ -13583,19 +13561,6 @@
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/glamor": {
|
||||
"version": "2.20.40",
|
||||
"resolved": "https://registry.npmmirror.com/glamor/-/glamor-2.20.40.tgz",
|
||||
"integrity": "sha512-DNXCd+c14N9QF8aAKrfl4xakPk5FdcFwmH7sD0qnC0Pr7xoZ5W9yovhUrY/dJc3psfGGXC58vqQyRtuskyUJxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.12",
|
||||
"inline-style-prefixer": "^3.0.6",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"through": "^2.3.8"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
|
||||
@ -15161,16 +15126,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/inline-style-prefixer": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz",
|
||||
"integrity": "sha512-ne8XIyyqkRaNJ1JfL1NYzNdCNxq+MCBQhC8NgOQlzNm2vv3XxlP0VSLQUbSRCF6KPEoveCVEpayHoHzcMyZsMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bowser": "^1.7.3",
|
||||
"css-in-js-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz",
|
||||
@ -15185,32 +15140,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-format-cache": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/intl-format-cache/-/intl-format-cache-4.3.1.tgz",
|
||||
"integrity": "sha512-OEUYNA7D06agqPOYhbTkl0T8HA3QKSuwWh1HiClEnpd9vw7N+3XsQt5iZ0GUEchp5CW1fQk/tary+NsbF3yQ1Q==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "7.8.4",
|
||||
"resolved": "https://registry.npmmirror.com/intl-messageformat/-/intl-messageformat-7.8.4.tgz",
|
||||
"integrity": "sha512-yS0cLESCKCYjseCOGXuV4pxJm/buTfyCJ1nzQjryHmSehlptbZbn9fnlk1I9peLopZGGbjj46yHHiTAEZ1qOTA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"intl-format-cache": "^4.2.21",
|
||||
"intl-messageformat-parser": "^3.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat-parser": {
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/intl-messageformat-parser/-/intl-messageformat-parser-3.6.4.tgz",
|
||||
"integrity": "sha512-RgPGwue0mJtoX2Ax8EmMzJzttxjnva7gx0Q7mKJ4oALrTZvtmCeAw5Msz2PcjW4dtCh/h7vN/8GJCxZO1uv+OA==",
|
||||
"deprecated": "We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-unified-numberformat": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz",
|
||||
@ -27547,12 +27476,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/thunky": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz",
|
||||
|
@ -14,7 +14,6 @@
|
||||
"@seafile/sdoc-editor": "2.0.11",
|
||||
"@seafile/seafile-calendar": "0.0.28",
|
||||
"@seafile/seafile-editor": "2.0.0",
|
||||
"@seafile/sf-metadata-ui-component": "^1.0.2",
|
||||
"@seafile/stldraw-editor": "1.0.0",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
"@uiw/codemirror-themes": "^4.23.5",
|
||||
|
7
frontend/src/components/centered-loading/index.css
Normal file
7
frontend/src/components/centered-loading/index.css
Normal file
@ -0,0 +1,7 @@
|
||||
.sf-centered-loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
20
frontend/src/components/centered-loading/index.js
Normal file
20
frontend/src/components/centered-loading/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Loading from '../loading';
|
||||
|
||||
import './index.css';
|
||||
|
||||
function CenteredLoading(props) {
|
||||
return (
|
||||
<div className={classnames('sf-centered-loading', props.className)}>
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CenteredLoading.propTypes = {
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default CenteredLoading;
|
@ -1,26 +1,30 @@
|
||||
.add-item-btn {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-top: 1px solid #dedede;
|
||||
background: #fff;
|
||||
padding: 0 1rem;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
position: relative;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.add-item-btn:hover {
|
||||
background-color: #f5f5f5;
|
||||
cursor: pointer;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.add-item-btn .add-new-option {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.add-item-btn .seafile-multicolor-icon-add-table {
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transform: none;
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.add-item-btn .description {
|
||||
flex: 1;
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import '../../css/common-add-tool.css';
|
||||
import Icon from '../icon';
|
||||
|
||||
function CommonAddTool(props) {
|
||||
const { callBack, footerName, className, addIconClassName } = props;
|
||||
import './index.css';
|
||||
|
||||
function CommonAddTool({ callBack, footerName, className, addIconClassName, hideIcon, style }) {
|
||||
return (
|
||||
<div className={`add-item-btn ${className ? className : ''}`} onClick={(e) => {callBack(e);}}>
|
||||
<span className={`sf3-font sf3-font-enlarge mr-2 ${addIconClassName || ''}`}></span>
|
||||
<span className='add-new-option' title={footerName}>{footerName}</span>
|
||||
<div className={`add-item-btn ${className ? className : ''}`} style={style} onClick={(e) => {callBack(e);}}>
|
||||
{!hideIcon && <Icon symbol="add-table" className={addIconClassName} />}
|
||||
<span className="description text-truncate">{footerName}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import SearchInput from '../search-input';
|
||||
import SearchInput from '../../search-input';
|
||||
import ClickOutside from '../../click-outside';
|
||||
import Option from './option';
|
||||
import KeyCodes from '../../../constants/keyCodes';
|
||||
import ClickOutside from './click-outside';
|
||||
|
||||
import './select-option-group.css';
|
||||
|
||||
|
@ -6,9 +6,8 @@ import DirOperationToolbar from '../../components/toolbar/dir-operation-toolbar'
|
||||
import MetadataViewName from '../../metadata/components/metadata-view-name';
|
||||
import TagViewName from '../../tag/components/tag-view-name';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { debounce, Utils } from '../../utils/utils';
|
||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||
import { debounce } from '../../metadata/utils/common';
|
||||
import { EVENT_BUS_TYPE } from '../../metadata/constants';
|
||||
import { ALL_TAGS_ID } from '../../tag/constants';
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap
|
||||
import { gettext, enableFileTags } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import TextTranslation from '../../utils/text-translation';
|
||||
import SeahubPopover from '../common/seahub-popover';
|
||||
import CustomizePopover from '../customize-popover';
|
||||
import ListTagPopover from '../popover/list-tag-popover';
|
||||
import ViewModes from '../../components/view-modes';
|
||||
import SortMenu from '../../components/sort-menu';
|
||||
@ -164,12 +164,11 @@ class DirTool extends React.Component {
|
||||
}
|
||||
</div>
|
||||
{this.state.isRepoTagDialogOpen &&
|
||||
<SeahubPopover
|
||||
<CustomizePopover
|
||||
popoverClassName="list-tag-popover"
|
||||
target="cur-folder-more-op-toggle"
|
||||
hideSeahubPopover={this.hidePopover}
|
||||
hideSeahubPopoverWithEsc={this.hidePopover}
|
||||
canHideSeahubPopover={true}
|
||||
hidePopover={this.hidePopover}
|
||||
hidePopoverWithEsc={this.hidePopover}
|
||||
boundariesElement={document.body}
|
||||
placement={'bottom-end'}
|
||||
>
|
||||
@ -177,7 +176,7 @@ class DirTool extends React.Component {
|
||||
repoID={repoID}
|
||||
onListTagCancel={this.toggleCancel}
|
||||
/>
|
||||
</SeahubPopover>
|
||||
</CustomizePopover>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Popover } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { KeyCodes } from '../../constants';
|
||||
import { KeyCodes } from '../constants';
|
||||
import { getEventClassName } from '../utils/dom';
|
||||
|
||||
const propTypes = {
|
||||
target: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||
@ -9,17 +10,17 @@ const propTypes = {
|
||||
innerClassName: PropTypes.string,
|
||||
popoverClassName: PropTypes.string,
|
||||
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
hideSeahubPopover: PropTypes.func.isRequired,
|
||||
hideSeahubPopoverWithEsc: PropTypes.func,
|
||||
hidePopover: PropTypes.func.isRequired,
|
||||
hidePopoverWithEsc: PropTypes.func,
|
||||
hideArrow: PropTypes.bool,
|
||||
canHideSeahubPopover: PropTypes.bool,
|
||||
canHidePopover: PropTypes.bool,
|
||||
placement: PropTypes.string,
|
||||
modifiers: PropTypes.object
|
||||
};
|
||||
|
||||
class SeahubPopover extends React.Component {
|
||||
class CustomizePopover extends React.Component {
|
||||
|
||||
SeahubPopoverRef = null;
|
||||
popoverRef = null;
|
||||
isSelectOpen = false;
|
||||
|
||||
componentDidMount() {
|
||||
@ -32,28 +33,22 @@ class SeahubPopover extends React.Component {
|
||||
document.removeEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
getEventClassName = (e) => {
|
||||
// svg mouseEvent event.target.className is an object
|
||||
if (!e || !e.target) return '';
|
||||
return e.target.getAttribute('class') || '';
|
||||
};
|
||||
|
||||
onKeyDown = (e) => {
|
||||
const { canHideSeahubPopover, hideSeahubPopoverWithEsc } = this.props;
|
||||
if (e.keyCode === KeyCodes.Escape && typeof hideSeahubPopoverWithEsc === 'function' && !this.isSelectOpen) {
|
||||
const { canHidePopover, hidePopoverWithEsc } = this.props;
|
||||
if (e.keyCode === KeyCodes.Escape && typeof hidePopoverWithEsc === 'function' && !this.isSelectOpen) {
|
||||
e.preventDefault();
|
||||
hideSeahubPopoverWithEsc();
|
||||
hidePopoverWithEsc();
|
||||
} else if (e.keyCode === KeyCodes.Enter) {
|
||||
// Resolve the default behavior of the enter key when entering formulas is blocked
|
||||
if (canHideSeahubPopover) return;
|
||||
if (canHidePopover) return;
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
};
|
||||
|
||||
onMouseDown = (e) => {
|
||||
if (!this.props.canHideSeahubPopover) return;
|
||||
if (this.SeahubPopoverRef && e && this.getEventClassName(e).indexOf('popover') === -1 && !this.SeahubPopoverRef.contains(e.target)) {
|
||||
this.props.hideSeahubPopover(e);
|
||||
if (!this.props.canHidePopover) return;
|
||||
if (this.popoverRef && e && getEventClassName(e).indexOf('popover') === -1 && !this.popoverRef.contains(e.target)) {
|
||||
this.props.hidePopover(e);
|
||||
}
|
||||
};
|
||||
|
||||
@ -82,7 +77,7 @@ class SeahubPopover extends React.Component {
|
||||
modifiers={modifiers}
|
||||
{...additionalProps}
|
||||
>
|
||||
<div ref={ref => this.SeahubPopoverRef = ref} onClick={this.onPopoverInsideClick}>
|
||||
<div ref={ref => this.popoverRef = ref} onClick={this.onPopoverInsideClick}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</Popover>
|
||||
@ -90,12 +85,12 @@ class SeahubPopover extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
SeahubPopover.defaultProps = {
|
||||
CustomizePopover.defaultProps = {
|
||||
placement: 'bottom-start',
|
||||
hideArrow: true,
|
||||
canHideSeahubPopover: true
|
||||
canHidePopover: true
|
||||
};
|
||||
|
||||
SeahubPopover.propTypes = propTypes;
|
||||
CustomizePopover.propTypes = propTypes;
|
||||
|
||||
export default SeahubPopover;
|
||||
export default CustomizePopover;
|
100
frontend/src/components/customize-select/index.css
Normal file
100
frontend/src/components/customize-select/index.css
Normal file
@ -0,0 +1,100 @@
|
||||
.seafile-customize-select {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 0 10px;
|
||||
border-radius: 3px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 900px;
|
||||
user-select: none;
|
||||
text-align: left;
|
||||
line-height: 1.5;
|
||||
background-image: none;
|
||||
font-size: 14px;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.seafile-customize-select:focus,
|
||||
.seafile-customize-select.focus {
|
||||
border-color: #1991eb !important;
|
||||
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25);
|
||||
}
|
||||
|
||||
.seafile-customize-select.disabled:focus,
|
||||
.seafile-customize-select.focus.disabled,
|
||||
.seafile-customize-select.disabled:hover {
|
||||
border-color: rgba(0, 40, 100, 0.12) !important;
|
||||
box-shadow: unset;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.seafile-customize-select:hover {
|
||||
cursor: pointer;
|
||||
border-color: rgb(179, 179, 179);
|
||||
}
|
||||
|
||||
.seafile-customize-select .sf3-font-down {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.seafile-customize-select .selected-option {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.seafile-customize-select .selected-option .custom-select-dropdown-icon {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.seafile-customize-select.selector-collaborator .seafile-option-group .seafile-option-group-content,
|
||||
.seafile-customize-select.selector-group .seafile-option-group .seafile-option-group-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.seafile-customize-select.selector-collaborator .seafile-option-group .seafile-option-group-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.seafile-customize-select.selector-collaborator .option {
|
||||
padding: 5px 0 5px 10px !important;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.seafile-customize-select.selector-group .option {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.seafile-customize-select.selector-group .select-group-option {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.seafile-customize-select.selector-group .selected-option .selected-group {
|
||||
padding: 0 2px;
|
||||
background: #eceff4;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.seafile-customize-select .selected-option-show {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.seafile-customize-select .select-placeholder {
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
173
frontend/src/components/customize-select/index.js
Normal file
173
frontend/src/components/customize-select/index.js
Normal file
@ -0,0 +1,173 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import ModalPortal from '../modal-portal';
|
||||
import SelectOptionGroup from './select-option-group';
|
||||
import { getEventClassName } from '../../utils/dom';
|
||||
|
||||
import './index.css';
|
||||
|
||||
class CustomizeSelect extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isShowSelectOptions: false
|
||||
};
|
||||
}
|
||||
|
||||
onSelectToggle = (event) => {
|
||||
event.preventDefault();
|
||||
/*
|
||||
if select is showing, click events do not need to be monitored by other click events,
|
||||
so it can be closed when other select is clicked.
|
||||
*/
|
||||
if (this.state.isShowSelectOptions) event.stopPropagation();
|
||||
const eventClassName = getEventClassName(event);
|
||||
if (this.props.readOnly || eventClassName.indexOf('option-search-control') > -1 || eventClassName === 'seafile-option-group-search') return;
|
||||
// Prevent closing by pressing the space bar in the search input
|
||||
if (event.target.value === '') return;
|
||||
this.setState({
|
||||
isShowSelectOptions: !this.state.isShowSelectOptions
|
||||
});
|
||||
};
|
||||
|
||||
onClick = (event) => {
|
||||
if (this.props.isShowSelected && event.target.className.includes('icon-fork-number')) {
|
||||
return;
|
||||
}
|
||||
if (!this.selector.contains(event.target)) {
|
||||
this.closeSelect();
|
||||
}
|
||||
};
|
||||
|
||||
closeSelect = () => {
|
||||
this.setState({ isShowSelectOptions: false });
|
||||
};
|
||||
|
||||
getSelectedOptionTop = () => {
|
||||
if (!this.selector) return 38;
|
||||
const { height } = this.selector.getBoundingClientRect();
|
||||
return height;
|
||||
};
|
||||
|
||||
getFilterOptions = (searchValue) => {
|
||||
const { options, searchable } = this.props;
|
||||
if (!searchable) return options || [];
|
||||
const validSearchVal = searchValue.trim().toLowerCase();
|
||||
if (!validSearchVal) return options || [];
|
||||
return options.filter(option => {
|
||||
const { value, name } = option;
|
||||
if (typeof name === 'string') {
|
||||
return name.toLowerCase().indexOf(validSearchVal) > -1;
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
if (value.column) {
|
||||
return value.column.name.toLowerCase().indexOf(validSearchVal) > -1;
|
||||
}
|
||||
if (value.name) {
|
||||
return value.name.toLowerCase().indexOf(validSearchVal) > -1;
|
||||
}
|
||||
return value.columnOption && value.columnOption.name.toLowerCase().indexOf(validSearchVal) > -1;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
renderDropDownIcon = () => {
|
||||
const { readOnly, component } = this.props;
|
||||
if (readOnly) return;
|
||||
const { DropDownIcon } = component || {};
|
||||
if (DropDownIcon) {
|
||||
return (
|
||||
<div className="custom-select-dropdown-icon">{DropDownIcon}</div>
|
||||
);
|
||||
}
|
||||
return (<i className="sf3-font sf3-font-down" aria-hidden="true"></i>);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, value, options, placeholder, searchable, searchPlaceholder, noOptionsPlaceholder,
|
||||
readOnly, isInModal, addOptionAble, component } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(node) => this.selector = node}
|
||||
className={classnames('seafile-customize-select custom-select',
|
||||
{ 'focus': this.state.isShowSelectOptions },
|
||||
{ 'disabled': readOnly },
|
||||
className
|
||||
)}
|
||||
onClick={this.onSelectToggle}>
|
||||
<div className="selected-option">
|
||||
{value && value.label ?
|
||||
<span className="selected-option-show">{value.label}</span>
|
||||
:
|
||||
<span className="select-placeholder">{placeholder}</span>
|
||||
}
|
||||
{this.renderDropDownIcon()}
|
||||
</div>
|
||||
{this.state.isShowSelectOptions && !isInModal && (
|
||||
<SelectOptionGroup
|
||||
value={value}
|
||||
addOptionAble={addOptionAble}
|
||||
component={component}
|
||||
isShowSelected={this.props.isShowSelected}
|
||||
top={this.getSelectedOptionTop()}
|
||||
options={options}
|
||||
onSelectOption={this.props.onSelectOption}
|
||||
searchable={searchable}
|
||||
searchPlaceholder={searchPlaceholder}
|
||||
noOptionsPlaceholder={noOptionsPlaceholder}
|
||||
onClickOutside={this.onClick}
|
||||
closeSelect={this.closeSelect}
|
||||
getFilterOptions={this.getFilterOptions}
|
||||
supportMultipleSelect={this.props.supportMultipleSelect}
|
||||
/>
|
||||
)}
|
||||
{this.state.isShowSelectOptions && isInModal && (
|
||||
<ModalPortal>
|
||||
<SelectOptionGroup
|
||||
className={className}
|
||||
value={value}
|
||||
addOptionAble={addOptionAble}
|
||||
component={component}
|
||||
isShowSelected={this.props.isShowSelected}
|
||||
position={this.selector.getBoundingClientRect()}
|
||||
isInModal={isInModal}
|
||||
top={this.getSelectedOptionTop()}
|
||||
options={options}
|
||||
onSelectOption={this.props.onSelectOption}
|
||||
searchable={searchable}
|
||||
searchPlaceholder={searchPlaceholder}
|
||||
noOptionsPlaceholder={noOptionsPlaceholder}
|
||||
onClickOutside={this.onClick}
|
||||
closeSelect={this.closeSelect}
|
||||
getFilterOptions={this.getFilterOptions}
|
||||
supportMultipleSelect={this.props.supportMultipleSelect}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CustomizeSelect.propTypes = {
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.object,
|
||||
options: PropTypes.array,
|
||||
placeholder: PropTypes.string,
|
||||
onSelectOption: PropTypes.func,
|
||||
readOnly: PropTypes.bool,
|
||||
searchable: PropTypes.bool,
|
||||
addOptionAble: PropTypes.bool,
|
||||
searchPlaceholder: PropTypes.string,
|
||||
noOptionsPlaceholder: PropTypes.string,
|
||||
component: PropTypes.object,
|
||||
supportMultipleSelect: PropTypes.bool,
|
||||
isShowSelected: PropTypes.bool,
|
||||
isInModal: PropTypes.bool, // if select component in a modal (option group need ModalPortal to show)
|
||||
};
|
||||
|
||||
export default CustomizeSelect;
|
@ -0,0 +1,103 @@
|
||||
.seafile-option-group {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
min-height: 60px;
|
||||
max-height: 300px;
|
||||
min-width: 100%;
|
||||
max-width: 15rem;
|
||||
padding: 0.5rem 0;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||
border-radius: 3px;
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.seafile-option-group .seafile-option-group-search {
|
||||
width: 100%;
|
||||
padding: 0 10px 6px 10px;
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.seafile-option-group-search .form-control {
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
.seafile-option-group .none-search-result {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.seafile-option-group .seafile-option-group-content {
|
||||
max-height: 252px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.seafile-select-option {
|
||||
display: block;
|
||||
width: 100%;
|
||||
line-height: 24px;
|
||||
padding: 0.25rem 10px;
|
||||
clear: both;
|
||||
font-weight: 400;
|
||||
text-align: inherit;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.seafile-select-option.seafile-select-option-active {
|
||||
background-color: #20a0ff;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.seafile-select-option.seafile-select-option-active .select-option-name {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.seafile-select-option:hover .header-icon .seafile-multicolor-icon,
|
||||
.seafile-select-option.seafile-select-option-active .header-icon .seafile-multicolor-icon {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.seafile-select-option:not(.seafile-select-option-active):hover .header-icon .seafile-multicolor-icon {
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.seafile-select-option .select-option-name .single-select-option {
|
||||
margin: 0 0 0 12px;
|
||||
}
|
||||
|
||||
.seafile-select-option .select-option-name .multiple-select-option {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-single-select .select-option-name,
|
||||
.seafile-option-group-selector-multiple-select .multiple-option-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-multiple-select .multiple-check-icon {
|
||||
display: inline-flex;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-multiple-select .multiple-check-icon .seafile-multicolor-icon-check-mark {
|
||||
font-size: 12px;
|
||||
color: #798d99;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-single-select .seafile-select-option:hover,
|
||||
.seafile-option-group-selector-single-select .seafile-select-option.seafile-select-option-active,
|
||||
.seafile-option-group-selector-multiple-select .seafile-select-option:hover,
|
||||
.seafile-option-group-selector-multiple-select .seafile-select-option.seafile-select-option-active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import ClickOutside from '../../click-outside';
|
||||
import SearchInput from '../../search-input';
|
||||
import Option from './option';
|
||||
import { KeyCodes } from '../../../constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const OPTION_HEIGHT = 32;
|
||||
|
||||
class SelectOptionGroup extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
searchVal: '',
|
||||
activeIndex: -1,
|
||||
disableHover: false,
|
||||
};
|
||||
this.filterOptions = null;
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('keydown', this.onHotKey);
|
||||
setTimeout(() => {
|
||||
this.resetMenuStyle();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.filterOptions = null;
|
||||
this.timer && clearTimeout(this.timer);
|
||||
window.removeEventListener('keydown', this.onHotKey);
|
||||
}
|
||||
|
||||
resetMenuStyle = () => {
|
||||
const { isInModal, position } = this.props;
|
||||
const { top, height } = this.optionGroupRef.getBoundingClientRect();
|
||||
if (isInModal) {
|
||||
if (position.y + position.height + height > window.innerHeight) {
|
||||
this.optionGroupRef.style.top = (position.y - height) + 'px';
|
||||
}
|
||||
this.optionGroupRef.style.opacity = 1;
|
||||
}
|
||||
else {
|
||||
if (height + top > window.innerHeight) {
|
||||
const borderWidth = 2;
|
||||
this.optionGroupRef.style.top = -1 * (height + borderWidth) + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onHotKey = (event) => {
|
||||
const keyCode = event.keyCode;
|
||||
if (keyCode === KeyCodes.UpArrow) {
|
||||
this.onPressUp();
|
||||
} else if (keyCode === KeyCodes.DownArrow) {
|
||||
this.onPressDown();
|
||||
} else if (keyCode === KeyCodes.Enter) {
|
||||
let option = this.filterOptions && this.filterOptions[this.state.activeIndex];
|
||||
if (option) {
|
||||
this.props.onSelectOption(option.value);
|
||||
if (!this.props.supportMultipleSelect) {
|
||||
this.props.closeSelect();
|
||||
}
|
||||
}
|
||||
} else if (keyCode === KeyCodes.Tab || keyCode === KeyCodes.Escape) {
|
||||
this.props.closeSelect();
|
||||
}
|
||||
};
|
||||
|
||||
onPressUp = () => {
|
||||
if (this.state.activeIndex > 0) {
|
||||
this.setState({ activeIndex: this.state.activeIndex - 1 }, () => {
|
||||
this.scrollContent();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onPressDown = () => {
|
||||
if (this.filterOptions && this.state.activeIndex < this.filterOptions.length - 1) {
|
||||
this.setState({ activeIndex: this.state.activeIndex + 1 }, () => {
|
||||
this.scrollContent();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMouseDown = (e) => {
|
||||
const { isInModal } = this.props;
|
||||
// prevent event propagation when click option or search input
|
||||
if (isInModal) {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
}
|
||||
};
|
||||
|
||||
scrollContent = () => {
|
||||
const { offsetHeight, scrollTop } = this.optionGroupContentRef;
|
||||
this.setState({ disableHover: true });
|
||||
this.timer = setTimeout(() => {
|
||||
this.setState({ disableHover: false });
|
||||
}, 500);
|
||||
if (this.state.activeIndex * OPTION_HEIGHT === 0) {
|
||||
this.optionGroupContentRef.scrollTop = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.activeIndex * OPTION_HEIGHT < scrollTop) {
|
||||
this.optionGroupContentRef.scrollTop = scrollTop - OPTION_HEIGHT;
|
||||
}
|
||||
else if (this.state.activeIndex * OPTION_HEIGHT > offsetHeight + scrollTop) {
|
||||
this.optionGroupContentRef.scrollTop = scrollTop + OPTION_HEIGHT;
|
||||
}
|
||||
};
|
||||
|
||||
changeIndex = (index) => {
|
||||
this.setState({ activeIndex: index });
|
||||
};
|
||||
|
||||
onChangeSearch = (searchVal) => {
|
||||
let value = searchVal || '';
|
||||
if (value !== this.state.searchVal) {
|
||||
this.setState({ searchVal: value, activeIndex: -1, });
|
||||
}
|
||||
};
|
||||
|
||||
renderOptGroup = (searchVal) => {
|
||||
let { noOptionsPlaceholder, onSelectOption } = this.props;
|
||||
this.filterOptions = this.props.getFilterOptions(searchVal);
|
||||
if (this.filterOptions.length === 0) {
|
||||
return (
|
||||
<div className="none-search-result">{noOptionsPlaceholder}</div>
|
||||
);
|
||||
}
|
||||
return this.filterOptions.map((opt, i) => {
|
||||
let key = opt.value.column ? opt.value.column.key : i;
|
||||
let isActive = this.state.activeIndex === i;
|
||||
return (
|
||||
<Option
|
||||
key={key}
|
||||
index={i}
|
||||
isActive={isActive}
|
||||
value={opt.value}
|
||||
onSelectOption={onSelectOption}
|
||||
changeIndex={this.changeIndex}
|
||||
supportMultipleSelect={this.props.supportMultipleSelect}
|
||||
disableHover={this.state.disableHover}
|
||||
>
|
||||
{opt.label}
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { searchable, searchPlaceholder, top, left, minWidth, value, isShowSelected, isInModal, position,
|
||||
className, addOptionAble, component } = this.props;
|
||||
const { AddOption } = component || {};
|
||||
let { searchVal } = this.state;
|
||||
let style = { top: top || 0, left: left || 0 };
|
||||
if (minWidth) {
|
||||
style = { top: top || 0, left: left || 0, minWidth };
|
||||
}
|
||||
if (isInModal) {
|
||||
style = {
|
||||
position: 'fixed',
|
||||
left: position.x,
|
||||
top: position.y + position.height,
|
||||
minWidth: position.width,
|
||||
opacity: 0,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.props.onClickOutside}>
|
||||
<div
|
||||
className={classnames('seafile-option-group', className ? 'seafile-option-group-' + className : '', {
|
||||
'pt-0': isShowSelected,
|
||||
'create-new-seafile-option-group': addOptionAble,
|
||||
})}
|
||||
ref={(ref) => this.optionGroupRef = ref}
|
||||
style={style}
|
||||
onMouseDown={this.onMouseDown}
|
||||
>
|
||||
{isShowSelected &&
|
||||
<div className="editor-list-delete mb-2" onClick={(e) => e.stopPropagation()}>{value.label || ''}</div>
|
||||
}
|
||||
{searchable && (
|
||||
<div className="seafile-option-group-search">
|
||||
<SearchInput
|
||||
className="option-search-control"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={this.onChangeSearch}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="seafile-option-group-content" ref={(ref) => this.optionGroupContentRef = ref}>
|
||||
{this.renderOptGroup(searchVal)}
|
||||
</div>
|
||||
{addOptionAble && AddOption}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectOptionGroup.propTypes = {
|
||||
top: PropTypes.number,
|
||||
left: PropTypes.number,
|
||||
minWidth: PropTypes.number,
|
||||
options: PropTypes.array,
|
||||
onSelectOption: PropTypes.func,
|
||||
searchable: PropTypes.bool,
|
||||
addOptionAble: PropTypes.bool,
|
||||
component: PropTypes.object,
|
||||
searchPlaceholder: PropTypes.string,
|
||||
noOptionsPlaceholder: PropTypes.string,
|
||||
onClickOutside: PropTypes.func.isRequired,
|
||||
closeSelect: PropTypes.func.isRequired,
|
||||
getFilterOptions: PropTypes.func.isRequired,
|
||||
supportMultipleSelect: PropTypes.bool,
|
||||
value: PropTypes.object,
|
||||
isShowSelected: PropTypes.bool,
|
||||
stopClickEvent: PropTypes.bool,
|
||||
isInModal: PropTypes.bool,
|
||||
position: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SelectOptionGroup;
|
@ -0,0 +1,50 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
class Option extends Component {
|
||||
|
||||
onSelectOption = (value, event) => {
|
||||
if (this.props.supportMultipleSelect) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.props.onSelectOption(value, event);
|
||||
};
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (!this.props.disableHover) {
|
||||
this.props.changeIndex(this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (!this.props.disableHover) {
|
||||
this.props.changeIndex(-1);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={classnames('seafile-select-option', { 'seafile-select-option-active': this.props.isActive })}
|
||||
onClick={this.onSelectOption.bind(this, this.props.value)}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Option.propTypes = {
|
||||
index: PropTypes.number,
|
||||
isActive: PropTypes.bool,
|
||||
changeIndex: PropTypes.func,
|
||||
value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||
onSelectOption: PropTypes.func,
|
||||
supportMultipleSelect: PropTypes.bool,
|
||||
disableHover: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Option;
|
@ -1,12 +1,12 @@
|
||||
import React, { Fragment, } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, isOrgContext, username } from '../../utils/constants';
|
||||
import { Modal, ModalBody } from 'reactstrap';
|
||||
import { gettext, isOrgContext, username } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
import Loading from '../../components/loading';
|
||||
import toaster from '../toast';
|
||||
import EmptyTip from '../empty-tip';
|
||||
import Loading from '../loading';
|
||||
import Department from '../../models/department';
|
||||
import SeahubModalHeader from '../common/seahub-modal-header';
|
||||
import DepartmentGroup from './department-detail-widget/department-group';
|
||||
|
@ -2,8 +2,8 @@ import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Tooltip } from 'reactstrap';
|
||||
import { gettext, mediaUrl } from '../../../utils/constants';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
import EmptyTip from '../../empty-tip';
|
||||
import Loading from '../../loading';
|
||||
|
||||
const ItemPropTypes = {
|
||||
member: PropTypes.object,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Loading from '../../../components/loading';
|
||||
import Loading from '../../loading';
|
||||
import { isOrgContext } from '../../../utils/constants';
|
||||
|
||||
const ItemPropTypes = {
|
||||
|
@ -5,7 +5,7 @@ import { Modal, ModalBody } from 'reactstrap';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext, siteRoot, enableRepoSnapshotLabel as showLabel } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import Loading from '../../components/loading';
|
||||
import Loading from '../loading';
|
||||
import Paginator from '../../components/paginator';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
import CommitDetails from '../../components/dialog/commit-details';
|
||||
|
@ -4,7 +4,7 @@ import { Link } from '@gatsbyjs/reach-router';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext, siteRoot, isPro, username } from '../../../utils/constants';
|
||||
import Loading from '../../../components/loading';
|
||||
import Loading from '../../loading';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import SharePermissionEditor from '../../../components/select-editor/share-permission-editor';
|
||||
|
@ -7,7 +7,7 @@ import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { repoShareAdminAPI } from '../../../utils/repo-share-admin-api';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import Loading from '../../../components/loading';
|
||||
import Loading from '../../loading';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
|
@ -7,7 +7,7 @@ import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { repoShareAdminAPI } from '../../../utils/repo-share-admin-api';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import Loading from '../../../components/loading';
|
||||
import Loading from '../../loading';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
|
@ -4,7 +4,7 @@ import { Link } from '@gatsbyjs/reach-router';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext, siteRoot, isPro, username } from '../../../utils/constants';
|
||||
import Loading from '../../../components/loading';
|
||||
import Loading from '../../loading';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import SharePermissionEditor from '../../../components/select-editor/share-permission-editor';
|
||||
|
@ -6,7 +6,6 @@ import { MODE_TYPE_MAP } from '../../constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { RepoInfo } from '../../models';
|
||||
import { ModalPortal } from '@seafile/sf-metadata-ui-component';
|
||||
import CreateFolder from '../dialog/create-folder-dialog';
|
||||
|
||||
const LibraryOption = ({ mode, label, currentMode, onUpdateMode }) => {
|
||||
@ -193,14 +192,12 @@ class SelectDirentBody extends React.Component {
|
||||
</ModalFooter>
|
||||
</Col>
|
||||
{this.state.showCreateFolderDialog && (
|
||||
<ModalPortal>
|
||||
<CreateFolder
|
||||
parentPath={this.props.selectedPath}
|
||||
onAddFolder={this.createFolder}
|
||||
checkDuplicatedName={this.checkDuplicatedName}
|
||||
addFolderCancel={this.onToggleCreateFolder}
|
||||
/>
|
||||
</ModalPortal>
|
||||
<CreateFolder
|
||||
parentPath={this.props.selectedPath}
|
||||
onAddFolder={this.createFolder}
|
||||
checkDuplicatedName={this.checkDuplicatedName}
|
||||
addFolderCancel={this.onToggleCreateFolder}
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import UserSelect from '../user-select';
|
||||
import { SeahubSelect } from '../common/select';
|
||||
import Switch from '../common/switch';
|
||||
import Switch from '../switch';
|
||||
import '../../css/transfer-dialog.css';
|
||||
|
||||
const propTypes = {
|
||||
|
@ -16,11 +16,7 @@ const ExtensionPrompts = ({ onExtendedProperties }) => {
|
||||
onClick={handlePromptsClick}
|
||||
>
|
||||
<div className='extension-prompts-icon-wrapper'>
|
||||
<Icon
|
||||
symbol={'bell'}
|
||||
className='extension-prompts-icon'
|
||||
aria-label={gettext('Bell Icon')}
|
||||
/>
|
||||
<Icon symbol="bell" className='extension-prompts-icon' aria-label={gettext('Bell Icon')} />
|
||||
</div>
|
||||
<div className='extension-prompts-content'>
|
||||
<p>
|
||||
|
@ -20,14 +20,14 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name .sf-metadata-icon {
|
||||
.dirent-detail-item .dirent-detail-item-name .seafile-multicolor-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 14px;
|
||||
fill: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name .sf-metadata-icon-tag {
|
||||
.dirent-detail-item .dirent-detail-item-name .seafile-multicolor-icon-tag {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../icon';
|
||||
import { CellType, COLUMNS_ICON_CONFIG } from '../../../metadata/constants';
|
||||
|
||||
import './index.css';
|
||||
@ -15,7 +15,7 @@ const DetailItem = ({ readonly = true, field, className, children }) => {
|
||||
return (
|
||||
<div className={classnames('dirent-detail-item', className)}>
|
||||
<div className="dirent-detail-item-name d-flex">
|
||||
<div><Icon iconName={icon} /></div>
|
||||
<div><Icon className="sf-metadata-icon" symbol={icon} /></div>
|
||||
<span className="dirent-detail-item-name-value">{field.name}</span>
|
||||
</div>
|
||||
<div className={classnames('dirent-detail-item-value', { 'editable': !readonly })} >
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Formatter } from '@seafile/sf-metadata-ui-component';
|
||||
import DetailItem from '../detail-item';
|
||||
import Formatter from '../../../metadata/components/formatter';
|
||||
import { CellType } from '../../../metadata/constants';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { MetadataDetails } from '../../../metadata';
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Formatter } from '@seafile/sf-metadata-ui-component';
|
||||
import DetailItem from '../../detail-item';
|
||||
import Collapse from './collapse';
|
||||
import Formatter from '../../../../metadata/components/formatter';
|
||||
import { CellType, PRIVATE_COLUMN_KEY } from '../../../../metadata/constants';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { MetadataDetails, useMetadataDetails } from '../../../../metadata';
|
||||
import ObjectUtils from '../../../../metadata/utils/object-utils';
|
||||
import ObjectUtils from '../../../../utils/object';
|
||||
import { getCellValueByColumn, getDateDisplayString, decimalToExposureTime } from '../../../../metadata/utils/cell';
|
||||
import Collapse from './collapse';
|
||||
import { useMetadataStatus } from '../../../../hooks';
|
||||
import { CAPTURE_INFO_SHOW_KEY } from '../../../../constants';
|
||||
import People from '../../people';
|
||||
|
@ -8,7 +8,7 @@ import Dirent from '../../../models/dirent';
|
||||
import { Detail, Header, Body } from '../detail';
|
||||
import DirDetails from './dir-details';
|
||||
import FileDetails from './file-details';
|
||||
import ObjectUtils from '../../../metadata/utils/object-utils';
|
||||
import ObjectUtils from '../../../utils/object';
|
||||
import { MetadataDetailsProvider } from '../../../metadata/hooks';
|
||||
import { Settings, AI } from '../../../metadata/components/metadata-details';
|
||||
import { getDirentPath } from './utils';
|
||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import LibDetail from './lib-details';
|
||||
import DirentDetail from './dirent-details';
|
||||
import ViewDetails from '../../metadata/components/view-details';
|
||||
import ObjectUtils from '../../metadata/utils/object-utils';
|
||||
import ObjectUtils from '../../utils/object';
|
||||
import { MetadataContext } from '../../metadata';
|
||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||
import { METADATA_MODE, TAGS_MODE } from '../dir-view-mode/constants';
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Formatter } from '@seafile/sf-metadata-ui-component';
|
||||
import toaster from '../toast';
|
||||
import Loading from '../loading';
|
||||
import { Detail, Header, Body } from './detail';
|
||||
import DetailItem from './detail-item';
|
||||
import Formatter from '../../metadata/components/formatter';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import toaster from '../toast';
|
||||
import { Detail, Header, Body } from './detail';
|
||||
import Repo from '../../models/repo';
|
||||
import Loading from '../loading';
|
||||
import DetailItem from './detail-item';
|
||||
import { CellType } from '../../metadata/constants';
|
||||
|
||||
const LibDetail = React.memo(({ currentRepoInfo, onClose }) => {
|
||||
|
20
frontend/src/components/icon-btn/index.css
Normal file
20
frontend/src/components/icon-btn/index.css
Normal file
@ -0,0 +1,20 @@
|
||||
.seafile-multicolor-icon-btn {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.seafile-multicolor-icon-btn-20 {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.seafile-multicolor-icon-btn-24 {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.seafile-multicolor-icon-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
26
frontend/src/components/icon-btn/index.js
Normal file
26
frontend/src/components/icon-btn/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Icon from '../icon';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const IconBtn = ({ size = 20, className, iconClassName, CustomIcon, symbol, iconStyle, onClick, ...params }) => {
|
||||
return (
|
||||
<div className={classnames('seafile-multicolor-icon-btn', `seafile-multicolor-icon-btn-${size}`, className)} onClick={onClick ? onClick : () => {}} { ...params }>
|
||||
{CustomIcon ? CustomIcon : <Icon symbol={symbol} className={iconClassName} style={iconStyle} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
IconBtn.propTypes = {
|
||||
customIcon: PropTypes.string,
|
||||
symbol: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
iconClassName: PropTypes.string,
|
||||
iconStyle: PropTypes.object,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default IconBtn;
|
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import '../css/icon.css';
|
||||
|
||||
const importAll = (requireContext) => {
|
||||
@ -13,10 +15,10 @@ try {
|
||||
}
|
||||
|
||||
const Icon = (props) => {
|
||||
const { className, symbol } = props;
|
||||
const iconClass = `seafile-multicolor-icon seafile-multicolor-icon-${symbol} ${className || ''}`;
|
||||
const { className, symbol, style } = props;
|
||||
const iconClass = classnames('seafile-multicolor-icon', className, `seafile-multicolor-icon-${symbol}`);
|
||||
return (
|
||||
<svg className={iconClass}>
|
||||
<svg className={iconClass} style={style}>
|
||||
<use xlinkHref={`#${symbol}`} />
|
||||
</svg>
|
||||
);
|
||||
@ -25,6 +27,7 @@ const Icon = (props) => {
|
||||
Icon.propTypes = {
|
||||
symbol: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
|
@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
function Loading() {
|
||||
return (
|
||||
<span className="loading-icon loading-tip"></span>
|
||||
);
|
||||
}
|
||||
|
||||
export default Loading;
|
4
frontend/src/components/loading/index.css
Normal file
4
frontend/src/components/loading/index.css
Normal file
@ -0,0 +1,4 @@
|
||||
.loading-tip.center {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
17
frontend/src/components/loading/index.js
Normal file
17
frontend/src/components/loading/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import './index.css';
|
||||
|
||||
function Loading({ className }) {
|
||||
return (
|
||||
<span className={classnames('loading-icon loading-tip', className)}></span>
|
||||
);
|
||||
}
|
||||
|
||||
Loading.propTypes = {
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Loading;
|
@ -4,9 +4,9 @@ import { gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import CommonAddTool from '../common/common-add-tool';
|
||||
import SearchInput from '../common/search-input';
|
||||
import SeahubPopover from '../common/seahub-popover';
|
||||
import CommonAddTool from '../common-add-tool';
|
||||
import SearchInput from '../search-input';
|
||||
import CustomizePopover from '../customize-popover';
|
||||
import TagItem from './tag-item';
|
||||
import { KeyCodes, TAG_COLORS } from '../../constants';
|
||||
|
||||
@ -147,12 +147,11 @@ class EditFileTagPopover extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<SeahubPopover
|
||||
<CustomizePopover
|
||||
popoverClassName="edit-filetag-popover"
|
||||
target={this.props.target}
|
||||
hideSeahubPopover={this.props.toggleCancel}
|
||||
hideSeahubPopoverWithEsc={this.props.toggleCancel}
|
||||
canHideSeahubPopover={true}
|
||||
hidePopover={this.props.toggleCancel}
|
||||
hidePopoverWithEsc={this.props.toggleCancel}
|
||||
>
|
||||
<SearchInput
|
||||
className="edit-filetag-popover-input"
|
||||
@ -187,7 +186,7 @@ class EditFileTagPopover extends React.Component {
|
||||
footerName={`${gettext('Create a new tag')} '${searchText}'`}
|
||||
/>
|
||||
}
|
||||
</SeahubPopover>
|
||||
</CustomizePopover>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { Utils } from '../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
placeholder: PropTypes.string,
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { ClickOutside } from '@seafile/sf-metadata-ui-component';
|
||||
import ClickOutside from '../../../click-outside';
|
||||
import Editor from './editor';
|
||||
import { EDITOR_CONTAINER as Z_INDEX_EDITOR_CONTAINER } from '../../constants/z-index';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { getEventClassName } from '../../utils';
|
||||
import { getEventClassName } from '../../../../utils/dom';
|
||||
import { checkCellValueChanged } from '../../utils/cell-comparer';
|
||||
import { getCellValueByColumn } from '../../utils/cell';
|
||||
import { isCtrlKeyHeldDown, isKeyPrintable } from '../../../../utils/keyboard-utils';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { ClickOutside } from '@seafile/sf-metadata-ui-component';
|
||||
import ClickOutside from '../../../click-outside';
|
||||
import Editor from './editor';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { EDITOR_CONTAINER as Z_INDEX_EDITOR_CONTAINER } from '../../constants/z-index';
|
||||
|
@ -50,7 +50,7 @@
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-select-tags .sf-metadata-delete-select-remove .sf-metadata-icon-x-01 {
|
||||
.sf-metadata-delete-select-tags .sf-metadata-delete-select-remove .seafile-multicolor-icon-x-01 {
|
||||
fill: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from './index';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IconBtn } from '@seafile/sf-metadata-ui-component';
|
||||
import IconBtn from '../../../../icon-btn';
|
||||
import { getTagColor, getTagName } from '../../../../../tag/utils/cell';
|
||||
import { getRowById } from '../../../utils/table';
|
||||
|
||||
@ -18,7 +18,7 @@ const DeleteTag = ({ value, tagsTable, onDelete }) => {
|
||||
<div className="sf-metadata-delete-select-tag" key={tagId}>
|
||||
<div className="sf-metadata-delete-select-tag-color" style={{ backgroundColor: tagColor }}></div>
|
||||
<div className="sf-metadata-delete-select-tag-name">{tagName}</div>
|
||||
<IconBtn className="sf-metadata-delete-select-remove" onClick={(event) => onDelete(tagId, event)} iconName="x-01" />
|
||||
<IconBtn className="sf-metadata-delete-select-remove" onClick={(event) => onDelete(tagId, event)} symbol="x-01" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, { useMemo, useCallback, useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { SearchInput, CustomizeAddTool, Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import CommonAddTool from '../../../common-add-tool';
|
||||
import SearchInput from '../../../search-input';
|
||||
import Icon from '../../../icon';
|
||||
import DeleteTags from './delete-tags';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { KeyCodes } from '../../../../constants';
|
||||
@ -219,7 +221,7 @@ const TagsEditor = ({
|
||||
<div className="sf-metadata-tag-name">{tagName}</div>
|
||||
</div>
|
||||
<div className="sf-metadata-tags-editor-tag-check-icon">
|
||||
{isSelected && (<Icon iconName="check-mark" />)}
|
||||
{isSelected && (<Icon className="sf-metadata-icon" symbol="check-mark" />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -244,7 +246,7 @@ const TagsEditor = ({
|
||||
{renderOptions()}
|
||||
</div>
|
||||
{isShowCreateBtn && (
|
||||
<CustomizeAddTool
|
||||
<CommonAddTool
|
||||
callBack={createTag}
|
||||
footerName={`${gettext('Add tag')} ${searchValue}`}
|
||||
className="add-search-result"
|
||||
|
@ -271,31 +271,6 @@
|
||||
border-left-color: #303133;
|
||||
}
|
||||
|
||||
.add-item-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-top: 1px solid #dedede;
|
||||
background: #fff;
|
||||
padding: 0 1rem;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
.add-item-btn:hover {
|
||||
cursor: pointer;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.add-item-btn .sf-metadata-icon-add-table {
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.formula-formatter-content-item {
|
||||
margin-right: 10px;
|
||||
font-size: 13px;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { KeyCodes } from '../../../constants';
|
||||
import { isModG, isModShiftG } from '../../../metadata/utils/hotkey';
|
||||
import { isModG, isModShiftG } from '../../../utils/hotkey';
|
||||
import SFTableSearcherInput from './searcher-input';
|
||||
import { checkHasSearchResult } from '../utils/search';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Loading } from '@seafile/sf-metadata-ui-component';
|
||||
import Loading from '../../../loading';
|
||||
import toaster from '../../../toast';
|
||||
import LoadAllTip from '../load-all-tip';
|
||||
import { RecordMetrics } from '../../utils/record-metrics';
|
||||
@ -8,7 +8,7 @@ import { TreeMetrics } from '../../utils/tree-metrics';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { CANVAS_RIGHT_INTERVAL } from '../../constants/grid';
|
||||
import { GRID_FOOTER as Z_INDEX_GRID_FOOTER } from '../../constants/z-index';
|
||||
import { addClassName, removeClassName } from '../../utils';
|
||||
import { addClassName, removeClassName } from '../../../../utils/dom';
|
||||
import { getRecordsFromSelectedRange } from '../../utils/selected-cell-utils';
|
||||
|
||||
import './index.css';
|
||||
@ -151,7 +151,7 @@ class RecordsFooter extends React.Component {
|
||||
{isLoadingMoreRecords &&
|
||||
<span className="loading-message ml-4">
|
||||
<span className="mr-2">{gettext('Loading')}</span>
|
||||
<Loading />
|
||||
<Loading className="sf-metadata-loading-tip center" />
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.sf-table-header-cell .sf-metadata-icon-drop-down {
|
||||
.sf-table-header-cell .seafile-multicolor-icon-drop-down {
|
||||
fill: #aaa;
|
||||
font-size: 12px;
|
||||
transform: scale(.8);
|
||||
|
@ -2,7 +2,7 @@ import React, { useRef, useCallback, useMemo, isValidElement, useState } from 'r
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { UncontrolledTooltip } from 'reactstrap';
|
||||
import { Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../../../icon';
|
||||
import ResizeColumnHandle from '../resize-column-handle';
|
||||
import HeaderDropdownMenu from '../dropdown-menu';
|
||||
import EventBus from '../../../../common/event-bus';
|
||||
@ -157,7 +157,7 @@ const Cell = ({
|
||||
return (
|
||||
<>
|
||||
<span className="mr-2" id={`header-icon-${key}`}>
|
||||
{icon_name && <Icon iconName={icon_name} className="sf-table-column-icon" />}
|
||||
{icon_name && <Icon symbol={icon_name} className="sf-metadata-icon sf-table-column-icon" />}
|
||||
</span>
|
||||
{icon_tooltip &&
|
||||
<UncontrolledTooltip placement="bottom" target={`header-icon-${key}`} fade={false} trigger="hover" className="sf-table-tooltip">
|
||||
|
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { UncontrolledTooltip, DropdownItem } from 'reactstrap';
|
||||
import classnames from 'classnames';
|
||||
import { Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../../../icon';
|
||||
|
||||
const ColumnDropdownItem = ({
|
||||
disabled = false,
|
||||
@ -32,7 +32,7 @@ const ColumnDropdownItem = ({
|
||||
if (!disabled) {
|
||||
return (
|
||||
<DropdownItem id={target} onClick={onChange} onMouseEnter={onMouseEnter} className={className}>
|
||||
<Icon iconName={iconName} />
|
||||
<Icon symbol={iconName} />
|
||||
<span className="item-text">{title}</span>
|
||||
</DropdownItem>
|
||||
);
|
||||
@ -47,7 +47,7 @@ const ColumnDropdownItem = ({
|
||||
onMouseEnter={onMouseEnter}
|
||||
id={target}
|
||||
>
|
||||
<Icon iconName={iconName} />
|
||||
<Icon symbol={iconName} />
|
||||
<span className="item-text">{title}</span>
|
||||
{isShowToolTip && (
|
||||
<UncontrolledTooltip placement="right" target={target} fade={false} delay={{ show: 0, hide: 0 }} className="sf-table-tooltip">
|
||||
|
@ -1,10 +1,10 @@
|
||||
.sf-table-dropdown-menu .dropdown-item .sf-metadata-icon {
|
||||
.sf-table-dropdown-menu .dropdown-item .seafile-multicolor-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.sf-table-dropdown-menu .dropdown-item:hover .sf-metadata-icon {
|
||||
.sf-table-dropdown-menu .dropdown-item:hover .seafile-multicolor-icon {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
@ -32,6 +32,6 @@
|
||||
color: #c2c2c2;
|
||||
}
|
||||
|
||||
.sf-table-dropdown-menu .disabled.dropdown-item .sf-metadata-icon {
|
||||
.sf-table-dropdown-menu .disabled.dropdown-item .seafile-multicolor-icon {
|
||||
fill: #c2c2c2;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useCallback, cloneElement } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||
import { ModalPortal } from '@seafile/sf-metadata-ui-component';
|
||||
import ModalPortal from '../../../../modal-portal';
|
||||
import { gettext } from '../../../../../utils/constants';
|
||||
import { isMobile } from '../../../../../utils/utils';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, cloneElement } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../../../icon';
|
||||
import { isEnter } from '../../../../../utils/hotkey';
|
||||
|
||||
import './index.css';
|
||||
@ -45,7 +45,7 @@ const InsertColumn = ({ lastColumn, height, groupOffsetLeft, NewColumnComponent,
|
||||
<>
|
||||
<div className="sf-table-header-cell">
|
||||
<div className="sf-table-cell column insert-column" style={style} id={id} ref={ref}>
|
||||
<Icon iconName="add-table" />
|
||||
<Icon symbol="add-table" />
|
||||
</div>
|
||||
</div>
|
||||
{cloneElement(NewColumnComponent, { target: id, onChange: insertColumn })}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../../icon';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
|
||||
class SelectAll extends Component {
|
||||
@ -45,7 +45,7 @@ class SelectAll extends Component {
|
||||
{isMobile ?
|
||||
<label className='mobile-select-all-container'>
|
||||
{isSelectedParts ?
|
||||
(<Icon iconName="partially-selected" />) :
|
||||
(<Icon symbol="partially-selected" />) :
|
||||
(
|
||||
<>
|
||||
<input
|
||||
@ -62,7 +62,7 @@ class SelectAll extends Component {
|
||||
</label> :
|
||||
<>
|
||||
{isSelectedParts ?
|
||||
(<Icon iconName="partially-selected" />) :
|
||||
(<Icon symbol="partially-selected" />) :
|
||||
(
|
||||
<input
|
||||
id="select-all-checkbox"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Loading } from '@seafile/sf-metadata-ui-component';
|
||||
import Loading from '../../../loading';
|
||||
import { RightScrollbar } from '../../scrollbar';
|
||||
import Record from './record';
|
||||
import InteractionMasks from '../../masks/interaction-masks';
|
||||
@ -522,14 +522,14 @@ class RecordsBody extends Component {
|
||||
// add top placeholder
|
||||
if (upperHeight > 0) {
|
||||
const style = { height: upperHeight, width: '100%' };
|
||||
const upperRow = <div key="upper-placeholder" className="d-flex align-items-end" style={style}><Loading /></div>;
|
||||
const upperRow = <div key="upper-placeholder" className="d-flex align-items-end" style={style}><Loading className="sf-metadata-loading-tip center" /></div>;
|
||||
shownRecords.unshift(upperRow);
|
||||
}
|
||||
|
||||
// add bottom placeholder
|
||||
if (belowHeight > 0) {
|
||||
const style = { height: belowHeight, width: '100%' };
|
||||
const belowRow = <div key="below-placeholder" style={style}><Loading /></div>;
|
||||
const belowRow = <div key="below-placeholder" style={style}><Loading className="sf-metadata-loading-tip center" /></div>;
|
||||
shownRecords.push(belowRow);
|
||||
}
|
||||
return shownRecords;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { IconBtn } from '@seafile/sf-metadata-ui-component';
|
||||
import IconBtn from '../../../../../icon-btn';
|
||||
import GroupTitle from './group-title';
|
||||
import { GROUP_HEADER_HEIGHT } from '../../../../constants/group';
|
||||
import { GROUP_FROZEN_HEADER as Z_INDEX_GROUP_FROZEN_HEADER } from '../../../../constants/z-index';
|
||||
@ -32,7 +32,7 @@ class GroupHeaderLeft extends Component {
|
||||
>
|
||||
<IconBtn
|
||||
className={classnames('group-toggle-btn', { 'hide': !isExpanded })}
|
||||
iconName="drop-down"
|
||||
symbol="drop-down"
|
||||
onClick={this.props.onExpandGroupToggle}
|
||||
/>
|
||||
<GroupTitle
|
||||
|
@ -173,13 +173,13 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.canvas-groups-rows .group-toggle-btn .sf-metadata-icon-drop-down {
|
||||
.canvas-groups-rows .group-toggle-btn .seafile-multicolor-icon-drop-down {
|
||||
font-size: 12px;
|
||||
fill: #666666;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.canvas-groups-rows .group-toggle-btn.hide .sf-metadata-icon-drop-down {
|
||||
.canvas-groups-rows .group-toggle-btn.hide .seafile-multicolor-icon-drop-down {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
@ -389,7 +389,7 @@
|
||||
-moz-transition-property: none;
|
||||
}
|
||||
|
||||
.canvas-groups-rows .group-title .group-cell-value .sf-metadata-group-title-rate-item .sf-metadata-icon {
|
||||
.canvas-groups-rows .group-title .group-cell-value .sf-metadata-group-title-rate-item .seafile-multicolor-icon {
|
||||
font-size: 16px;
|
||||
fill: inherit;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import Record from '../record';
|
||||
import { isShiftKeyDown } from '../../../../../utils/keyboard-utils';
|
||||
import { RecordMetrics } from '../../../utils/record-metrics';
|
||||
import { getColumnScrollPosition, getColVisibleEndIdx, getColVisibleStartIdx } from '../../../utils/records-body-utils';
|
||||
import { addClassName, removeClassName } from '../../../utils';
|
||||
import { addClassName, removeClassName } from '../../../../../utils/dom';
|
||||
import { createGroupMetrics, getGroupRecordByIndex, isNestedGroupRow } from '../../../utils/group-metrics';
|
||||
import { checkIsColumnSupportDirectEdit, checkIsColumnFrozen, checkIsNameColumn, getColumnByIndex, checkIsColumnEditable } from '../../../utils/column';
|
||||
import { checkIsCellSupportOpenEditor } from '../../../utils/selected-cell-utils';
|
||||
|
@ -16,7 +16,8 @@ import { getVisibleBoundaries } from '../../utils/viewport';
|
||||
import { getColOverScanEndIdx, getColOverScanStartIdx } from '../../utils/grid';
|
||||
import { isShiftKeyDown } from '../../../../utils/keyboard-utils';
|
||||
import { isMobile } from '../../../../utils/utils';
|
||||
import { isWindowsBrowser, isWebkitBrowser, addClassName, removeClassName, getEventClassName } from '../../utils';
|
||||
import { addClassName, removeClassName, getEventClassName } from '../../../../utils/dom';
|
||||
import { isWindowsBrowser, isWebkitBrowser } from '../../utils';
|
||||
import EventBus from '../../../common/event-bus';
|
||||
import { EVENT_BUS_TYPE } from '../../constants/event-bus-type';
|
||||
import { CANVAS_RIGHT_INTERVAL } from '../../constants/grid';
|
||||
|
@ -83,7 +83,7 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sf-table-cell .select-all-checkbox-container .sf-metadata-icon-partially-selected {
|
||||
.sf-table-cell .select-all-checkbox-container .seafile-multicolor-icon-partially-selected {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
fill: #2b76f6;
|
||||
@ -132,7 +132,7 @@
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.sf-table-cell .mobile-select-all-container .sf-metadata-icon-partially-selected {
|
||||
.sf-table-cell .mobile-select-all-container .seafile-multicolor-icon-partially-selected {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Loading } from '@seafile/sf-metadata-ui-component';
|
||||
import Loading from '../../../loading';
|
||||
import { RightScrollbar } from '../../scrollbar';
|
||||
import InteractionMasks from '../../masks/interaction-masks';
|
||||
import Record from './record';
|
||||
@ -628,14 +628,14 @@ class TreeBody extends Component {
|
||||
// add top placeholder
|
||||
if (upperHeight > 0) {
|
||||
const style = { height: upperHeight, width: '100%' };
|
||||
const upperRow = <div key="upper-placeholder" className="d-flex align-items-end" style={style}><Loading /></div>;
|
||||
const upperRow = <div key="upper-placeholder" className="d-flex align-items-end" style={style}><Loading className="sf-metadata-loading-tip center" /></div>;
|
||||
shownNodes.unshift(upperRow);
|
||||
}
|
||||
|
||||
// add bottom placeholder
|
||||
if (belowHeight > 0) {
|
||||
const style = { height: belowHeight, width: '100%' };
|
||||
const belowRow = <div key="below-placeholder" style={style}><Loading /></div>;
|
||||
const belowRow = <div key="below-placeholder" style={style}><Loading className="sf-metadata-loading-tip center" /></div>;
|
||||
shownNodes.push(belowRow);
|
||||
}
|
||||
return shownNodes;
|
||||
|
@ -1,23 +1,3 @@
|
||||
export const addClassName = (originClassName, targetClassName) => {
|
||||
const originClassNames = originClassName.split(' ');
|
||||
if (originClassNames.indexOf(targetClassName) > -1) return originClassName;
|
||||
return originClassName + ' ' + targetClassName;
|
||||
};
|
||||
|
||||
export const removeClassName = (originClassName, targetClassName) => {
|
||||
let originClassNames = originClassName.split(' ');
|
||||
const targetClassNameIndex = originClassNames.indexOf(targetClassName);
|
||||
if (targetClassNameIndex < 0) return originClassName;
|
||||
originClassNames.splice(targetClassNameIndex, 1);
|
||||
return originClassNames.join(' ');
|
||||
};
|
||||
|
||||
export const getEventClassName = (e) => {
|
||||
// svg mouseEvent event.target.className is an object
|
||||
if (!e || !e.target) return '';
|
||||
return e.target.getAttribute('class') || '';
|
||||
};
|
||||
|
||||
/* is weiXin built-in browser */
|
||||
export const isWeiXinBuiltInBrowser = () => {
|
||||
const agent = navigator.userAgent.toLowerCase();
|
||||
|
@ -4,7 +4,7 @@ import { gettext, siteRoot } from '../../utils/constants';
|
||||
import EmptyTip from '../empty-tip';
|
||||
import LinkItem from './link-item';
|
||||
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
|
||||
import Loading from '../../components/loading';
|
||||
import Loading from '../loading';
|
||||
|
||||
const propTypes = {
|
||||
shareLinks: PropTypes.array.isRequired,
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import '../../../css/switch.css';
|
||||
import './index.css';
|
||||
|
||||
function Switch({ onChange, checked, placeholder, disabled, className, size, textPosition = 'left', setRef }) {
|
||||
return (
|
@ -10,3 +10,7 @@
|
||||
fill: #444;
|
||||
color: #bdbdbd;
|
||||
}
|
||||
|
||||
.sf-metadata-icon {
|
||||
vertical-align: -0.15em;
|
||||
}
|
||||
|
96
frontend/src/css/metadata-rc-calendar.css
Normal file
96
frontend/src/css/metadata-rc-calendar.css
Normal file
@ -0,0 +1,96 @@
|
||||
.sf-metadata-rc-calendar .rc-calendar-date {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar table thead tr,
|
||||
.sf-metadata-rc-calendar .rc-calendar-tbody tr {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-cell {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-date-panel {
|
||||
width: 253px;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-selected-day .rc-calendar-date {
|
||||
background-color: #fcecd9;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-date:hover,
|
||||
.sf-metadata-rc-calendar .rc-calendar-year-panel-year:hover,
|
||||
.sf-metadata-rc-calendar .rc-calendar-month-panel-cell .rc-calendar-month-panel-month:hover {
|
||||
background-color: #fcecd9;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-selected-date .rc-calendar-date,
|
||||
.sf-metadata-rc-calendar .rc-calendar-selected-date .rc-calendar-date:hover,
|
||||
.sf-metadata-rc-calendar .rc-calendar-year-panel-selected-cell .rc-calendar-year-panel-year,
|
||||
.sf-metadata-rc-calendar .rc-calendar-month-panel-selected-cell .rc-calendar-month-panel-month {
|
||||
background-color: #f09f3f;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-next-year-btn:not([href]):not([tabindex]),
|
||||
.sf-metadata-rc-calendar .rc-calendar-prev-year-btn:not([href]):not([tabindex]),
|
||||
.sf-metadata-rc-calendar .rc-calendar-next-month-btn:not([href]):not([tabindex]),
|
||||
.sf-metadata-rc-calendar .rc-calendar-prev-month-btn:not([href]):not([tabindex]) {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-next-year-btn:not([href]):not([tabindex]):hover,
|
||||
.sf-metadata-rc-calendar .rc-calendar-prev-year-btn:not([href]):not([tabindex]):hover,
|
||||
.sf-metadata-rc-calendar .rc-calendar-next-month-btn:not([href]):not([tabindex]):hover,
|
||||
.sf-metadata-rc-calendar .rc-calendar-prev-month-btn:not([href]):not([tabindex]):hover {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-today .rc-calendar-date {
|
||||
border: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-today .rc-calendar-date::after {
|
||||
content: '';
|
||||
background: #f09f3f;
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
left: 45%;
|
||||
bottom: 0%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rdg-editor-container input.editor-main[readonly] {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .rc-calendar-time-picker {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .date-picker-container {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .date-picker-container-row-expand {
|
||||
margin-bottom: 0;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.sf-metadata-rc-calendar .sf-metadata-rc-calendar-clear {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 225px;
|
||||
fill: gray;
|
||||
font-size: 12px;
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sf-metadata-checkbox-editor .sf-metadata-checkbox-editor-content .sf-metadata-icon-check-mark {
|
||||
.sf-metadata-checkbox-editor .sf-metadata-checkbox-editor-content .seafile-multicolor-icon-check-mark {
|
||||
fill: #20c933;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../../../components/icon';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@ -33,7 +33,7 @@ const CheckboxEditor = ({
|
||||
return (
|
||||
<div className="sf-metadata-checkbox-editor" onClick={onClickContainer}>
|
||||
<div className="sf-metadata-checkbox-editor-content" onClick={onChangeValue}>
|
||||
{value && (<Icon iconName="check-mark" />)}
|
||||
{value && (<Icon symbol="check-mark" />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -42,7 +42,7 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.collaborator .collaborator-remove .sf-metadata-icon-x-01 {
|
||||
.collaborator .collaborator-remove .seafile-multicolor-icon-x-01 {
|
||||
fill: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IconBtn } from '@seafile/sf-metadata-ui-component';
|
||||
import IconBtn from '../../../../../components/icon-btn';
|
||||
import { useCollaborators } from '../../../../hooks';
|
||||
|
||||
import './index.css';
|
||||
@ -20,7 +20,7 @@ const DeleteCollaborator = ({ value, onDelete }) => {
|
||||
<img className="collaborator-avatar m-0" alt={name} src={avatar_url} />
|
||||
</span>
|
||||
<span className="collaborator-name text-truncate" title={name} aria-label={name}>{name}</span>
|
||||
<IconBtn className="collaborator-remove" onClick={(event) => onDelete(email, event)} iconName="x-01" />
|
||||
<IconBtn className="collaborator-remove" onClick={(event) => onDelete(email, event)} symbol="x-01" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -84,7 +84,7 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.sf-metadata-collaborator-editor .collaborator-check-icon .sf-metadata-icon-check-icon {
|
||||
.sf-metadata-collaborator-editor .collaborator-check-icon .seafile-multicolor-icon-check-icon {
|
||||
font-size: 12px;
|
||||
fill: #666;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { forwardRef, useMemo, useImperativeHandle, useCallback, useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { SearchInput, Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import SearchInput from '../../../../components/search-input';
|
||||
import Icon from '../../../../components/icon';
|
||||
import DeleteCollaborator from './delete-collaborator';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { KeyCodes } from '../../../../constants';
|
||||
@ -232,7 +233,7 @@ const CollaboratorEditor = forwardRef(({
|
||||
</span>
|
||||
</div>
|
||||
<div className="collaborator-check-icon">
|
||||
{isSelected && (<Icon iconName="check-mark" />)}
|
||||
{isSelected && (<Icon className="sf-metadata-icon" symbol="check-mark" />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
252
frontend/src/metadata/components/cell-editors/date-editor.js
Normal file
252
frontend/src/metadata/components/cell-editors/date-editor.js
Normal file
@ -0,0 +1,252 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import localeData from 'dayjs/plugin/localeData';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||
import Calendar from '@seafile/seafile-calendar';
|
||||
import DatePicker from '@seafile/seafile-calendar/lib/Picker';
|
||||
import dayjs from '../../utils/dayjs';
|
||||
import { translateCalendar } from '../../../utils/date-format-utils';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { getEventClassName } from '../../../utils/dom';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import 'dayjs/locale/en-gb';
|
||||
|
||||
import '@seafile/seafile-calendar/assets/index.css';
|
||||
import '../../../css/metadata-rc-calendar.css';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(localeData);
|
||||
dayjs.extend(weekOfYear);
|
||||
|
||||
let now = dayjs();
|
||||
|
||||
class DateEditor extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: null,
|
||||
};
|
||||
this.format = props.format || 'YYYY-MM-DD';
|
||||
this.calendarContainerRef = React.createRef();
|
||||
const isZhcn = props.lang === 'zh-cn';
|
||||
if (isZhcn) {
|
||||
now = now.locale('zh-cn');
|
||||
} else {
|
||||
now = now.locale('en-gb');
|
||||
}
|
||||
this.defaultCalendarValue = now.clone();
|
||||
this.timeFormat = this.format.split(' ')[1] || '';
|
||||
this.valueSubmitFormat = 'YYYY-MM-DD';
|
||||
if (this.timeFormat) {
|
||||
this.valueSubmitFormat = this.valueSubmitFormat + ' ' + this.timeFormat;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { lang, value } = this.props;
|
||||
const isZhcn = lang === 'zh-cn';
|
||||
if (value && dayjs(value).isValid()) {
|
||||
if (typeof value === 'string' && value.length === 1 && !isNaN(Number(value, 10))) {
|
||||
this.timer = setTimeout(() => {
|
||||
let inputDom = document.getElementsByClassName('rc-calendar-input')[0];
|
||||
if (inputDom) {
|
||||
inputDom.value = value;
|
||||
}
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
let validValue = dayjs(value).isValid() ? dayjs(value) : dayjs(this.defaultCalendarValue);
|
||||
this.setState({ value: isZhcn ? dayjs(validValue).locale('zh-cn') : dayjs(validValue).locale('en-gb') });
|
||||
}
|
||||
document.addEventListener('keydown', this.onHotKey, true);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.onHotKey, true);
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
if (!value) return;
|
||||
this.setState({ value });
|
||||
if (Utils.isFunction(this.props.onChange)) {
|
||||
this.props.onChange(value.format(this.valueSubmitFormat));
|
||||
}
|
||||
};
|
||||
|
||||
getValue = () => {
|
||||
const { value } = this.state;
|
||||
return value ? value.format(this.valueSubmitFormat) : null;
|
||||
};
|
||||
|
||||
getInputNode = () => {
|
||||
if (!this.datePickerRef) return null;
|
||||
if (this.datePickerRef.tagName === 'INPUT') {
|
||||
return this.datePickerRef;
|
||||
}
|
||||
return this.datePickerRef.querySelector('input:not([type=hidden])');
|
||||
};
|
||||
|
||||
onBlur = (type) => {
|
||||
if (this.props.onBlur) this.props.onBlur(type);
|
||||
};
|
||||
|
||||
closeEditor = () => {
|
||||
this.onBlur();
|
||||
};
|
||||
|
||||
getCalendarContainer = () => {
|
||||
return document.body;
|
||||
};
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
const directionKeyCodes = [37, 38, 39, 40];
|
||||
if (directionKeyCodes.includes(e.keyCode)) {
|
||||
e.stopPropagation();
|
||||
} else if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
this.onBlur('enter');
|
||||
}
|
||||
};
|
||||
|
||||
onClick = (event) => {
|
||||
event.stopPropagation();
|
||||
const className = getEventClassName(event);
|
||||
if (!this.timeFormat && className === 'rc-calendar-date') {
|
||||
this.timer = setTimeout(() => {
|
||||
this.closeEditor();
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
onClear = () => {
|
||||
if (Utils.isFunction(this.props.onClear)) {
|
||||
this.props.onClear();
|
||||
return;
|
||||
}
|
||||
this.setState({ value: null });
|
||||
};
|
||||
|
||||
onHotKey = (e) => {
|
||||
if (e.keyCode === 27) {
|
||||
e.stopPropagation();
|
||||
this.props.onClose && this.props.onClose(true);
|
||||
}
|
||||
};
|
||||
|
||||
getCalendarFormat = () => {
|
||||
if (this.format.indexOf('YYYY-MM-DD') > -1) {
|
||||
let newColumnDataFormat = this.format.replace('YYYY-MM-DD', 'YYYY-M-D');
|
||||
return [this.format, newColumnDataFormat];
|
||||
}
|
||||
if (this.format.indexOf('DD/MM/YYYY') > -1) {
|
||||
let newColumnDataFormat = this.format.replace('DD/MM/YYYY', 'D/M/YYYY');
|
||||
return [this.format, newColumnDataFormat];
|
||||
}
|
||||
return [this.format];
|
||||
};
|
||||
|
||||
getDefaultMinutesTime = () => {
|
||||
const { value } = this.props;
|
||||
if (!this.timeFormat) return '';
|
||||
if (value) return dayjs(value).format('HH:mm');
|
||||
return '';
|
||||
};
|
||||
|
||||
onClickRightPanelTime = () => {
|
||||
if (this.timeFormat.indexOf('ss') > 0) return;
|
||||
|
||||
setTimeout(() => {
|
||||
this.closeEditor();
|
||||
}, 1);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isReadOnly, lang } = this.props;
|
||||
const state = this.state;
|
||||
if (isReadOnly) return (
|
||||
<input
|
||||
className="ant-calendar-picker-input ant-input form-control"
|
||||
value={state.value ? state.value.format(this.format) : ''}
|
||||
disabled={true}
|
||||
/>
|
||||
);
|
||||
const calendarFormat = this.getCalendarFormat();
|
||||
const defaultMinutesTime = this.getDefaultMinutesTime();
|
||||
const calendar = (
|
||||
<Calendar
|
||||
className="sf-metadata-rc-calendar"
|
||||
locale={translateCalendar(lang)}
|
||||
style={{ zIndex: 1060 }}
|
||||
dateInputPlaceholder={gettext('Enter date')}
|
||||
format={calendarFormat}
|
||||
defaultValue={this.defaultCalendarValue}
|
||||
showDateInput={true}
|
||||
focusablePanel={false}
|
||||
showHourAndMinute={Boolean(this.timeFormat)}
|
||||
defaultMinutesTime={defaultMinutesTime}
|
||||
onClear={this.onClear}
|
||||
onClickRightPanelTime={this.onClickRightPanelTime}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div className="date-picker-container" ref={ref => this.datePickerRef = ref} onKeyDown={(e) => this.handleKeyDown(e)} onClick={(e) => this.onClick(e)}>
|
||||
<DatePicker
|
||||
calendar={calendar}
|
||||
value={state.value}
|
||||
onChange={this.onChange}
|
||||
getCalendarContainer={this.getCalendarContainer}
|
||||
onOpenChange={this.onOpenChange}
|
||||
open={true}
|
||||
style={{ zIndex: 1060 }}
|
||||
>
|
||||
{
|
||||
({ value }) => {
|
||||
return (
|
||||
<span tabIndex="0">
|
||||
<input
|
||||
ref={ref => this.inputRef = ref}
|
||||
placeholder={this.format ? this.format : gettext('Please select')}
|
||||
tabIndex="-1"
|
||||
readOnly
|
||||
className="ant-calendar-picker-input ant-input form-control"
|
||||
value={value ? value.format(this.format) : ''}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
/>
|
||||
<div ref={this.calendarContainerRef} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
</DatePicker>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DateEditor.propTypes = {
|
||||
isReadOnly: PropTypes.bool,
|
||||
format: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
DateEditor.defaultProps = {
|
||||
format: 'YYYY-MM-DD',
|
||||
lang: 'zh-cn',
|
||||
isReadOnly: false,
|
||||
};
|
||||
|
||||
export default DateEditor;
|
@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { ClickOutside } from '@seafile/sf-metadata-ui-component';
|
||||
import ClickOutside from '../../../../components/click-outside';
|
||||
import Editor from '../editor';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { getEventClassName } from '../../../utils/common';
|
||||
import { getEventClassName } from '../../../../utils/dom';
|
||||
import { isCellValueChanged, getCellValueByColumn } from '../../../utils/cell';
|
||||
import { canEditCell } from '../../../utils/column';
|
||||
import { isCtrlKeyHeldDown, isKeyPrintable } from '../../../utils/keyboard-utils';
|
||||
import { isCtrlKeyHeldDown, isKeyPrintable } from '../../../../utils/keyboard-utils';
|
||||
import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEYS, metadataZIndexes, CellType } from '../../../constants';
|
||||
|
||||
class NormalEditorContainer extends React.Component {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { ClickOutside } from '@seafile/sf-metadata-ui-component';
|
||||
import ClickOutside from '../../../../components/click-outside';
|
||||
import Editor from '../editor';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { isCellValueChanged, getCellValueByColumn, getColumnOptionNameById, getColumnOptionNamesByIds } from '../../../utils/cell';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SfCalendar } from '@seafile/sf-metadata-ui-component';
|
||||
import DateEditor from './date-editor';
|
||||
import FileNameEditor from './file-name-editor';
|
||||
import TextEditor from './text-editor';
|
||||
import NumberEditor from './number-editor';
|
||||
@ -23,7 +23,7 @@ const Editor = React.forwardRef((props, ref) => {
|
||||
return (<TextEditor ref={ref} { ...props } />);
|
||||
}
|
||||
case CellType.DATE: {
|
||||
return (<SfCalendar ref={ref} { ...props } lang={lang} />);
|
||||
return (<DateEditor ref={ref} { ...props } lang={lang} />);
|
||||
}
|
||||
case CellType.NUMBER: {
|
||||
return (<NumberEditor ref={ref} {...props} />);
|
||||
|
@ -23,7 +23,7 @@
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-select-options .sf-metadata-delete-select-option .sf-metadata-icon-x-01 {
|
||||
.sf-metadata-delete-select-options .sf-metadata-delete-select-option .seafile-multicolor-icon-x-01 {
|
||||
fill: inherit;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IconBtn } from '@seafile/sf-metadata-ui-component';
|
||||
import IconBtn from '../../../../../components/icon-btn';
|
||||
import { gettext } from '../../../../../utils/constants';
|
||||
import { DELETED_OPTION_TIPS, DELETED_OPTION_BACKGROUND_COLOR } from '../../../../constants';
|
||||
|
||||
@ -41,7 +41,7 @@ const DeleteOption = ({ value, options, onDelete }) => {
|
||||
return (
|
||||
<div key={id} className="sf-metadata-delete-select-option" style={style}>
|
||||
<span className="sf-metadata-delete-select-option-name text-truncate" title={name} aria-label={name}>{name}</span>
|
||||
<IconBtn className="sf-metadata-delete-select-remove" onClick={(event) => onDelete(id, event)} iconName="x-01" />
|
||||
<IconBtn className="sf-metadata-delete-select-remove" onClick={(event) => onDelete(id, event)} symbol="x-01" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, { forwardRef, useMemo, useImperativeHandle, useCallback, useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { SearchInput, CustomizeAddTool, Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import CommonAddTool from '../../../../components/common-add-tool';
|
||||
import SearchInput from '../../../../components/search-input';
|
||||
import Icon from '../../../../components/icon';
|
||||
import DeleteOption from './delete-options';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { getColumnOptionIdsByNames } from '../../../utils/cell';
|
||||
@ -236,7 +238,7 @@ const MultipleSelectEditor = forwardRef(({
|
||||
</span>
|
||||
</div>
|
||||
<div className="single-select-check-icon">
|
||||
{isSelected && (<Icon iconName="check-mark" />)}
|
||||
{isSelected && (<Icon className="sf-metadata-icon" symbol="check-mark" />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -261,7 +263,7 @@ const MultipleSelectEditor = forwardRef(({
|
||||
{renderOptions()}
|
||||
</div>
|
||||
{isShowCreateBtn && (
|
||||
<CustomizeAddTool
|
||||
<CommonAddTool
|
||||
callBack={createOption}
|
||||
footerName={`${gettext('Add option')} ${searchValue}`}
|
||||
className="add-search-result"
|
||||
|
@ -12,7 +12,7 @@
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.sf-metadata-rate-item .sf-metadata-icon {
|
||||
.sf-metadata-rate-item .seafile-multicolor-icon {
|
||||
fill: inherit;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import React, { useCallback, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { UncontrolledTooltip } from 'reactstrap';
|
||||
import classnames from 'classnames';
|
||||
import { Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../../../components/icon';
|
||||
import { DEFAULT_RATE_DATA } from '../../../constants';
|
||||
|
||||
const RateItem = ({
|
||||
@ -51,7 +51,7 @@ const RateItem = ({
|
||||
className={classnames('sf-metadata-rate-item', { 'active': value >= index })}
|
||||
ref={ref}
|
||||
>
|
||||
<Icon iconName={type || 'rate'} />
|
||||
<Icon className="sf-metadata-icon" symbol={type || 'rate'} />
|
||||
</div>
|
||||
{enterIndex !== -1 && (
|
||||
<UncontrolledTooltip placement='bottom' target={ref} modifiers={[{ name: 'preventOverflow', options: { boundary: document.body } }]} className="sf-metadata-tooltip">
|
||||
|
@ -97,7 +97,7 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sf-metadata-single-select-editor .single-select-check-icon .sf-metadata-icon-check-icon {
|
||||
.sf-metadata-single-select-editor .single-select-check-icon .seafile-multicolor-icon-check-icon {
|
||||
font-size: 12px;
|
||||
fill: #666;
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, { forwardRef, useMemo, useImperativeHandle, useCallback, useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { SearchInput, CustomizeAddTool, Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import CommonAddTool from '../../../../components/common-add-tool';
|
||||
import SearchInput from '../../../../components/search-input';
|
||||
import Icon from '../../../../components/icon';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { KeyCodes } from '../../../../constants';
|
||||
@ -232,7 +234,7 @@ const SingleSelectEditor = forwardRef(({
|
||||
</span>
|
||||
</div>
|
||||
<div className="single-select-check-icon">
|
||||
{isSelected && (<Icon iconName="check-mark" />)}
|
||||
{isSelected && (<Icon className="sf-metadata-icon" symbol="check-mark" />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -256,7 +258,7 @@ const SingleSelectEditor = forwardRef(({
|
||||
{renderOptions()}
|
||||
</div>
|
||||
{isShowCreateBtn && (
|
||||
<CustomizeAddTool
|
||||
<CommonAddTool
|
||||
callBack={createOption}
|
||||
footerName={`${gettext('Add option')} ${searchValue}`}
|
||||
className="add-search-result"
|
||||
|
@ -50,7 +50,7 @@
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-select-tags .sf-metadata-delete-select-remove .sf-metadata-icon-x-01 {
|
||||
.sf-metadata-delete-select-tags .sf-metadata-delete-select-remove .seafile-multicolor-icon-x-01 {
|
||||
fill: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from './index';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IconBtn } from '@seafile/sf-metadata-ui-component';
|
||||
import { getRowById } from '../../../../utils/table';
|
||||
import IconBtn from '../../../../../components/icon-btn';
|
||||
import { getRowById } from '../../../../../components/sf-table/utils/table';
|
||||
import { getTagColor, getTagName } from '../../../../../tag/utils/cell';
|
||||
|
||||
import './index.css';
|
||||
@ -18,7 +18,7 @@ const DeleteTag = ({ value, tags, onDelete }) => {
|
||||
<div className="sf-metadata-delete-select-tag" key={tagId}>
|
||||
<div className="sf-metadata-delete-select-tag-color" style={{ backgroundColor: tagColor }}></div>
|
||||
<div className="sf-metadata-delete-select-tag-name">{tagName}</div>
|
||||
<IconBtn className="sf-metadata-delete-select-remove" onClick={(event) => onDelete(tagId, event)} iconName="x-01" />
|
||||
<IconBtn className="sf-metadata-delete-select-remove" onClick={(event) => onDelete(tagId, event)} symbol="x-01" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -1,17 +1,19 @@
|
||||
import React, { forwardRef, useMemo, useCallback, useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { SearchInput, CustomizeAddTool, Icon } from '@seafile/sf-metadata-ui-component';
|
||||
import CommonAddTool from '../../../../components/common-add-tool';
|
||||
import SearchInput from '../../../../components/search-input';
|
||||
import Icon from '../../../../components/icon';
|
||||
import DeleteTags from './delete-tags';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { KeyCodes } from '../../../../constants';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { useTags } from '../../../../tag/hooks';
|
||||
import { getTagColor, getTagId, getTagName, getTagsByNameOrColor, getTagByNameOrColor } from '../../../../tag/utils/cell';
|
||||
import { getRecordIdFromRecord } from '../../../utils/cell';
|
||||
import { getRowById } from '../../../utils/table';
|
||||
import { getRowById } from '../../../../components/sf-table/utils/table';
|
||||
import { SELECT_OPTION_COLORS } from '../../../constants';
|
||||
import { PRIVATE_COLUMN_KEY as TAG_PRIVATE_COLUMN_KEY } from '../../../../tag/constants';
|
||||
import DeleteTags from './delete-tags';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@ -228,7 +230,7 @@ const TagsEditor = forwardRef(({
|
||||
<div className="sf-metadata-tag-name">{tagName}</div>
|
||||
</div>
|
||||
<div className="sf-metadata-tags-editor-tag-check-icon">
|
||||
{isSelected && (<Icon iconName="check-mark" />)}
|
||||
{isSelected && (<Icon className="sf-metadata-icon" symbol="check-mark" />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -253,7 +255,7 @@ const TagsEditor = forwardRef(({
|
||||
{renderOptions()}
|
||||
</div>
|
||||
{isShowCreateBtn && (
|
||||
<CustomizeAddTool
|
||||
<CommonAddTool
|
||||
callBack={createTag}
|
||||
footerName={`${gettext('Add tag')} ${searchValue}`}
|
||||
className="add-search-result"
|
||||
|
@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Collaborator from './collaborator';
|
||||
import { isValidEmail } from '../../utils/validate/email';
|
||||
|
||||
const AsyncCollaborator = ({ value, mediaUrl, api, collaborators, collaboratorsCache, updateCollaboratorsCache }) => {
|
||||
const [collaborator, setCollaborator] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
if (!value) {
|
||||
isMounted && setCollaborator(null);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
let collaborator = collaborators && collaborators.find(c => c.email === value);
|
||||
if (collaborator) {
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
const defaultAvatarUrl = `${mediaUrl}/avatars/default.png`;
|
||||
if (value === 'anonymous') {
|
||||
collaborator = {
|
||||
name: 'anonymous',
|
||||
avatar_url: defaultAvatarUrl,
|
||||
};
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
collaborator = collaboratorsCache[value];
|
||||
if (collaborator) {
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
if (!isValidEmail(value)) {
|
||||
collaborator = {
|
||||
email: value,
|
||||
name: value,
|
||||
avatar_url: defaultAvatarUrl,
|
||||
};
|
||||
updateCollaboratorsCache(collaborator);
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
api && api(value, (userMap) => {
|
||||
collaborator = userMap[value];
|
||||
updateCollaboratorsCache(collaborator);
|
||||
isMounted && setCollaborator(collaborator);
|
||||
});
|
||||
return () => isMounted = false;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!collaborator) return null;
|
||||
|
||||
return (
|
||||
<Collaborator collaborator={collaborator} />
|
||||
);
|
||||
};
|
||||
|
||||
AsyncCollaborator.propTypes = {
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default AsyncCollaborator;
|
@ -0,0 +1,13 @@
|
||||
.sf-metadata-ui.checkbox-formatter {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.checkbox-formatter .seafile-multicolor-icon-check-mark {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
fill: #20c933;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Icon from '../../../../components/icon';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const CheckboxFormatter = ({ value, className, children: emptyFormatter }) => {
|
||||
if (!value) return emptyFormatter;
|
||||
return (
|
||||
<div className={classnames('sf-metadata-ui cell-formatter-container checkbox-formatter', className)}>
|
||||
<Icon symbol="check-mark"/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CheckboxFormatter.propTypes = {
|
||||
value: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default CheckboxFormatter;
|
@ -0,0 +1,59 @@
|
||||
.sf-metadata-ui.collaborator-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
padding: 0 8px 0 2px;
|
||||
height: 20px;
|
||||
font-size: 13px;
|
||||
border-radius: 10px;
|
||||
background: #eaeaea;
|
||||
width: fit-content;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.collaborator-item .collaborator-avatar,
|
||||
.sf-metadata-ui.collaborator-item .collaborator-name,
|
||||
.sf-metadata-ui.collaborator-item .collaborator-remove {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.collaborator-item .collaborator-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 5px 0 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.collaborator-item .collaborator-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.collaborator-item .collaborator-avatar img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.collaborator-item .collaborator-remove {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
margin: 0 -2px 0 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.collaborator-item .collaborator-remove .seafile-multicolor-icon {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: #909090;
|
||||
transform: scale(.8);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-metadata-ui.collaborator-item .collaborator-remove .seafile-multicolor-icon:hover {
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from '../../../../components/icon';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const Collaborator = ({ enableDelete = false, collaborator, onDelete }) => {
|
||||
if (!collaborator) return null;
|
||||
|
||||
return (
|
||||
<div className="sf-metadata-ui collaborator-item" title={collaborator.name}>
|
||||
<span className="collaborator-avatar">
|
||||
<img className="collaborator-avatar-icon" alt={collaborator.name} src={collaborator.avatar_url} />
|
||||
</span>
|
||||
<span className="collaborator-name">{collaborator.name}</span>
|
||||
{enableDelete && (
|
||||
<span className="collaborator-remove" onClick={onDelete}>
|
||||
<Icon symbol="delete" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Collaborator.propTypes = {
|
||||
collaborator: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
avatar_url: PropTypes.string.isRequired,
|
||||
email: PropTypes.string,
|
||||
}),
|
||||
enableDelete: PropTypes.bool,
|
||||
onDeleteCollaborator: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Collaborator;
|
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import AsyncCollaborator from './async-collaborator';
|
||||
|
||||
const CollaboratorsFormatter = ({ value, className, children: emptyFormatter, ...params }) => {
|
||||
if (!Array.isArray(value) || value.length === 0) return emptyFormatter || null;
|
||||
const validValue = value.filter(item => item);
|
||||
if (validValue.length === 0) return emptyFormatter || null;
|
||||
return (
|
||||
<div className={classnames('sf-metadata-ui cell-formatter-container collaborators-formatter', className)}>
|
||||
{value.map(email => <AsyncCollaborator key={email} { ...params } value={email} />)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CollaboratorsFormatter.propTypes = {
|
||||
value: PropTypes.array,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default CollaboratorsFormatter;
|
75
frontend/src/metadata/components/cell-formatter/creator.js
Normal file
75
frontend/src/metadata/components/cell-formatter/creator.js
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Collaborator from './collaborator';
|
||||
import { isValidEmail } from '../../utils/validate/email';
|
||||
|
||||
const CreatorFormatter = ({ value, mediaUrl, className, api, collaborators = [], collaboratorsCache = {}, updateCollaboratorsCache, children: emptyFormatter }) => {
|
||||
const [collaborator, setCollaborator] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
if (!value) {
|
||||
isMounted && setCollaborator(null);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
let collaborator = collaborators && collaborators.find(c => c.email === value);
|
||||
if (collaborator) {
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
const defaultAvatarUrl = `${mediaUrl}/avatars/default.png`;
|
||||
if (value === 'anonymous') {
|
||||
collaborator = {
|
||||
name: 'anonymous',
|
||||
avatar_url: defaultAvatarUrl,
|
||||
};
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
collaborator = collaboratorsCache[value];
|
||||
if (collaborator) {
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
if (!isValidEmail(value)) {
|
||||
collaborator = {
|
||||
email: value,
|
||||
name: value,
|
||||
avatar_url: defaultAvatarUrl,
|
||||
};
|
||||
updateCollaboratorsCache && updateCollaboratorsCache(collaborator);
|
||||
isMounted && setCollaborator(collaborator);
|
||||
return () => isMounted = false;
|
||||
}
|
||||
|
||||
api && api(value, (userMap) => {
|
||||
collaborator = userMap[value];
|
||||
Object.values(userMap).forEach(user => {
|
||||
updateCollaboratorsCache && updateCollaboratorsCache(user);
|
||||
});
|
||||
isMounted && setCollaborator(collaborator);
|
||||
});
|
||||
return () => isMounted = false;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!collaborator) return emptyFormatter || null;
|
||||
return (
|
||||
<div className={classnames('sf-metadata-ui cell-formatter-container creator-formatter', className)}>
|
||||
<Collaborator collaborator={collaborator} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CreatorFormatter.propTypes = {
|
||||
value: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default CreatorFormatter;
|
26
frontend/src/metadata/components/cell-formatter/ctime.js
Normal file
26
frontend/src/metadata/components/cell-formatter/ctime.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const CTimeFormatter = ({ value, className, children: emptyFormatter }) => {
|
||||
if (!value) return emptyFormatter || null;
|
||||
|
||||
const valueFormat = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
||||
return (
|
||||
<div
|
||||
className={classnames('sf-metadata-ui cell-formatter-container ctime-formatter', className)}
|
||||
title={valueFormat}
|
||||
>
|
||||
{valueFormat}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CTimeFormatter.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default CTimeFormatter;
|
@ -0,0 +1,3 @@
|
||||
.sf-metadata-ui.date-formatter {
|
||||
text-align: right;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { getDateDisplayString } from '../../../utils/cell/column/date';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const DateFormatter = ({ value, format, className, children: emptyFormatter }) => {
|
||||
const displayValue = useMemo(() => {
|
||||
return getDateDisplayString(value, format);
|
||||
}, [value, format]);
|
||||
|
||||
if (!displayValue) return emptyFormatter || null;
|
||||
return (
|
||||
<div
|
||||
className={classnames('sf-metadata-ui cell-formatter-container date-formatter', className)}
|
||||
title={displayValue}
|
||||
>
|
||||
{displayValue}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DateFormatter.propTypes = {
|
||||
value: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default DateFormatter;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user