diff --git a/frontend/src/components/dir-view-mode/dir-list-view.js b/frontend/src/components/dir-view-mode/dir-list-view.js index 4fce0d6057..0d8439cf04 100644 --- a/frontend/src/components/dir-view-mode/dir-list-view.js +++ b/frontend/src/components/dir-view-mode/dir-list-view.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import DirentNoneView from '../../components/dirent-list-view/dirent-none-view'; import RepoInfoBar from '../../components/repo-info-bar'; import DirentListView from '../../components/dirent-list-view/dirent-list-view'; +import Loading from '../loading'; const propTypes = { path: PropTypes.string.isRequired, @@ -71,43 +72,46 @@ class DirListView extends React.Component { onFileTagChanged={this.props.onFileTagChanged} /> )} - + {this.props.isDirentListLoading ? ( + + ) : ( + + )} ); } diff --git a/frontend/src/components/dirent-list-view/dirent-list-item.js b/frontend/src/components/dirent-list-view/dirent-list-item.js index e8044016e9..107660efec 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-item.js +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -819,17 +819,18 @@ class DirentListItem extends React.Component { onMouseDown={this.onItemMouseDown} onContextMenu={this.onItemContextMenu} > - + {}} checked={isSelected} aria-label={isSelected ? gettext('Unselect this item') : gettext('Select this item')} /> - + {dirent.starred !== undefined && } - +
{(this.canPreview && dirent.encoded_thumbnail_src) ? : diff --git a/frontend/src/components/dirent-list-view/dirent-list-view.js b/frontend/src/components/dirent-list-view/dirent-list-view.js index 91543ad21e..400eef1394 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-view.js +++ b/frontend/src/components/dirent-list-view/dirent-list-view.js @@ -4,7 +4,6 @@ import { siteRoot, gettext, username, enableSeadoc, thumbnailSizeForOriginal, th import { Utils } from '../../utils/utils'; import TextTranslation from '../../utils/text-translation'; import URLDecorator from '../../utils/url-decorator'; -import Loading from '../loading'; import toaster from '../toast'; import ModalPortal from '../modal-portal'; import CreateFile from '../dialog/create-file-dialog'; @@ -27,7 +26,6 @@ const propTypes = { repoID: PropTypes.string.isRequired, currentRepoInfo: PropTypes.object, isAllItemSelected: PropTypes.bool.isRequired, - isDirentListLoading: PropTypes.bool.isRequired, direntList: PropTypes.array.isRequired, sortBy: PropTypes.string.isRequired, sortOrder: PropTypes.string.isRequired, @@ -82,6 +80,7 @@ class DirentListView extends React.Component { activeDirent: null, isListDropTipShow: false, isShowDirentsDraggablePreview: false, + containerWidth: 0, }; this.enteredCounter = 0; // Determine whether to enter the child element to avoid dragging bubbling bugs。 @@ -102,12 +101,20 @@ class DirentListView extends React.Component { const { modify } = customPermission.permission; this.canDrop = modify; } + + this.containerRef = null; } componentDidMount() { this.unsubscribeEvent = this.props.eventBus.subscribe(EVENT_BUS_TYPE.RESTORE_IMAGE, this.recalculateImageItems); + this.resizeObserver = new ResizeObserver(this.handleResize); + this.containerRef && this.resizeObserver.observe(this.containerRef); } + handleResize = () => { + this.setState({ containerWidth: this.containerRef.offsetWidth - 32 }); + }; + recalculateImageItems = () => { if (!this.state.isImagePopupOpen) return; let imageItems = this.props.direntList @@ -122,6 +129,7 @@ class DirentListView extends React.Component { componentWillUnmount() { this.unsubscribeEvent(); + this.containerRef && this.resizeObserver.unobserve(this.containerRef); } freezeItem = () => { @@ -680,10 +688,7 @@ class DirentListView extends React.Component { render() { const { direntList, sortBy, sortOrder } = this.props; - - if (this.props.isDirentListLoading) { - return (); - } + const { containerWidth } = this.state; // sort const sortByName = sortBy == 'name'; @@ -704,13 +709,14 @@ class DirentListView extends React.Component { onDragOver={this.onTableDragOver} onDragLeave={this.onTableDragLeave} onDrop={this.tableDrop} + ref={ref => this.containerRef = ref} > {direntList.length > 0 && {isDesktop ? ( - - - - - - - - + + + + + + + ) : ( diff --git a/frontend/src/metadata/components/cell-editors/file-name-editor.js b/frontend/src/metadata/components/cell-editors/file-name-editor.js index 97f9cb013d..333b9cab73 100644 --- a/frontend/src/metadata/components/cell-editors/file-name-editor.js +++ b/frontend/src/metadata/components/cell-editors/file-name-editor.js @@ -37,9 +37,12 @@ const FileNameEditor = React.forwardRef((props, ref) => { if (mode === EDITOR_TYPE.PREVIEWER) { const fileType = getFileType(); + const repoID = window.sfMetadataContext.getSetting('repoID'); + const repoInfo = window.sfMetadataContext.getSetting('repoInfo'); + if (fileType === 'image') { return ( - + ); } diff --git a/frontend/src/metadata/components/cell-formatter/image-previewer.js b/frontend/src/metadata/components/cell-formatter/image-previewer.js index 7fb5d7ce22..4b7c90d1cb 100644 --- a/frontend/src/metadata/components/cell-formatter/image-previewer.js +++ b/frontend/src/metadata/components/cell-formatter/image-previewer.js @@ -9,14 +9,11 @@ import { Utils } from '../../../utils/utils'; import { siteRoot, thumbnailSizeForOriginal, fileServerRoot, thumbnailDefaultSize } from '../../../utils/constants'; import { getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell'; -const ImagePreviewer = (props) => { - const { record, table, closeImagePopup } = props; +const ImagePreviewer = ({ record, table, repoID, repoInfo, closeImagePopup }) => { const [imageIndex, setImageIndex] = useState(0); const [imageItems, setImageItems] = useState([]); useEffect(() => { - const repoID = window.sfMetadataContext.getSetting('repoID'); - const repoInfo = window.sfMetadataContext.getSetting('repoInfo'); const newImageItems = table.rows .filter((row) => Utils.imageCheck(getFileNameFromRecord(row))) .map((row) => { @@ -40,7 +37,7 @@ const ImagePreviewer = (props) => { }; }); setImageItems(newImageItems); - }, [table]); + }, [table, repoID, repoInfo]); useEffect(() => { if (imageItems.length > 0) { @@ -98,9 +95,10 @@ const ImagePreviewer = (props) => { }; ImagePreviewer.propTypes = { - table: PropTypes.object, - column: PropTypes.object, record: PropTypes.object, + table: PropTypes.object, + repoID: PropTypes.string, + repoInfo: PropTypes.object, closeImagePopup: PropTypes.func, }; diff --git a/frontend/src/metadata/constants/column/icon.js b/frontend/src/metadata/constants/column/icon.js index 8465254366..b4861ab66d 100644 --- a/frontend/src/metadata/constants/column/icon.js +++ b/frontend/src/metadata/constants/column/icon.js @@ -39,6 +39,7 @@ const COLUMNS_ICON_NAME = { [CellType.GEOLOCATION]: 'Geolocation', [CellType.RATE]: 'Rate', [CellType.LINK]: 'Link', + [CellType.TAGS]: 'Tag', }; export { diff --git a/frontend/src/metadata/utils/cell/core.js b/frontend/src/metadata/utils/cell/core.js index 350f2833bf..e497c964a9 100644 --- a/frontend/src/metadata/utils/cell/core.js +++ b/frontend/src/metadata/utils/cell/core.js @@ -1,4 +1,6 @@ import { PRIVATE_COLUMN_KEY, PRIVATE_COLUMN_KEYS } from '../../constants'; +import { siteRoot } from '../../../utils/constants'; +import { Utils } from '../../../utils/utils'; /** * @param {any} value @@ -59,3 +61,18 @@ export const getFileMTimeFromRecord = record => { export const getTagsFromRecord = record => { return record ? record[PRIVATE_COLUMN_KEY.TAGS] : ''; }; + +const _getParentDir = (record) => { + const parentDir = getParentDirFromRecord(record); + if (parentDir === '/') { + return ''; + } + return parentDir; +}; + +export const getFilePathByRecord = (repoID, record) => { + const parentDir = _getParentDir(record); + const fileName = getFileNameFromRecord(record); + const path = Utils.encodePath(Utils.joinPath(parentDir, fileName)); + return siteRoot + 'lib/' + repoID + '/file' + path; +}; diff --git a/frontend/src/metadata/utils/open-file.js b/frontend/src/metadata/utils/open-file.js index 2263a4b138..18310c0586 100644 --- a/frontend/src/metadata/utils/open-file.js +++ b/frontend/src/metadata/utils/open-file.js @@ -2,7 +2,6 @@ import { getFileNameFromRecord, getParentDirFromRecord } from './cell'; import { checkIsDir } from './row'; import { Utils } from '../../utils/utils'; import { siteRoot } from '../../utils/constants'; -import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; const FILE_TYPE = { FOLDER: 'folder', @@ -32,23 +31,28 @@ const _getParentDir = (record) => { return parentDir; }; -const _generateUrl = (fileName, parentDir) => { - const repoID = window.sfMetadataContext.getSetting('repoID'); +const _generateUrl = (repoID, fileName, parentDir) => { const path = Utils.encodePath(Utils.joinPath(parentDir, fileName)); return `${siteRoot}lib/${repoID}/file${path}`; }; const _openUrl = (url) => { + const isWeChat = Utils.isWeChat(); + if (isWeChat) { + location.href = url; + return; + } window.open(url); }; -const _openMarkdown = (fileName, parentDir, eventBus) => { - eventBus && eventBus.dispatch(EVENT_BUS_TYPE.OPEN_MARKDOWN, parentDir, fileName); +const _openMarkdown = (repoID, fileName, parentDir) => { + const url = _generateUrl(repoID, fileName, parentDir); + _openUrl(url); }; -const _openByNewWindow = (fileName, parentDir, fileType) => { +const _openByNewWindow = (repoID, fileName, parentDir, fileType) => { if (!fileType) { - const url = _generateUrl(fileName, parentDir); + const url = _generateUrl(repoID, fileName, parentDir); _openUrl(url); return; } @@ -59,16 +63,16 @@ const _openByNewWindow = (fileName, parentDir, fileType) => { _openUrl(window.location.origin + pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName))); }; -const _openSdoc = (fileName, parentDir) => { - const url = _generateUrl(fileName, parentDir); +const _openSdoc = (repoID, fileName, parentDir) => { + const url = _generateUrl(repoID, fileName, parentDir); _openUrl(url); }; -const _openOthers = (fileName, parentDir, fileType) => { - _openByNewWindow(fileName, parentDir, fileType); +const _openOthers = (repoID, fileName, parentDir, fileType) => { + _openByNewWindow(repoID, fileName, parentDir, fileType); }; -export const openFile = (record, eventBus, _openImage = () => {}) => { +export const openFile = (repoID, record, _openImage = () => {}) => { if (!record) return; const fileName = getFileNameFromRecord(record); const isDir = checkIsDir(record); @@ -77,11 +81,11 @@ export const openFile = (record, eventBus, _openImage = () => {}) => { switch (fileType) { case FILE_TYPE.MARKDOWN: { - _openMarkdown(fileName, parentDir, eventBus); + _openMarkdown(repoID, fileName, parentDir); break; } case FILE_TYPE.SDOC: { - _openSdoc(fileName, parentDir); + _openSdoc(repoID, fileName, parentDir); break; } case FILE_TYPE.IMAGE: { @@ -89,7 +93,7 @@ export const openFile = (record, eventBus, _openImage = () => {}) => { break; } default: { - _openOthers(fileName, parentDir, fileType); + _openOthers(repoID, fileName, parentDir, fileType); break; } } diff --git a/frontend/src/metadata/views/kanban/boards/board/card/index.css b/frontend/src/metadata/views/kanban/boards/board/card/index.css index 28fd3ef8a7..4db250ea94 100644 --- a/frontend/src/metadata/views/kanban/boards/board/card/index.css +++ b/frontend/src/metadata/views/kanban/boards/board/card/index.css @@ -12,7 +12,7 @@ user-select: none; } -.smooth-dnd-container.vertical .sf-metadata-kanban-card:last-child { +.smooth-dnd-container.vertical .smooth-dnd-draggable-wrapper:last-child .sf-metadata-kanban-card { margin-bottom: 0; } diff --git a/frontend/src/metadata/views/kanban/boards/index.js b/frontend/src/metadata/views/kanban/boards/index.js index f3c17bca37..31b34e3985 100644 --- a/frontend/src/metadata/views/kanban/boards/index.js +++ b/frontend/src/metadata/views/kanban/boards/index.js @@ -176,7 +176,8 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { }, []); const onOpenFile = useCallback((record) => { - openFile(record, window.sfMetadataContext.eventBus, () => { + const repoID = window.sfMetadataContext.getSetting('repoID'); + openFile(repoID, record, () => { currentImageRef.current = record; setImagePreviewerVisible(true); }); @@ -222,6 +223,8 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { }, [isDirentDetailShow]); const isEmpty = boards.length === 0; + const repoID = window.sfMetadataContext.getSetting('repoID'); + const repoInfo = window.sfMetadataContext.getSetting('repoInfo'); return (
{ )} {!readonly && ()}
- {isImagePreviewerVisible && ()} + {isImagePreviewerVisible && ( + + )} ); }; diff --git a/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js b/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js index 7d117e93db..a71c0811a9 100644 --- a/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js +++ b/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js @@ -21,7 +21,8 @@ const FileNameOperationBtn = ({ column, record, ...props }) => { const handelClick = (event) => { event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); - openFile(record, window.sfMetadataContext.eventBus, () => { + const repoID = window.sfMetadataContext.getSetting('repoID'); + openFile(repoID, record, () => { window.sfMetadataContext.eventBus.dispatch(METADATA_EVENT_BUS_TYPE.OPEN_EDITOR, EDITOR_TYPE.PREVIEWER); }); }; diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index 074bfadf67..e07c4e512d 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -447,14 +447,6 @@ class LibContentView extends React.Component { window.history.pushState({ url: url, path: path }, path, url); }; - openMarkDown = (parentDir, fileName) => { - let filePath = Utils.joinPath(parentDir, fileName); - let repoID = this.props.repoID; - const w = window.open('about:blank'); - const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(filePath); - w.location.href = url; - }; - showFile = (filePath, noRedirection) => { let repoID = this.props.repoID; @@ -517,10 +509,6 @@ class LibContentView extends React.Component { path: filePath, viewId: viewId, isDirentDetailShow: viewType === VIEW_TYPE.GALLERY ? this.state.isDirentDetailShow : false, - }, () => { - setTimeout(() => { - this.unsubscribeEventBus = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.OPEN_MARKDOWN, this.openMarkDown); - }, 1); }); const url = `${siteRoot}library/${repoID}/${encodeURIComponent(repoInfo.repo_name)}/?view=${encodeURIComponent(viewId)}`; window.history.pushState({ url: url, path: '' }, '', url); diff --git a/frontend/src/tag/hooks/tag-view.js b/frontend/src/tag/hooks/tag-view.js index aba5b2ca2f..f658f9e4d0 100644 --- a/frontend/src/tag/hooks/tag-view.js +++ b/frontend/src/tag/hooks/tag-view.js @@ -45,6 +45,7 @@ export const TagViewProvider = ({ repoID, tagID, children, ...params }) => { tagFiles, repoID, tagID, + repoInfo: params.repoInfo, deleteFilesCallback: params.deleteFilesCallback, renameFileCallback: params.renameFileCallback, updateCurrentDirent: params.updateCurrentDirent, diff --git a/frontend/src/tag/tags-tree-view/index.js b/frontend/src/tag/tags-tree-view/index.js index 2f5f7d309c..6e974736df 100644 --- a/frontend/src/tag/tags-tree-view/index.js +++ b/frontend/src/tag/tags-tree-view/index.js @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { useTags } from '../hooks'; import Tag from './tag'; @@ -19,8 +19,7 @@ const updateFavicon = () => { const TagsTreeView = ({ userPerm, currentPath }) => { const originalTitle = useRef(''); - // const {} = { } - const { tagsData, selectTag, deleteTags, duplicateTag, updateTag } = useTags(); + const { tagsData, selectTag } = useTags(); const tags = useMemo(() => { if (!tagsData) return []; @@ -32,16 +31,6 @@ const TagsTreeView = ({ userPerm, currentPath }) => { return true; }, [userPerm]); - const deleteTag = useCallback((tagId, isSelected) => { - if (isSelected) { - const currentTagIndex = tagsData.row_ids.indexOf(tagId); - const lastTagId = tagsData.row_ids[currentTagIndex - 1]; - const lastTag = getRowById(tagsData, lastTagId); - selectTag(lastTag); - } - deleteTags([tagId]); - }, [tagsData, deleteTags, selectTag]); - useEffect(() => { originalTitle.current = document.title; }, []); @@ -105,13 +94,8 @@ const TagsTreeView = ({ userPerm, currentPath }) => { selectTag(tag, isSelected)} - onDelete={() => deleteTag(id, isSelected)} - onCopy={() => duplicateTag(id)} - onUpdateTag={updateTag} /> ); })} diff --git a/frontend/src/tag/tags-tree-view/tag/index.css b/frontend/src/tag/tags-tree-view/tag/index.css index 4e8773c22f..e5a41875f3 100644 --- a/frontend/src/tag/tags-tree-view/tag/index.css +++ b/frontend/src/tag/tags-tree-view/tag/index.css @@ -1,8 +1,8 @@ .tag-tree-node .tag-tree-node-color { - height: 12px; - width: 12px; + height: 10px; + width: 10px; border-radius: 50%; - transform: translateY(2px); + transform: translateY(3px); } .tag-tree-node .tag-tree-node-text { @@ -14,11 +14,22 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex: 1; } .tag-tree-node .tag-tree-node-text .tag-tree-node-count { color: #666; font-size: 14px; margin-left: 8px; - margin-right: 8px; + padding-right: 14px; + min-width: 36px; + text-align: right; +} + +.metadata-tree-view .tag-tree-node .tag-tree-node-text { + width: calc(100%); +} + +.metadata-tree-view .tag-tree-node .right-icon:hover { + background-color: unset; } diff --git a/frontend/src/tag/tags-tree-view/tag/index.js b/frontend/src/tag/tags-tree-view/tag/index.js index 27bf8b21df..04d024ce67 100644 --- a/frontend/src/tag/tags-tree-view/tag/index.js +++ b/frontend/src/tag/tags-tree-view/tag/index.js @@ -1,227 +1,51 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { Input } from 'reactstrap'; -import { gettext } from '../../../utils/constants'; -import ItemDropdownMenu from '../../../components/dropdown-menu/item-dropdown-menu'; -import { isMobile } from '../../../utils/utils'; -import { getTagColor, getTagName, getTagId, getTagFilesCount, isValidTagName } from '../../utils'; -import { isEnter } from '../../../metadata/utils/hotkey'; -import toaster from '../../../components/toast'; -import { PRIVATE_COLUMN_KEY } from '../../constants'; +import { getTagColor, getTagName, getTagFilesCount } from '../../utils'; import './index.css'; -const Tag = ({ - userPerm, - isSelected, - tag, - tags, - onClick, - onDelete, - onCopy, - onUpdateTag, -}) => { +const Tag = ({ isSelected, tag, onClick }) => { const tagName = useMemo(() => getTagName(tag), [tag]); const tagColor = useMemo(() => getTagColor(tag), [tag]); - const tagId = useMemo(() => getTagId(tag), [tag]); const tagCount = useMemo(() => getTagFilesCount(tag), [tag]); const [highlight, setHighlight] = useState(false); - const [freeze, setFreeze] = useState(false); - const [isRenaming, setRenaming] = useState(false); - const [inputValue, setInputValue] = useState(''); - - const inputRef = useRef(null); - - const otherTagsName = useMemo(() => { - return tags.filter(tagItem => getTagId(tagItem) !== tagId).map(tagItem => getTagName(tagItem)); - }, [tags, tagId]); - - const canUpdate = useMemo(() => { - if (userPerm !== 'rw' && userPerm !== 'admin') return false; - return true; - }, [userPerm]); - - const operations = useMemo(() => { - if (!canUpdate) return []; - const value = [ - { key: 'rename', value: gettext('Rename') }, - { key: 'duplicate', value: gettext('Duplicate') }, - { key: 'delete', value: gettext('Delete') } - ]; - return value; - }, [canUpdate]); const onMouseEnter = useCallback(() => { - if (freeze) return; setHighlight(true); - }, [freeze]); + }, []); const onMouseOver = useCallback(() => { - if (freeze) return; setHighlight(true); - }, [freeze]); + }, []); const onMouseLeave = useCallback(() => { - if (freeze) return; - setHighlight(false); - }, [freeze]); - - const freezeItem = useCallback(() => { - setFreeze(true); - }, []); - - const unfreezeItem = useCallback(() => { - setFreeze(false); setHighlight(false); }, []); - const operationClick = useCallback((operationKey) => { - switch (operationKey) { - case 'rename': { - setInputValue(tagName); - setRenaming(true); - return; - } - case 'duplicate': { - onCopy(); - return; - } - case 'delete': { - onDelete(); - return; - } - default: { - return; - } - } - }, [tagName, onDelete, onCopy]); - - const renameTag = useCallback((name, failCallback) => { - onUpdateTag(tagId, { [PRIVATE_COLUMN_KEY.TAG_NAME]: name }, { - success_callback: () => { - setRenaming(false); - if (!isSelected) return; - document.title = `${name} - Seafile`; - }, - fail_callback: (error) => { - failCallback(error); - if (!isSelected) return; - document.title = `${tagName} - Seafile`; - } - }); - }, [onUpdateTag, isSelected, tagId, tagName]); - - const handleSubmit = useCallback((event) => { - event.preventDefault(); - event.stopPropagation(); - const { isValid, message } = isValidTagName(inputValue, otherTagsName); - if (!isValid) { - toaster.danger(message); - return; - } - if (message === tagName) { - setRenaming(false); - return; - } - renameTag(message); - }, [tagName, inputValue, otherTagsName, renameTag]); - - const onChange = useCallback((e) => { - setInputValue(e.target.value); - }, []); - - const onKeyDown = useCallback((event) => { - if (isEnter(event)) { - handleSubmit(event); - unfreezeItem(); - } - }, [handleSubmit, unfreezeItem]); - - const onInputClick = useCallback((event) => { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - }, []); - - useEffect(() => { - if (isRenaming && inputRef.current) { - inputRef.current.focus(); - inputRef.current.select(); - } - }, [isRenaming]); - - useEffect(() => { - const handleClickOutside = (event) => { - if (inputRef.current && !inputRef.current.contains(event.target)) { - handleSubmit(event); - } - }; - - if (isRenaming) { - document.addEventListener('mousedown', handleClickOutside); - } else { - document.removeEventListener('mousedown', handleClickOutside); - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [isRenaming, handleSubmit]); - return ( - <> -
onClick(tag)} - > -
-
- {isRenaming ? ( - setRenaming(false)} - onClick={onInputClick} - onKeyDown={onKeyDown} - /> - ) : ( - <>{tagName} - )} -
-
{` (${tagCount})`}
-
-
-
-
-
-
-
- {highlight && operations.length > 0 && ( - operations} - onMenuItemClick={operationClick} - menuStyle={isMobile ? { zIndex: 1050 } : {}} - /> - )} +
onClick(tag)} + > +
+
{tagName}
+
{tagCount}
+
+
+
+
- +
); }; Tag.propTypes = { - canDelete: PropTypes.bool, isSelected: PropTypes.bool, tag: PropTypes.object, onClick: PropTypes.func, diff --git a/frontend/src/tag/views/tag-files/index.css b/frontend/src/tag/views/tag-files/index.css index e69de29bb2..169670e511 100644 --- a/frontend/src/tag/views/tag-files/index.css +++ b/frontend/src/tag/views/tag-files/index.css @@ -0,0 +1,3 @@ +.sf-metadata-tag-files-wrapper .sf-metadata-tags-main { + overflow: auto !important; +} diff --git a/frontend/src/tag/views/tag-files/index.js b/frontend/src/tag/views/tag-files/index.js index 2aecc710fd..bc2cc1401c 100644 --- a/frontend/src/tag/views/tag-files/index.js +++ b/frontend/src/tag/views/tag-files/index.js @@ -1,15 +1,21 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useState, useRef, useEffect } from 'react'; import { useTagView } from '../../hooks'; import { gettext } from '../../../utils/constants'; import TagFile from './tag-file'; import { getRecordIdFromRecord } from '../../../metadata/utils/cell'; import EmptyTip from '../../../components/empty-tip'; +import ImagePreviewer from '../../../metadata/components/cell-formatter/image-previewer'; import './index.css'; const TagFiles = () => { - const { tagFiles, repoID } = useTagView(); + const { tagFiles, repoID, repoInfo } = useTagView(); const [selectedFiles, setSelectedFiles] = useState(null); + const [isImagePreviewerVisible, setImagePreviewerVisible] = useState(false); + const [containerWidth, setContainerWidth] = useState(0); + + const currentImageRef = useRef(null); + const containerRef = useRef(null); const onMouseDown = useCallback((event) => { if (event.button === 2) { @@ -45,6 +51,31 @@ const TagFiles = () => { } }, [selectedFiles]); + const openImagePreview = useCallback((record) => { + currentImageRef.current = record; + setImagePreviewerVisible(true); + }, []); + + const closeImagePreviewer = useCallback(() => { + currentImageRef.current = null; + setImagePreviewerVisible(false); + }, []); + + useEffect(() => { + const container = containerRef.current; + const handleResize = () => { + if (!container) return; + // 32: container padding left + container padding right + setContainerWidth(container.offsetWidth - 32); + }; + const resizeObserver = new ResizeObserver(handleResize); + container && resizeObserver.observe(container); + + return () => { + container && resizeObserver.unobserve(container); + }; + }, []); + if (tagFiles.rows.length === 0) { return (); } @@ -52,43 +83,55 @@ const TagFiles = () => { const isSelectedAll = selectedFiles && selectedFiles.length === tagFiles.rows.length; return ( -
-
+ {/* icon */}{/* star */}{gettext('Name')} {sortByName && sortIcon}{/* tag */}{/* operation */}{gettext('Size')} {sortBySize && sortIcon}{gettext('Last Update')} {sortByTime && sortIcon}{/* star */}{/* icon */}{gettext('Name')} {sortByName && sortIcon}{/* tag */}{/* operation */}{gettext('Size')} {sortBySize && sortIcon}{gettext('Last Update')} {sortByTime && sortIcon}
- - - - - - - - - - - - - {tagFiles.rows.map(file => { - const fileId = getRecordIdFromRecord(file); - return ( - ); - })} - -
- - {/* icon */}{gettext('Name')}{/* operation */}{/* tag */}{gettext('Size')}{gettext('Last Update')}
-
+ <> +
+ + + + + + + + + + + + + + {tagFiles.rows.map(file => { + const fileId = getRecordIdFromRecord(file); + return ( + ); + })} + +
+ + {/* icon */}{gettext('Name')}{/* tag */}{/* operation */}{gettext('Size')}{gettext('Last Update')}
+
+ {isImagePreviewerVisible && ( + + )} + ); }; diff --git a/frontend/src/tag/views/tag-files/tag-file/index.css b/frontend/src/tag/views/tag-files/tag-file/index.css index 083d3737e0..f9dae450dc 100644 --- a/frontend/src/tag/views/tag-files/tag-file/index.css +++ b/frontend/src/tag/views/tag-files/tag-file/index.css @@ -6,3 +6,7 @@ .tag-list-title .sf-metadata-tags-formatter .sf-metadata-tag-formatter:last-child { margin-right: 0; } + +.sf-metadata-tags-main .table-container td.name a { + word-break: break-word; +} diff --git a/frontend/src/tag/views/tag-files/tag-file/index.js b/frontend/src/tag/views/tag-files/tag-file/index.js index ca48e7295e..7644c422fb 100644 --- a/frontend/src/tag/views/tag-files/tag-file/index.js +++ b/frontend/src/tag/views/tag-files/tag-file/index.js @@ -1,20 +1,21 @@ import React, { useCallback, useMemo, useState } from 'react'; - +import PropTypes from 'prop-types'; import classnames from 'classnames'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import { gettext, siteRoot, thumbnailDefaultSize } from '../../../../utils/constants'; import { getParentDirFromRecord, getRecordIdFromRecord, getFileNameFromRecord, getFileSizedFromRecord, - getFileMTimeFromRecord, getTagsFromRecord, + getFileMTimeFromRecord, getTagsFromRecord, getFilePathByRecord, } from '../../../../metadata/utils/cell'; import { Utils } from '../../../../utils/utils'; import FileTagsFormatter from '../../../../metadata/components/cell-formatter/file-tags-formatter'; +import { openFile } from '../../../../metadata/utils/open-file'; import './index.css'; dayjs.extend(relativeTime); -const TagFile = ({ isSelected, repoID, file, onSelectFile }) => { +const TagFile = ({ isSelected, repoID, file, onSelectFile, openImagePreview }) => { const [highlight, setHighlight] = useState(false); const [isIconLoadError, setIconLoadError] = useState(false); @@ -34,6 +35,7 @@ const TagFile = ({ isSelected, repoID, file, onSelectFile }) => { const mtimeTip = useMemo(() => mtime ? dayjs(mtime).format('dddd, MMMM D, YYYY h:mm:ss A') : '', [mtime]); const mtimeRelative = useMemo(() => mtime ? dayjs(mtime).fromNow() : '', [mtime]); + const path = useMemo(() => getFilePathByRecord(repoID, file), [repoID, file]); const displayIcons = useMemo(() => { const defaultIconUrl = Utils.getFileIconUrl(name); @@ -67,6 +69,13 @@ const TagFile = ({ isSelected, repoID, file, onSelectFile }) => { setIconLoadError(true); }, []); + const handelClickFileName = useCallback((event) => { + event.preventDefault(); + openFile(repoID, file, () => { + openImagePreview(file); + }); + }, [repoID, file, openImagePreview]); + return ( { onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} > - + {}} checked={isSelected} aria-label={isSelected ? gettext('Unselect this item') : gettext('Select this item')} /> - +
- - {name} + + {name} - + {size || ''} {mtimeRelative} @@ -105,4 +115,12 @@ const TagFile = ({ isSelected, repoID, file, onSelectFile }) => { }; +TagFile.propTypes = { + isSelected: PropTypes.bool, + repoID: PropTypes.string, + file: PropTypes.object, + onSelectFile: PropTypes.func, + openImagePreview: PropTypes.func, +}; + export default TagFile; diff --git a/frontend/src/tag/views/tags-management/index.js b/frontend/src/tag/views/tags-management/index.js index 47f13dbbcb..25e5c816b1 100644 --- a/frontend/src/tag/views/tags-management/index.js +++ b/frontend/src/tag/views/tags-management/index.js @@ -40,7 +40,7 @@ const TagsManagement = () => { if (isLoading) return (); return ( <> -
+
@@ -48,7 +48,7 @@ const TagsManagement = () => {
{context.canAddTag() && ( )}
diff --git a/frontend/src/tag/views/tags-management/main/tag/index.css b/frontend/src/tag/views/tags-management/main/tag/index.css index 2ae0cb9075..cb1ff373fd 100644 --- a/frontend/src/tag/views/tags-management/main/tag/index.css +++ b/frontend/src/tag/views/tags-management/main/tag/index.css @@ -18,12 +18,10 @@ .sf-metadata-tags-table-cell-tag .sf-metadata-tag-color { display: inline-block; - height: 12px; - width: 12px; + height: 10px; + width: 10px; border-radius: 50%; margin-right: 8px; - position: relative; - top: 1px; } .sf-metadata-tags-table-row:hover .sf-metadata-tags-table-cell .sf-metadata-tags-table-cell-action { diff --git a/frontend/src/tag/views/view.js b/frontend/src/tag/views/view.js index 2591acb398..ad4528ec32 100644 --- a/frontend/src/tag/views/view.js +++ b/frontend/src/tag/views/view.js @@ -13,7 +13,7 @@ const View = () => { if (isLoading) return (); return ( -
+
{errorMessage ?
{errorMessage}
: renderTagView()}