diff --git a/frontend/src/components/dirent-grid-view/dirent-grid-item.js b/frontend/src/components/dirent-grid-view/dirent-grid-item.js index 146197b661..89c64cba68 100644 --- a/frontend/src/components/dirent-grid-view/dirent-grid-item.js +++ b/frontend/src/components/dirent-grid-view/dirent-grid-item.js @@ -1,9 +1,12 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import MD5 from 'MD5'; import { UncontrolledTooltip } from 'reactstrap'; -import { gettext, siteRoot, mediaUrl } from '../../utils/constants'; +import { gettext, siteRoot, mediaUrl, enableVideoThumbnail, enablePDFThumbnail } from '../../utils/constants'; import { Utils } from '../../utils/utils'; +import { imageThumbnailCenter, videoThumbnailCenter } from '../../utils/thumbnail-center'; +import Dirent from '../../models/dirent'; const propTypes = { path: PropTypes.string.isRequired, @@ -13,26 +16,24 @@ const propTypes = { showImagePopup: PropTypes.func.isRequired, onGridItemContextMenu: PropTypes.func.isRequired, onGridItemClick: PropTypes.func.isRequired, - activeDirent: PropTypes.object, onGridItemMouseDown: PropTypes.func, currentRepoInfo: PropTypes.object, onItemMove: PropTypes.func.isRequired, onItemsMove: PropTypes.func.isRequired, selectedDirentList: PropTypes.array.isRequired, + repoEncrypted: PropTypes.bool.isRequired, }; class DirentGridItem extends React.Component { constructor(props) { super(props); + let dirent = props.dirent; this.state = { + dirent, isGridDropTipShow: false, }; - - const { dirent } = this.props; const { isCustomPermission, customPermission } = Utils.getUserPermission(dirent.permission); - this.isCustomPermission = isCustomPermission; - this.customPermission = customPermission; this.canPreview = true; this.canDrag = dirent.permission === 'rw'; if (isCustomPermission) { @@ -42,19 +43,64 @@ class DirentGridItem extends React.Component { } this.clickTimeout = null; + this.isGeneratingThumbnail = false; + this.thumbnailCenter = null; + } + + checkGenerateThumbnail = (dirent) => { + if (this.props.repoEncrypted || dirent.encoded_thumbnail_src) { + return false; + } + if (enableVideoThumbnail && Utils.videoCheck(dirent.name)) { + this.thumbnailCenter = videoThumbnailCenter; + return true; + } + if (Utils.imageCheck(dirent.name) || (enablePDFThumbnail && Utils.pdfCheck(dirent.name))) { + this.thumbnailCenter = imageThumbnailCenter; + return true; + } + return false; + }; + + componentDidMount() { + const { repoID, path } = this.props; + const { dirent } = this.state; + if (this.checkGenerateThumbnail(dirent)) { + this.isGeneratingThumbnail = true; + this.thumbnailCenter.createThumbnail({ + repoID, + path: [path, dirent.name].join('/'), + callback: this.updateDirentThumbnail, + }); + } } componentWillUnmount() { if (this.clickTimeout) { clearTimeout(this.clickTimeout); } + if (this.isGeneratingThumbnail) { + const { dirent } = this.state; + const { repoID, path } = this.props; + this.thumbnailCenter.cancelThumbnail({ + repoID, + path: [path, dirent.name].join('/'), + }); + this.thumbnailCenter = null; + } + this.setState = () => {}; } + updateDirentThumbnail = (encoded_thumbnail_src) => { + this.isGeneratingThumbnail = false; + this.setState({ dirent: new Dirent(Object.assign({}, this.state.dirent, { encoded_thumbnail_src })) }); + }; + onItemClick = (e) => { e.preventDefault(); e.stopPropagation(); - const { dirent } = this.props; + const { dirent } = this.state; if (this.clickTimeout) { clearTimeout(this.clickTimeout); @@ -91,7 +137,7 @@ class DirentGridItem extends React.Component { onItemLinkClick = (e) => { e.preventDefault(); - const dirent = this.props.dirent; + const { dirent } = this.state; if (dirent.isDir()) { this.props.onItemClick(dirent); @@ -115,7 +161,7 @@ class DirentGridItem extends React.Component { const dragStartItemsData = selectedDirentList.length > 0 ? selectedDirentList.map(dirent => ({ nodeDirent: dirent, nodeParentPath: path - })) : { nodeDirent: this.props.dirent, nodeParentPath: this.props.path }; + })) : { nodeDirent: this.state.dirent, nodeParentPath: this.props.path }; const serializedData = JSON.stringify(dragStartItemsData); @@ -127,7 +173,7 @@ class DirentGridItem extends React.Component { if (Utils.isIEBrowser() || !this.canDrag) { return false; } - if (this.props.dirent.type === 'dir') { + if (this.state.dirent.type === 'dir') { this.setState({ isGridDropTipShow: true }); } }; @@ -156,13 +202,13 @@ class DirentGridItem extends React.Component { return; } - let selectedPath = Utils.joinPath(this.props.path, this.props.dirent.name); + let selectedPath = Utils.joinPath(this.props.path, this.state.dirent.name); let dragStartItemsData = e.dataTransfer.getData('application/drag-item-info'); dragStartItemsData = JSON.parse(dragStartItemsData); if (!Array.isArray(dragStartItemsData)) { let { nodeDirent, nodeParentPath } = dragStartItemsData; - let dropItemData = this.props.dirent; + let dropItemData = this.state.dirent; if (nodeDirent.name === dropItemData.name) { return; } @@ -172,7 +218,7 @@ class DirentGridItem extends React.Component { this.props.onItemMove(this.props.currentRepoInfo, nodeDirent, selectedPath, nodeParentPath); } else { // if current dirent is a dir and selected list include it, return - if (dragStartItemsData.some(item => item.nodeDirent.name === this.props.dirent.name)) { + if (dragStartItemsData.some(item => item.nodeDirent.name === this.state.dirent.name)) { return; } this.props.onItemsMove(this.props.currentRepoInfo, selectedPath); @@ -184,7 +230,7 @@ class DirentGridItem extends React.Component { }; onGridItemContextMenu = (event) => { - this.props.onGridItemContextMenu(event, this.props.dirent); + this.props.onGridItemContextMenu(event, this.state.dirent); }; getTextRenderWidth = (text, font) => { @@ -231,35 +277,32 @@ class DirentGridItem extends React.Component { return showName; }; - render() { - let { dirent, path, repoID } = this.props; + getDirentLink = (dirent) => { + let { path, repoID, currentRepoInfo } = this.props; let direntPath = Utils.joinPath(path, dirent.name); - let iconUrl = Utils.getDirentIcon(dirent, true); + let link = ''; + if (dirent.type === 'dir') { + if (currentRepoInfo) { + link = siteRoot + 'library/' + repoID + '/' + currentRepoInfo.repo_name + Utils.encodePath(direntPath); + } + } else { + link = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); + if (dirent.is_sdoc_revision && dirent.revision_id) { + link = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/'; + } + } + return link; + }; + render() { + let { dirent } = this.state; + let { is_freezed, is_locked, lock_owner_name, file_tags } = dirent; let toolTipID = ''; let tagTitle = ''; - if (dirent.file_tags && dirent.file_tags.length > 0) { + if (file_tags && file_tags.length > 0) { toolTipID = MD5(dirent.name).slice(0, 7); - tagTitle = dirent.file_tags.map(item => item.name).join(' '); + tagTitle = file_tags.map(item => item.name).join(' '); } - - let dirHref = ''; - if (this.props.currentRepoInfo) { - dirHref = siteRoot + 'library/' + repoID + '/' + this.props.currentRepoInfo.repo_name + Utils.encodePath(direntPath); - } - let fileHref = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); - if (dirent.is_sdoc_revision && dirent.revision_id) { - fileHref = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/'; - } - - let gridClass = 'grid-file-img-link cursor-pointer'; - gridClass += this.state.isGridDropTipShow ? ' grid-drop-show' : ' '; - - let lockedInfo = dirent.is_freezed ? gettext('Frozen by {name}') : gettext('locked by {name}'); - lockedInfo = lockedInfo.replace('{name}', dirent.lock_owner_name); - - const lockedImageUrl = `${mediaUrl}img/file-${dirent.is_freezed ? 'freezed-32.svg' : 'locked-32.png'}`; - const lockedMessage = dirent.is_freezed ? gettext('freezed') : gettext('locked'); const showName = this.getRenderedText(dirent); return ( @@ -268,7 +311,7 @@ class DirentGridItem extends React.Component { onContextMenu={this.onGridItemContextMenu} onMouseDown={this.onGridItemMouseDown}>
{(this.canPreview && dirent.encoded_thumbnail_src) ? : - + + } + {is_locked && + {is_freezed } - {dirent.is_locked && {lockedMessage}}
- {(dirent.type !== 'dir' && dirent.file_tags && dirent.file_tags.length > 0) && ( + {(dirent.type !== 'dir' && file_tags && file_tags.length > 0) && (
- {dirent.file_tags.map((fileTag, index) => { - let length = dirent.file_tags.length; + {file_tags.map((fileTag, index) => { + let length = file_tags.length; return ( ); @@ -308,7 +358,7 @@ class DirentGridItem extends React.Component { : {showName} diff --git a/frontend/src/components/dirent-grid-view/dirent-grid-view.js b/frontend/src/components/dirent-grid-view/dirent-grid-view.js index f03c9de3bb..da0102e435 100644 --- a/frontend/src/components/dirent-grid-view/dirent-grid-view.js +++ b/frontend/src/components/dirent-grid-view/dirent-grid-view.js @@ -852,8 +852,8 @@ class DirentGridView extends React.Component { onItemsMove={this.props.onItemsMove} onGridItemMouseDown={this.onGridItemMouseDown} onGridItemClick={this.onGridItemClick} - activeDirent={this.state.activeDirent} selectedDirentList={selectedDirentList} + repoEncrypted={this.props.currentRepoInfo.encrypted} /> ); })} 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 0d9f145dcf..f13af93ad6 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-item.js +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -1,13 +1,15 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import MediaQuery from 'react-responsive'; import { v4 as uuidv4 } from 'uuid'; import moment from 'moment'; import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap'; -import { gettext, siteRoot, mediaUrl, username, useGoFileserver, fileServerRoot } from '../../utils/constants'; +import { gettext, siteRoot, mediaUrl, username, useGoFileserver, fileServerRoot, enableVideoThumbnail, enablePDFThumbnail } from '../../utils/constants'; import { Utils } from '../../utils/utils'; import { seafileAPI } from '../../utils/seafile-api'; import URLDecorator from '../../utils/url-decorator'; +import { imageThumbnailCenter, videoThumbnailCenter } from '../../utils/thumbnail-center'; import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu'; import Rename from '../rename'; import ModalPortal from '../modal-portal'; @@ -21,6 +23,7 @@ import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-di import FileAccessLog from '../dialog/file-access-log'; import toaster from '../toast'; import FileTag from './file-tag'; +import Dirent from '../../models/dirent'; import '../../css/dirent-list-item.css'; @@ -66,7 +69,7 @@ class DirentListItem extends React.Component { constructor(props) { super(props); - const { dirent } = this.props; + let { dirent } = this.props; const { isCustomPermission, customPermission } = Utils.getUserPermission(dirent.permission); this.isCustomPermission = isCustomPermission; this.customPermission = customPermission; @@ -79,6 +82,7 @@ class DirentListItem extends React.Component { } this.state = { + dirent, isOperationShow: false, highlight: false, isZipDialogOpen: false, @@ -96,6 +100,39 @@ class DirentListItem extends React.Component { isOpMenuOpen: false // for mobile }; this.tagListTitleID = `tag-list-title-${uuidv4()}`; + this.isGeneratingThumbnail = false; + this.thumbnailCenter = null; + } + + componentDidMount() { + const { repoID, path } = this.props; + const { dirent } = this.state; + if (this.checkGenerateThumbnail(dirent)) { + this.isGeneratingThumbnail = true; + this.thumbnailCenter.createThumbnail({ + repoID, + path: [path, dirent.name].join('/'), + callback: this.updateDirentThumbnail, + }); + } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if (nextProps.dirent && this.state.dirent && nextProps.dirent.name !== this.state.dirent.name) { + this.setState({ + dirent: nextProps.dirent, + }, () => { + if (this.checkGenerateThumbnail(nextProps.dirent)) { + const { repoID, path } = nextProps; + this.isGeneratingThumbnail = true; + this.thumbnailCenter.createThumbnail({ + repoID, + path: [path, nextProps.dirent.name].join('/'), + callback: this.updateDirentThumbnail, + }); + } + }); + } } componentDidUpdate(prevProps) { @@ -109,6 +146,39 @@ class DirentListItem extends React.Component { } } + componentWillUnmount() { + if (this.isGeneratingThumbnail) { + const { dirent } = this.state; + const { repoID, path } = this.props; + this.thumbnailCenter.cancelThumbnail({ + repoID, + path: [path, dirent.name].join('/'), + }); + this.thumbnailCenter = null; + } + this.setState = () => {}; + } + + checkGenerateThumbnail = (dirent) => { + if (this.props.repoEncrypted || dirent.encoded_thumbnail_src) { + return false; + } + if (enableVideoThumbnail && Utils.videoCheck(dirent.name)) { + this.thumbnailCenter = videoThumbnailCenter; + return true; + } + if (Utils.imageCheck(dirent.name) || (enablePDFThumbnail && Utils.pdfCheck(dirent.name))) { + this.thumbnailCenter = imageThumbnailCenter; + return true; + } + return false; + }; + + updateDirentThumbnail = (encoded_thumbnail_src) => { + this.isGeneratingThumbnail = false; + this.setState({ dirent: new Dirent(Object.assign({}, this.state.dirent, { encoded_thumbnail_src })) }); + }; + toggleOpMenu = () => { this.setState({ isOpMenuOpen: !this.state.isOpMenuOpen @@ -160,24 +230,24 @@ class DirentListItem extends React.Component { // buiness handler onItemSelected = () => { - this.props.onItemSelected(this.props.dirent); + this.props.onItemSelected(this.state.dirent); }; onItemStarred = (e) => { - let dirent = this.props.dirent; + let dirent = this.state.dirent; let repoID = this.props.repoID; let filePath = this.getDirentPath(dirent); if (dirent.starred) { seafileAPI.unstarItem(repoID, filePath).then(() => { - this.props.updateDirent(this.props.dirent, 'starred', false); + this.props.updateDirent(this.state.dirent, 'starred', false); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); } else { seafileAPI.starItem(repoID, filePath).then(() => { - this.props.updateDirent(this.props.dirent, 'starred', true); + this.props.updateDirent(this.state.dirent, 'starred', true); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -190,13 +260,13 @@ class DirentListItem extends React.Component { // '' is clicked e.stopPropagation(); if (e.target.tagName == 'TD') { - this.props.onDirentClick(this.props.dirent, e); + this.props.onDirentClick(this.state.dirent, e); } }; onItemClick = (e) => { e.preventDefault(); - const dirent = this.props.dirent; + const dirent = this.state.dirent; if (this.state.isRenameing) { return; } @@ -220,7 +290,7 @@ class DirentListItem extends React.Component { onItemDelete = (e) => { e.preventDefault(); e.nativeEvent.stopImmediatePropagation(); // for document event - this.props.onItemDelete(this.props.dirent); + this.props.onItemDelete(this.state.dirent); }; onItemShare = (e) => { @@ -232,7 +302,7 @@ class DirentListItem extends React.Component { exportDocx = () => { const serviceUrl = window.app.config.serviceURL; let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + let filePath = this.getDirentPath(this.state.dirent); let exportToDocxUrl = serviceUrl + '/repo/sdoc_export_to_docx/' + repoID + '/?file_path=' + filePath; window.location.href = exportToDocxUrl; }; @@ -240,7 +310,7 @@ class DirentListItem extends React.Component { exportSdoc = () => { const serviceUrl = window.app.config.serviceURL; let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + let filePath = this.getDirentPath(this.state.dirent); let exportToSdocUrl = serviceUrl + '/lib/' + repoID + '/file/' + filePath + '?dl=1'; window.location.href = exportToSdocUrl; }; @@ -314,7 +384,7 @@ class DirentListItem extends React.Component { this.toggleFileAccessLogDialog(); break; case 'Properties': - this.props.onDirentClick(this.props.dirent); + this.props.onDirentClick(this.state.dirent); this.props.showDirentDetail('info'); break; case 'Open via Client': @@ -331,7 +401,7 @@ class DirentListItem extends React.Component { onItemConvert = (e, dstType) => { e.preventDefault(); e.nativeEvent.stopImmediatePropagation(); // for document event - this.props.onItemConvert(this.props.dirent, dstType); + this.props.onItemConvert(this.state.dirent, dstType); }; onEditFileTagToggle = () => { @@ -341,12 +411,12 @@ class DirentListItem extends React.Component { }; onFileTagChanged = () => { - let direntPath = this.getDirentPath(this.props.dirent); - this.props.onFileTagChanged(this.props.dirent, direntPath); + let direntPath = this.getDirentPath(this.state.dirent); + this.props.onFileTagChanged(this.state.dirent, direntPath); }; onItemRenameToggle = () => { - this.props.onItemRenameToggle(this.props.dirent); + this.props.onItemRenameToggle(this.state.dirent); this.setState({ isOperationShow: false, isRenameing: true, @@ -355,7 +425,7 @@ class DirentListItem extends React.Component { }; onRenameConfirm = (newName) => { - this.props.onItemRename(this.props.dirent, newName); + this.props.onItemRename(this.state.dirent, newName); this.onRenameCancel(); }; @@ -381,12 +451,13 @@ class DirentListItem extends React.Component { onLockItem = () => { let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + let dirent = this.state.dirent; + let filePath = this.getDirentPath(dirent); seafileAPI.lockfile(repoID, filePath).then(() => { - this.props.updateDirent(this.props.dirent, 'is_locked', true); - this.props.updateDirent(this.props.dirent, 'locked_by_me', true); + this.props.updateDirent(dirent, 'is_locked', true); + this.props.updateDirent(dirent, 'locked_by_me', true); let lockName = username.split('@'); - this.props.updateDirent(this.props.dirent, 'lock_owner_name', lockName[0]); + this.props.updateDirent(dirent, 'lock_owner_name', lockName[0]); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -395,13 +466,14 @@ class DirentListItem extends React.Component { onFreezeDocument = () => { let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + let dirent = this.state.dirent; + let filePath = this.getDirentPath(dirent); seafileAPI.lockfile(repoID, filePath, -1).then(() => { - this.props.updateDirent(this.props.dirent, 'is_freezed', true); - this.props.updateDirent(this.props.dirent, 'is_locked', true); - this.props.updateDirent(this.props.dirent, 'locked_by_me', true); + this.props.updateDirent(dirent, 'is_freezed', true); + this.props.updateDirent(dirent, 'is_locked', true); + this.props.updateDirent(dirent, 'locked_by_me', true); let lockName = username.split('@'); - this.props.updateDirent(this.props.dirent, 'lock_owner_name', lockName[0]); + this.props.updateDirent(dirent, 'lock_owner_name', lockName[0]); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -410,11 +482,12 @@ class DirentListItem extends React.Component { onUnlockItem = () => { let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + let dirent = this.state.dirent; + let filePath = this.getDirentPath(dirent); seafileAPI.unlockfile(repoID, filePath).then(() => { - this.props.updateDirent(this.props.dirent, 'is_locked', false); - this.props.updateDirent(this.props.dirent, 'locked_by_me', false); - this.props.updateDirent(this.props.dirent, 'lock_owner_name', ''); + this.props.updateDirent(dirent, 'is_locked', false); + this.props.updateDirent(dirent, 'locked_by_me', false); + this.props.updateDirent(dirent, 'lock_owner_name', ''); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -422,9 +495,8 @@ class DirentListItem extends React.Component { }; onHistory = () => { - let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); - let url = URLDecorator.getUrl({ type: 'file_revisions', repoID: repoID, filePath: filePath }); + let filePath = this.getDirentPath(this.state.dirent); + let url = URLDecorator.getUrl({ type: 'file_revisions', repoID: this.props.repoID, filePath: filePath }); location.href = url; }; @@ -436,14 +508,14 @@ class DirentListItem extends React.Component { onOpenViaClient = () => { let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + let filePath = this.getDirentPath(this.state.dirent); let url = URLDecorator.getUrl({ type: 'open_via_client', repoID: repoID, filePath: filePath }); location.href = url; }; onConvertWithONLYOFFICE = () => { let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + let filePath = this.getDirentPath(this.state.dirent); seafileAPI.onlyofficeConvert(repoID, filePath).then(res => { this.props.loadDirentList(res.data.parent_dir); }).catch(error => { @@ -455,7 +527,7 @@ class DirentListItem extends React.Component { onItemDownload = (e) => { e.preventDefault(); e.nativeEvent.stopImmediatePropagation(); - let dirent = this.props.dirent; + let dirent = this.state.dirent; let repoID = this.props.repoID; let direntPath = this.getDirentPath(dirent); if (dirent.type === 'dir') { @@ -464,7 +536,7 @@ class DirentListItem extends React.Component { isZipDialogOpen: true }); } else { - seafileAPI.zipDownload(repoID, this.props.path, this.props.dirent.name).then((res) => { + seafileAPI.zipDownload(repoID, this.props.path, this.state.dirent.name).then((res) => { const zipToken = res.data['zip_token']; location.href = `${fileServerRoot}zip/${zipToken}`; }).catch((error) => { @@ -507,7 +579,7 @@ class DirentListItem extends React.Component { } e.dataTransfer.effectAllowed = 'move'; let { selectedDirentList } = this.props; - if (selectedDirentList.length > 0 && selectedDirentList.includes(this.props.dirent)) { // drag items and selectedDirentList include item + if (selectedDirentList.length > 0 && selectedDirentList.includes(this.state.dirent)) { // drag items and selectedDirentList include item this.props.onShowDirentsDraggablePreview(); e.dataTransfer.setDragImage(this.refs.empty_content, 0, 0); // Show an empty content let selectedList = selectedDirentList.map(item => { @@ -524,8 +596,8 @@ class DirentListItem extends React.Component { e.dataTransfer.setDragImage(this.refs.drag_icon, 15, 15); } - let nodeRootPath = this.getDirentPath(this.props.dirent); - let dragStartItemData = { nodeDirent: this.props.dirent, nodeParentPath: this.props.path, nodeRootPath: nodeRootPath }; + let nodeRootPath = this.getDirentPath(this.state.dirent); + let dragStartItemData = { nodeDirent: this.state.dirent, nodeParentPath: this.props.path, nodeRootPath: nodeRootPath }; dragStartItemData = JSON.stringify(dragStartItemData); e.dataTransfer.setData('application/drag-item-info', dragStartItemData); @@ -535,7 +607,7 @@ class DirentListItem extends React.Component { if (Utils.isIEBrowser() || !this.state.canDrag) { return false; } - if (this.props.dirent.type === 'dir') { + if (this.state.dirent.type === 'dir') { e.stopPropagation(); this.setState({ isDropTipshow: true }); } @@ -557,7 +629,7 @@ class DirentListItem extends React.Component { return false; } - if (this.props.dirent.type === 'dir') { + if (this.state.dirent.type === 'dir') { e.stopPropagation(); } this.setState({ isDropTipshow: false }); @@ -571,7 +643,7 @@ class DirentListItem extends React.Component { if (e.dataTransfer.files.length) { // uploaded files return; } - if (this.props.dirent.type === 'dir') { + if (this.state.dirent.type === 'dir') { e.stopPropagation(); } else { return; @@ -583,7 +655,7 @@ class DirentListItem extends React.Component { return draggedItem.nodeRootPath; }); - let selectedPath = Utils.joinPath(this.props.path, this.props.dirent.name); + let selectedPath = Utils.joinPath(this.props.path, this.state.dirent.name); if (direntPaths.some(direntPath => { return direntPath === selectedPath;})) { // eg; A/B, A/C --> A/B return; @@ -594,7 +666,7 @@ class DirentListItem extends React.Component { } let { nodeDirent, nodeParentPath, nodeRootPath } = dragStartItemData; - let dropItemData = this.props.dirent; + let dropItemData = this.state.dirent; if (nodeDirent.name === dropItemData.name) { return; @@ -609,7 +681,7 @@ class DirentListItem extends React.Component { } } - let selectedPath = Utils.joinPath(this.props.path, this.props.dirent.name); + let selectedPath = Utils.joinPath(this.props.path, this.state.dirent.name); this.onItemMove(this.props.currentRepoInfo, nodeDirent, selectedPath, nodeParentPath); }; @@ -618,12 +690,27 @@ class DirentListItem extends React.Component { }; onItemContextMenu = (event) => { - let dirent = this.props.dirent; - this.props.onItemContextMenu(event, dirent); + this.props.onItemContextMenu(event, this.state.dirent); + }; + + getDirentHref = () => { + let { path, repoID } = this.props; + let dirent = this.state.dirent; + let direntPath = Utils.joinPath(path, dirent.name); + let dirHref = ''; + if (this.props.currentRepoInfo) { + dirHref = siteRoot + 'library/' + repoID + '/' + this.props.currentRepoInfo.repo_name + Utils.encodePath(direntPath); + } + let fileHref = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); + if (dirent.is_sdoc_revision && dirent.revision_id) { + fileHref = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/'; + } + return dirent.type === 'dir' ? dirHref : fileHref; }; renderItemOperation = () => { - let { dirent, currentRepoInfo, selectedDirentList } = this.props; + let { currentRepoInfo, selectedDirentList } = this.props; + let dirent = this.state.dirent; let canDownload = true; let canDelete = true; const { isCustomPermission, customPermission } = this; @@ -637,9 +724,9 @@ class DirentListItem extends React.Component { let showShareBtn = Utils.isHasPermissionToShare(currentRepoInfo, dirent.permission, dirent); return ( - + <> {selectedDirentList.length > 1 ? - + <> {this.state.isOperationShow && !dirent.isSelected &&
{(dirent.permission === 'rw' || dirent.permission === 'r' || (isCustomPermission && canDownload)) && ( @@ -652,7 +739,7 @@ class DirentListItem extends React.Component { )}
} - : - + : + <> {this.state.isOperationShow &&
{(dirent.permission === 'rw' || dirent.permission === 'r' || (isCustomPermission && canDownload)) && ( @@ -675,7 +762,7 @@ class DirentListItem extends React.Component { )}
} -
+ } - + ); }; render() { - let { path, dirent, repoID } = this.props; + let { path } = this.props; + let dirent = this.state.dirent; let direntPath = Utils.joinPath(path, dirent.name); - let dirHref = ''; - if (this.props.currentRepoInfo) { - dirHref = siteRoot + 'library/' + repoID + '/' + this.props.currentRepoInfo.repo_name + Utils.encodePath(direntPath); - } - let fileHref = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); - if (dirent.is_sdoc_revision && dirent.revision_id) { - fileHref = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/'; - } let iconUrl = Utils.getDirentIcon(dirent); - let isActive = dirent.isSelected; - let trClass = this.state.highlight ? 'tr-highlight ' : ''; - trClass += this.state.isDropTipshow ? 'tr-drop-effect' : ''; - trClass += isActive ? 'tr-active' : ''; + let isSelected = dirent.isSelected; let lockedInfo = dirent.is_freezed ? gettext('Frozen by {name}') : gettext('locked by {name}'); lockedInfo = lockedInfo.replace('{name}', dirent.lock_owner_name); @@ -718,10 +795,14 @@ class DirentListItem extends React.Component { const lockedMessage = dirent.is_freezed ? gettext('freezed') : gettext('locked'); return ( - + <> {isDesktop ? @@ -763,18 +844,25 @@ class DirentListItem extends React.Component { } {dirent.is_locked && {lockedMessage}} -
+
- {this.state.isRenameing && } + {this.state.isRenameing && + + } {!this.state.isRenameing && ( - + <> {(!dirent.isDir() && !this.canPreview) ? {dirent.name} : - {dirent.name} + {dirent.name} } - + )} @@ -787,12 +875,12 @@ class DirentListItem extends React.Component { })}
)} - {(dirent.type !== 'dir' && (!dirent.file_tags || dirent.file_tags.length == 0)) && ( + {(dirent.type !== 'dir' && (!dirent.file_tags || dirent.file_tags.length == 0)) &&
- )} + } {this.renderItemOperation()} - {dirent.size && dirent.size} + {dirent.size || ''} {dirent.mtime_relative} : @@ -807,14 +895,21 @@ class DirentListItem extends React.Component { - {this.state.isRenameing && } + {this.state.isRenameing && + + } {!this.state.isRenameing && ( - + <> {(!dirent.isDir() && !this.canPreview) ? {dirent.name} : - {dirent.name} + {dirent.name} } - + )}
{dirent.size && {dirent.size}} @@ -865,7 +960,7 @@ class DirentListItem extends React.Component { @@ -958,7 +1053,7 @@ class DirentListItem extends React.Component { /> } -
+ ); } } diff --git a/frontend/src/components/popover/list-tag-popover.js b/frontend/src/components/popover/list-tag-popover.js index 76c70145fc..77765e72a8 100644 --- a/frontend/src/components/popover/list-tag-popover.js +++ b/frontend/src/components/popover/list-tag-popover.js @@ -32,6 +32,10 @@ export default class ListTagPopover extends React.Component { this.loadTags(); } + componentWillUnmount() { + this.setState = () => {}; + } + loadTags = () => { seafileAPI.listRepoTags(this.props.repoID).then(res => { let repotagList = []; diff --git a/frontend/src/css/dirent-list-item.css b/frontend/src/css/dirent-list-item.css index e7f42dfce0..0f80228221 100644 --- a/frontend/src/css/dirent-list-item.css +++ b/frontend/src/css/dirent-list-item.css @@ -12,6 +12,12 @@ max-width: 24px; } +.dir-icon .empty-content { + position: absolute; + width: 1px; + height: 1px; +} + .dir-icon .locked { position: absolute; width: 1rem; diff --git a/frontend/src/models/dirent.js b/frontend/src/models/dirent.js index c15c3912a6..6d5d451808 100644 --- a/frontend/src/models/dirent.js +++ b/frontend/src/models/dirent.js @@ -20,8 +20,8 @@ class Dirent { this.has_been_shared_out = false; } if (json.type === 'file') { - this.size_original = json.size; - this.size = Utils.bytesToSize(json.size); + this.size_original = json.size_original || json.size; + this.size = (typeof json.size === 'number') ? Utils.bytesToSize(json.size) : json.size; this.is_locked = json.is_locked || false; this.is_freezed = json.is_freezed || false; this.lock_time = json.lock_time || ''; 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 363cba513a..1fce32cf9c 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; import MediaQuery from 'react-responsive'; import { Modal } from 'reactstrap'; import { navigate } from '@gatsbyjs/reach-router'; -import { gettext, siteRoot, username, enableVideoThumbnail, enablePDFThumbnail, thumbnailDefaultSize } from '../../utils/constants'; +import { gettext, siteRoot, username } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import collabServer from '../../utils/collab-server'; @@ -588,10 +588,6 @@ class LibContentView extends React.Component { isSessionExpired: false, }); - if (!this.state.repoEncrypted && direntList.length) { - this.getThumbnails(repoID, path, this.state.direntList); - } - if (this.state.currentRepoInfo.is_admin) { if (this.foldersSharedOut) { this.identifyFoldersSharedOut(); @@ -644,37 +640,6 @@ class LibContentView extends React.Component { this.setState({ itemsShowLength: 100 }); }; - shouldDisplayThumbnail = (item) => { - return ((Utils.imageCheck(item.name) || (enableVideoThumbnail && Utils.videoCheck(item.name)) || (enablePDFThumbnail && Utils.pdfCheck(item.name))) && !item.encoded_thumbnail_src); - }; - - getThumbnails = (repoID, path, direntList) => { - let items = direntList.filter((item) => this.shouldDisplayThumbnail(item)); - if (items.length == 0) { - return ; - } - const _this = this; - const len = items.length; - let getThumbnail = (i) => { - const curItem = items[i]; - const curItemPath = [path, curItem.name].join('/'); - seafileAPI.createThumbnail(repoID, curItemPath, thumbnailDefaultSize).then((res) => { - curItem.encoded_thumbnail_src = res.data.encoded_thumbnail_src; - }).catch((error) => { - // do nothing - }).then(() => { - if (i < len - 1) { - getThumbnail(++i); - } else { - _this.setState({ - direntList: direntList - }); - } - }); - }; - getThumbnail(0); - }; - updateMoveCopyTreeNode = (path) => { let repoID = this.props.repoID; diff --git a/frontend/src/utils/thumbnail-center.js b/frontend/src/utils/thumbnail-center.js new file mode 100644 index 0000000000..74b5c03149 --- /dev/null +++ b/frontend/src/utils/thumbnail-center.js @@ -0,0 +1,52 @@ +import { seafileAPI } from '../utils/seafile-api'; +import { thumbnailDefaultSize } from '../utils/constants'; + +class ThumbnailCenter { + + constructor() { + this.waitingQuery = []; + this.isStartQuery = false; + } + + createThumbnail = ({ repoID, path, callback }) => { + this.waitingQuery.push({ repoID, path, callback }); + if (!this.isStartQuery) { + this.startQuery(); + } + }; + + cancelThumbnail = ({ repoID, path }) => { + const index = this.waitingQuery.findIndex(q => (q.repoID === repoID && q.path === path)); + if (index > -1) { + this.waitingQuery.splice(index, 1); + } + if (this.waitingQuery.length === 0) { + this.isStartQuery = false; + } + }; + + startQuery = () => { + if (this.waitingQuery.length === 0) { + this.isStartQuery = false; + return; + } + this.isStartQuery = true; + let { repoID, path, callback } = this.waitingQuery[0]; + seafileAPI.createThumbnail(repoID, path, thumbnailDefaultSize).then((res) => { + callback && callback(res.data.encoded_thumbnail_src); + }).catch((error) => { + // eslint-disable-next-line no-console + console.log(error); + }).then(() => { + this.waitingQuery.shift(); + this.startQuery(); + }); + }; +} + +// server generates image and PDF thumbnails quickly, but generates video thumbnails slowly, so use two queues +const imageThumbnailCenter = new ThumbnailCenter(); + +const videoThumbnailCenter = new ThumbnailCenter(); + +export { imageThumbnailCenter, videoThumbnailCenter };