import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import MD5 from 'MD5'; import { createRoot } from 'react-dom/client'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, UncontrolledTooltip } from 'reactstrap'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import Account from './components/common/account'; import { useGoFileserver, fileServerRoot, gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle, thumbnailSizeForOriginal, thumbnailDefaultSize, thumbnailSizeForGrid } from './utils/constants'; import { Utils } from './utils/utils'; import { seafileAPI } from './utils/seafile-api'; import Loading from './components/loading'; import toaster from './components/toast'; import ModalPortal from './components/modal-portal'; import ZipDownloadDialog from './components/dialog/zip-download-dialog'; import ImageDialog from './components/dialog/image-dialog'; import FileUploader from './components/shared-link-file-uploader/file-uploader'; import SaveSharedDirDialog from './components/dialog/save-shared-dir-dialog'; import CopyMoveDirentProgressDialog from './components/dialog/copy-move-dirent-progress-dialog'; import RepoInfoBar from './components/repo-info-bar'; import RepoTag from './models/repo-tag'; import { LIST_MODE } from './components/dir-view-mode/constants'; import { MetadataAIOperationsProvider } from './hooks/metadata-ai-operation'; import ViewModes from './components/view-modes'; import SortMenu from './components/sort-menu'; import { TreeHelper, TreeNode, TreeView } from './components/shared-dir-tree-view'; import ResizeBar from './components/resize-bar'; import { DRAG_HANDLER_HEIGHT, INIT_SIDE_PANEL_RATE, MAX_SIDE_PANEL_RATE, MIN_SIDE_PANEL_RATE } from './components/resize-bar/constants'; import './css/layout.css'; import './css/header.css'; import './css/shared-dir-view.css'; import './css/grid-view.css'; dayjs.locale(window.app.config.lang); dayjs.extend(relativeTime); let loginUser = window.app.pageOptions.name; let { token, dirName, dirPath, sharedBy, repoID, relativePath, mode, thumbnailSize, trafficOverLimit, canDownload, noQuota, canUpload, enableVideoThumbnail, enablePDFThumbnail } = window.shared.pageOptions; const showDownloadIcon = !trafficOverLimit && canDownload; class SharedDirView extends React.Component { constructor(props) { super(props); this.state = { isTreeDataLoading: true, treeData: TreeHelper.buildTree(), // for resizing side/main panels inResizing: false, sidePanelRate: parseFloat(localStorage.getItem('sf_side_panel_rate') || INIT_SIDE_PANEL_RATE), isLoading: true, errorMsg: '', items: [], path: relativePath, isDropdownMenuOpen: false, currentMode: mode, isAllItemsSelected: false, selectedItems: [], sortBy: 'name', // 'name' or 'time' or 'size' sortOrder: 'asc', // 'asc' or 'desc' isZipDialogOpen: false, zipFolderPath: '', usedRepoTags: [], isSaveSharedDirDialogShow: false, itemsForSave: [], asyncCopyMoveTaskId: '', asyncOperationProgress: 0, asyncOperatedFilesLength: 0, isCopyMoveProgressDialogShow: false, isImagePopupOpen: false, imageItems: [], imageIndex: 0 }; this.resizeBarRef = React.createRef(); this.dragHandlerRef = React.createRef(); } componentDidMount() { if (trafficOverLimit) { toaster.danger(gettext('File download is disabled: the share link traffic of owner is used up.'), { duration: 3 }); } this.loadTreePanel(); this.listItems(); this.getShareLinkRepoTags(); } loadTreePanel = () => { seafileAPI.listSharedDir(token, '/', thumbnailSize).then((res) => { const { dirent_list } = res.data; let tree = this.state.treeData; this.addResponseListToNode(dirent_list, tree.root); this.setState({ isTreeDataLoading: false, treeData: tree }); }).catch(() => { this.setState({ isTreeDataLoading: false }); }); /* keep it for now if (relativePath == '/') { } else { this.loadNodeAndParentsByPath(relativePath); } */ }; /* loadNodeAndParentsByPath = (path) => { }; */ addResponseListToNode = (list, node) => { node.isLoaded = true; node.isExpanded = true; // only display folders in the tree const dirList = list.filter(item => item.is_dir); const direntList = Utils.sortDirentsInSharedDir(dirList, this.state.sortBy, this.state.sortOrder); const nodeList = direntList.map(object => { return new TreeNode({ object }); }); node.addChildren(nodeList); }; listItems = () => { const { path, currentMode } = this.state; const thumbnailSize = currentMode == LIST_MODE ? thumbnailDefaultSize : thumbnailSizeForGrid; seafileAPI.listSharedDir(token, path, thumbnailSize).then((res) => { const items = res.data['dirent_list'].map(item => { item.isSelected = false; return item; }); this.setState({ isLoading: false, errorMsg: '', items: Utils.sortDirentsInSharedDir(items, this.state.sortBy, this.state.sortOrder) }); this.getThumbnails(thumbnailSize); }).catch((error) => { let errorMsg = Utils.getErrorMsg(error); this.setState({ isLoading: false, errorMsg: errorMsg }); }); // update the URL let normalizedPath = ''; if (path == '/') { normalizedPath = path; } else { normalizedPath = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path; } let url = new URL(location.href); let searchParams = new URLSearchParams(url.search); searchParams.set('p', normalizedPath); searchParams.set('mode', currentMode); url.search = searchParams.toString(); url = url.toString(); window.history.pushState({ url: url, path: path }, path, url); }; sortItems = (sortBy, sortOrder) => { this.setState({ sortBy: sortBy, sortOrder: sortOrder, items: Utils.sortDirentsInSharedDir(this.state.items, sortBy, sortOrder) }); }; getThumbnails = (thumbnailSize) => { let items = this.state.items.filter((item) => { return !item.is_dir && (Utils.imageCheck(item.file_name) || (enableVideoThumbnail && Utils.videoCheck(item.file_name)) || (enablePDFThumbnail && Utils.pdfCheck(item.file_name))) && !item.encoded_thumbnail_src; }); if (items.length == 0) { return ; } const len = items.length; const _this = this; let getThumbnail = function (i) { const curItem = items[i]; seafileAPI.getShareLinkThumbnail(token, curItem.file_path, thumbnailSize).then((res) => { curItem.encoded_thumbnail_src = res.data.encoded_thumbnail_src; }).catch((error) => { // do nothing }).then(() => { if (i < len - 1) { getThumbnail(++i); } else { // when done, `setState()` _this.setState({ items: _this.state.items }); } }); }; getThumbnail(0); }; toggleDropdownMenu = () => { this.setState({ isDropdownMenuOpen: !this.state.isDropdownMenuOpen }); }; onDropdownToggleKeyDown = (e) => { if (e.key == 'Enter' || e.key == 'Space') { this.toggleDropdownMenu(); } }; onMenuItemKeyDown = (item, e) => { if (e.key == 'Enter' || e.key == 'Space') { item.onClick(); } }; visitFolder = (folderPath) => { this.setState({ path: folderPath }, () => { this.listItems(); }); }; renderPath = () => { let opList = []; if (showDownloadIcon) { opList.push({ 'icon': 'download1', 'text': gettext('ZIP'), 'onClick': this.zipDownloadFolder.bind(this, this.state.path) }); if (canDownload && loginUser && (loginUser !== sharedBy)) { opList.push({ 'icon': 'save', 'text': gettext('Save'), 'onClick': this.saveAllItems }); } } if (canUpload) { opList.push({ 'icon': 'upload-files', 'disabled': noQuota, 'title': noQuota ? gettext('The owner of this library has run out of space.') : '', 'text': gettext('Upload'), 'onClick': this.onUploadFile }); } const zipped = []; // be compatible with the old code const rootItem = { path: '/', name: dirName }; zipped.push(rootItem); const { path } = this.state; if (path != '/') { const normalizedPath = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path; const pathList = normalizedPath.split('/'); pathList.shift(); let itemPath = ''; const subItems = pathList.map((item, index) => { itemPath += '/' + item; return { path: itemPath + '/', // the ending '/' is necessary name: item }; }); zipped.push(...subItems); } return ( {zipped.map((item, index) => { if (index != zipped.length - 1) { return ( {item.name} / ); } return null; })} {(!showDownloadIcon && !canUpload) ? {zipped[zipped.length - 1].name} : ( {zipped[zipped.length - 1].name} {canUpload ? <> : } {opList.map((item, index) => { if (item == 'Divider') { return ; } else { return ( {item.text} ); } })} ) } ); }; zipDownloadFolder = (folderPath) => { if (!useGoFileserver) { this.setState({ isZipDialogOpen: true, zipFolderPath: folderPath }); } else { seafileAPI.getShareLinkZipTask(token, folderPath).then((res) => { const zipToken = res.data['zip_token']; location.href = `${fileServerRoot}zip/${zipToken}`; }).catch((error) => { let errorMsg = Utils.getErrorMsg(error); this.setState({ isLoading: false, errorMsg: errorMsg }); }); } }; zipDownloadSelectedItems = () => { const { path } = this.state; if (!useGoFileserver) { this.setState({ isZipDialogOpen: true, zipFolderPath: path, selectedItems: this.state.items.filter(item => item.isSelected) .map(item => item.file_name || item.folder_name) }); } else { let target = this.state.items.filter(item => item.isSelected).map(item => item.file_name || item.folder_name); seafileAPI.getShareLinkDirentsZipTask(token, path, target).then((res) => { const zipToken = res.data['zip_token']; location.href = `${fileServerRoot}zip/${zipToken}`; }).catch((error) => { let errorMsg = Utils.getErrorMsg(error); this.setState({ isLoading: false, errorMsg: errorMsg }); }); } }; async getAsyncCopyMoveProgress() { let { asyncCopyMoveTaskId } = this.state; try { let res = await seafileAPI.queryAsyncOperationProgress(asyncCopyMoveTaskId); let data = res.data; if (data.failed) { let message = gettext('Failed to copy files to another library.'); toaster.danger(message); this.setState({ asyncOperationProgress: 0, isCopyMoveProgressDialogShow: false, }); return; } if (data.successful) { this.setState({ asyncOperationProgress: 0, isCopyMoveProgressDialogShow: false, }); let message = gettext('Successfully copied files to another library.'); toaster.success(message); return; } // init state: total is 0 let asyncOperationProgress = !data.total ? 0 : parseInt((data.done / data.total * 100).toFixed(2)); this.getAsyncCopyMoveProgress(); this.setState({ asyncOperationProgress: asyncOperationProgress }); } catch (error) { this.setState({ asyncOperationProgress: 0, isCopyMoveProgressDialogShow: false, }); } } saveSelectedItems = () => { this.setState({ isSaveSharedDirDialogShow: true, itemsForSave: this.state.items.filter(item => item.isSelected) .map(item => item.file_name || item.folder_name) }); }; saveAllItems = () => { this.setState({ isSaveSharedDirDialogShow: true, itemsForSave: this.state.items .map(item => item.file_name || item.folder_name) }); }; toggleSaveSharedDirCancel = () => { this.setState({ isSaveSharedDirDialogShow: false, itemsForSave: [] }); }; handleSaveSharedDir = (destRepoID, dstPath) => { const { path, itemsForSave } = this.state; seafileAPI.saveSharedDir(destRepoID, dstPath, token, path, itemsForSave).then((res) => { this.setState({ isSaveSharedDirDialogShow: false, itemsForSave: [], isCopyMoveProgressDialogShow: true, asyncCopyMoveTaskId: res.data.task_id, asyncOperatedFilesLength: itemsForSave.length, }, () => { this.getAsyncCopyMoveProgress(); }); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); this.setState({ errMessage: errMessage }); }); }; onProgressDialogToggle = () => { let { asyncOperationProgress } = this.state; if (asyncOperationProgress !== 100) { let taskId = this.state.asyncCopyMoveTaskId; seafileAPI.cancelCopyMoveOperation(taskId); } this.setState({ asyncOperationProgress: 0, isCopyMoveProgressDialogShow: false, }); }; closeZipDialog = () => { this.setState({ isZipDialogOpen: false, zipFolderPath: '', selectedItems: [] }); }; // for image popup prepareImageItem = (item) => { const name = item.file_name; const fileExt = name.substr(name.lastIndexOf('.') + 1).toLowerCase(); const isGIF = fileExt == 'gif'; let src; const fileURL = `${siteRoot}d/${token}/files/?p=${encodeURIComponent(item.file_path)}`; if (!isGIF) { src = `${siteRoot}thumbnail/${token}/${thumbnailSizeForOriginal}${Utils.encodePath(item.file_path)}`; } else { src = `${fileURL}&raw=1`; } return { 'name': name, 'url': fileURL, 'parentDir': item.file_path.slice(0, item.file_path.indexOf(name)), 'thumbnail': `${siteRoot}thumbnail/${token}/${thumbnailSizeForOriginal}${Utils.encodePath(item.file_path)}`, 'src': src, 'downloadURL': fileURL + '&dl=1' }; }; showImagePopup = (curItem) => { const items = this.state.items.filter((item) => { return !item.is_dir && Utils.imageCheck(item.file_name); }); const imageItems = items.map((item) => { return this.prepareImageItem(item); }); this.setState({ isImagePopupOpen: true, imageItems: imageItems, imageIndex: items.indexOf(curItem) }); }; closeImagePopup = () => { this.setState({ isImagePopupOpen: false }); }; moveToPrevImage = () => { const imageItemsLength = this.state.imageItems.length; this.setState((prevState) => ({ imageIndex: (prevState.imageIndex + imageItemsLength - 1) % imageItemsLength })); }; moveToNextImage = () => { const imageItemsLength = this.state.imageItems.length; this.setState((prevState) => ({ imageIndex: (prevState.imageIndex + 1) % imageItemsLength })); }; unselectItems = () => { this.setState({ isAllItemsSelected: false, items: this.state.items.map((item) => { item.isSelected = false; return item; }) }); }; toggleAllSelected = () => { this.setState((prevState) => ({ isAllItemsSelected: !prevState.isAllItemsSelected, items: this.state.items.map((item) => { item.isSelected = !prevState.isAllItemsSelected; return item; }) })); }; toggleItemSelected = (targetItem, isSelected) => { this.setState({ items: this.state.items.map((item) => { if (item === targetItem) { item.isSelected = isSelected; } return item; }) }, () => { this.setState({ isAllItemsSelected: !this.state.items.some(item => !item.isSelected) }); }); }; onUploadFile = (e) => { e.nativeEvent.stopImmediatePropagation(); this.uploader.onFileUpload(); }; onFileUploadSuccess = (direntObject) => { const { path } = this.state; const { name, size } = direntObject; const newItem = { isSelected: false, file_name: name, file_path: Utils.joinPath(path, name), is_dir: false, last_modified: dayjs().format(), size: size }; const folderItems = this.state.items.filter(item => { return item.is_dir; }); // put the new file as the first file let items = Array.from(this.state.items); items.splice(folderItems.length, 0, newItem); this.setState({ items: items }); seafileAPI.shareLinksUploadDone(token, Utils.joinPath(dirPath, name)); }; getShareLinkRepoTags = () => { seafileAPI.getShareLinkRepoTags(token).then(res => { let usedRepoTags = []; res.data.repo_tags.forEach(item => { let usedRepoTag = new RepoTag(item); if (usedRepoTag.fileCount > 0) { usedRepoTags.push(usedRepoTag); } }); this.setState({ usedRepoTags: usedRepoTags }); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); }; switchMode = (mode) => { const { currentMode } = this.state; if (mode == currentMode) { return; } else { this.setState({ currentMode: mode, isLoading: true }, () => { this.listItems(); }); } }; onSelectSortOption = (item) => { const [sortBy, sortOrder] = item.value.split('-'); this.sortItems(sortBy, sortOrder); }; onTreeNodeCollapse = (node) => { const tree = TreeHelper.collapseNode(this.state.treeData, node); this.setState({ treeData: tree }); }; onTreeNodeExpanded = (node) => { let tree = this.state.treeData.clone(); node = tree.getNodeByPath(node.path); if (!node.isLoaded) { seafileAPI.listSharedDir(token, node.path, thumbnailSize).then((res) => { const { dirent_list } = res.data; this.addResponseListToNode(dirent_list, node); this.setState({ treeData: tree }); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); } else { tree.expandNode(node); this.setState({ treeData: tree }); } }; onTreeNodeClick = (node) => { if (node.object.is_dir) { if (node.isLoaded && node.path === this.state.path) { if (node.isExpanded) { let tree = TreeHelper.collapseNode(this.state.treeData, node); this.setState({ treeData: tree }); } else { let tree = this.state.treeData.clone(); node = tree.getNodeByPath(node.path); tree.expandNode(node); this.setState({ treeData: tree }); } } if (!node.isLoaded) { let tree = this.state.treeData.clone(); node = tree.getNodeByPath(node.path); seafileAPI.listSharedDir(token, node.path, thumbnailSize).then((res) => { const { dirent_list } = res.data; this.addResponseListToNode(dirent_list, node); tree.collapseNode(node); this.setState({ treeData: tree }); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); } if (node.path === this.state.path) { return; } this.visitFolder(node.path); } }; onResizeMouseUp = () => { if (this.state.inResizing) { this.setState({ inResizing: false }); } localStorage.setItem('sf_side_panel_rate', this.state.sidePanelRate); }; onResizeMouseDown = () => { this.setState({ inResizing: true }); }; onResizeMouseMove = (e) => { let rate = e.nativeEvent.clientX / window.innerWidth; this.setState({ sidePanelRate: Math.max(Math.min(rate, MAX_SIDE_PANEL_RATE), MIN_SIDE_PANEL_RATE), }); }; onResizeMouseOver = (event) => { if (!this.dragHandlerRef.current) return; const { top } = this.resizeBarRef.current.getBoundingClientRect(); const dragHandlerRefTop = event.pageY - top - DRAG_HANDLER_HEIGHT / 2; this.setDragHandlerTop(dragHandlerRefTop); }; setDragHandlerTop = (top) => { this.dragHandlerRef.current.style.top = top + 'px'; }; render() { const { usedRepoTags, currentMode: mode, sortBy, sortOrder, isTreeDataLoading, treeData, path, sidePanelRate, inResizing } = this.state; const mainPanelStyle = { userSelect: inResizing ? 'none' : '', flex: sidePanelRate ? `1 0 ${(1 - sidePanelRate) * 100}%` : `0 0 ${100 - INIT_SIDE_PANEL_RATE * 100}%`, }; const sidePanelStyle = { userSelect: inResizing ? 'none' : '', flex: sidePanelRate ? `0 0 ${sidePanelRate * 100}%` : `0 0 ${INIT_SIDE_PANEL_RATE * 100}%`, }; const isDesktop = Utils.isDesktop(); const selectedItemsLength = this.state.items.filter(item => item.isSelected).length; const isRepoInfoBarShown = isDesktop && path == '/' && usedRepoTags.length != 0; return (
logo {loginUser && }

{dirName}

{gettext('Shared by: ')}{sharedBy}

{isTreeDataLoading ? : ( )}
{isDesktop && }
{(showDownloadIcon && this.state.items.some(item => item.isSelected)) ? (
{`${selectedItemsLength} ${gettext('selected')}`} {(canDownload && loginUser && (loginUser !== sharedBy)) && }
) : (
{gettext('Current path: ')} {this.renderPath()}
) }
{isDesktop && ( <> )}
{!noQuota && canUpload && ( this.uploader = uploader} dragAndDrop={false} token={token} path={dirPath === '/' ? dirPath : dirPath.replace(/\/+$/, '')} relativePath={path === '/' ? path : path.replace(/\/+$/, '')} repoID={repoID} onFileUploadSuccess={this.onFileUploadSuccess} /> )}
{isRepoInfoBarShown && ( )}
{this.state.isZipDialogOpen && } {this.state.isSaveSharedDirDialogShow && } {this.state.isCopyMoveProgressDialogShow && ( )} {this.state.isImagePopupOpen && }
); } } class Content extends React.Component { constructor(props) { super(props); } sortByName = (e) => { e.preventDefault(); const sortBy = 'name'; const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc'; this.props.sortItems(sortBy, sortOrder); }; sortByTime = (e) => { e.preventDefault(); const sortBy = 'time'; const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc'; this.props.sortItems(sortBy, sortOrder); }; sortBySize = (e) => { e.preventDefault(); const sortBy = 'size'; const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc'; this.props.sortItems(sortBy, sortOrder); }; render() { const { isDesktop, isLoading, errorMsg, mode, items, sortBy, sortOrder, isAllItemsSelected } = this.props; if (isLoading) { return ; } if (errorMsg) { return

{errorMsg}

; } const tbody = ( {items.map((item, index) => { return ; })} ); if (!isDesktop) { return ( {tbody}
); } const sortIcon = ; return mode == LIST_MODE ? (
{showDownloadIcon && } {tbody}
{gettext('Name')} {sortBy == 'name' && sortIcon} {gettext('Size')} {sortBy == 'size' && sortIcon} {gettext('Last Update')} {sortBy == 'time' && sortIcon}
) : ( ); } } Content.propTypes = { isDesktop: PropTypes.bool, isLoading: PropTypes.bool, isAllItemsSelected: PropTypes.bool, errorMsg: PropTypes.string, mode: PropTypes.string, items: PropTypes.array, sortItems: PropTypes.func, sortBy: PropTypes.string, sortOrder: PropTypes.string, toggleAllSelected: PropTypes.func, toggleItemSelected: PropTypes.func, zipDownloadFolder: PropTypes.func, showImagePopup: PropTypes.func, visitFolder: PropTypes.func.isRequired }; class Item extends React.Component { constructor(props) { super(props); this.state = { isIconShown: false, isOpMenuOpen: false }; } toggleOpMenu = () => { this.setState({ isOpMenuOpen: !this.state.isOpMenuOpen }); }; handleMouseOver = () => { this.setState({ isIconShown: true }); }; handleMouseOut = () => { this.setState({ isIconShown: false }); }; zipDownloadFolder = (e) => { e.preventDefault(); this.props.zipDownloadFolder.bind(this, this.props.item.folder_path)(); }; handleFileClick = (e) => { const item = this.props.item; if (!Utils.imageCheck(item.file_name)) { return; } e.preventDefault(); this.props.showImagePopup(item); }; toggleItemSelected = (e) => { this.props.toggleItemSelected(this.props.item, e.target.checked); }; onFolderItemClick = (e) => { e.preventDefault(); const { item } = this.props; const { folder_path } = item; this.props.visitFolder(folder_path); }; render() { const { item, isDesktop, mode } = this.props; const { isIconShown } = this.state; let toolTipID = ''; let tagTitle = ''; if (item.file_tags && item.file_tags.length > 0) { toolTipID = MD5(item.file_name).slice(0, 7); tagTitle = item.file_tags.map(item => item.tag_name).join(' '); } if (item.is_dir) { return isDesktop ? ( {showDownloadIcon && } {item.folder_name} {dayjs(item.last_modified).fromNow()} {showDownloadIcon && } ) : ( {item.folder_name}
{dayjs(item.last_modified).fromNow()} {showDownloadIcon &&
{gettext('Download')}
} ); } else { const fileURL = `${siteRoot}d/${token}/files/?p=${encodeURIComponent(item.file_path)}`; const thumbnailURL = item.encoded_thumbnail_src ? `${siteRoot}${item.encoded_thumbnail_src}` : ''; return isDesktop ? ( {showDownloadIcon && } {thumbnailURL ? : } {item.file_name} {(item.file_tags && item.file_tags.length > 0) && (
{item.file_tags.map((fileTag, index) => { let length = item.file_tags.length; return ( ); })}
{tagTitle}
)} {Utils.bytesToSize(item.size)} {dayjs(item.last_modified).fromNow()} {showDownloadIcon && } ) : ( {thumbnailURL ? : } {item.file_name}
{Utils.bytesToSize(item.size)} {dayjs(item.last_modified).fromNow()} {showDownloadIcon &&
{gettext('Download')}
} ); } } } Item.propTypes = { isDesktop: PropTypes.bool, mode: PropTypes.string, item: PropTypes.object, sortItems: PropTypes.func, sortBy: PropTypes.string, sortOrder: PropTypes.string, toggleAllSelected: PropTypes.func, toggleItemSelected: PropTypes.func, zipDownloadFolder: PropTypes.func, showImagePopup: PropTypes.func, visitFolder: PropTypes.func.isRequired }; class GridItem extends React.Component { constructor(props) { super(props); this.state = { isIconShown: false }; } handleMouseOver = () => { this.setState({ isIconShown: true }); }; handleMouseOut = () => { this.setState({ isIconShown: false }); }; zipDownloadFolder = (e) => { e.preventDefault(); this.props.zipDownloadFolder.bind(this, this.props.item.folder_path)(); }; handleFileClick = (e) => { const item = this.props.item; if (!Utils.imageCheck(item.file_name)) { return; } e.preventDefault(); this.props.showImagePopup(item); }; onFolderItemClick = (e) => { e.preventDefault(); const { item } = this.props; const { folder_path } = item; this.props.visitFolder(folder_path); }; render() { const { item, mode } = this.props; const { isIconShown } = this.state; if (item.is_dir) { const folderURL = `?p=${encodeURIComponent(item.folder_path.substr(0, item.folder_path.length - 1))}&mode=${mode}`; return (
  • {item.folder_name} {showDownloadIcon && }
  • ); } else { const fileURL = `${siteRoot}d/${token}/files/?p=${encodeURIComponent(item.file_path)}`; const thumbnailURL = item.encoded_thumbnail_src ? `${siteRoot}${item.encoded_thumbnail_src}` : ''; return (
  • {thumbnailURL ? : } {item.file_name} {showDownloadIcon && }
  • ); } } } GridItem.propTypes = { mode: PropTypes.string, item: PropTypes.object, zipDownloadFolder: PropTypes.func, showImagePopup: PropTypes.func, visitFolder: PropTypes.func.isRequired }; const root = createRoot(document.getElementById('wrapper')); root.render();