import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import MD5 from 'MD5'; import ReactDom from 'react-dom'; import { Button, Dropdown, DropdownToggle, DropdownItem, UncontrolledTooltip } from 'reactstrap'; import moment from 'moment'; import Account from './components/common/account'; import { useGoFileserver, fileServerRoot, gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle, thumbnailSizeForOriginal } 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 './css/shared-dir-view.css'; import './css/grid-view.css'; moment.locale(window.app.config.lang); let loginUser = window.app.pageOptions.name; let { token, dirName, dirPath, sharedBy, repoID, relativePath, mode, thumbnailSize, zipped, trafficOverLimit, canDownload, noQuota, canUpload, enableVideoThumbnail } = window.shared.pageOptions; const showDownloadIcon = !trafficOverLimit && canDownload; class SharedDirView extends React.Component { constructor(props) { super(props); this.state = { isLoading: true, errorMsg: '', items: [], isAllItemsSelected: false, selectedItems: [], sortBy: 'name', // 'name' or 'time' or 'size' sortOrder: 'asc', // 'asc' or 'desc' isZipDialogOpen: false, zipFolderPath: '', usedRepoTags: [], isRepoInfoBarShow: false, isSaveSharedDirDialogShow: false, itemsForSave: [], asyncCopyMoveTaskId: '', asyncOperationProgress: 0, asyncOperatedFilesLength: 0, isCopyMoveProgressDialogShow: false, isImagePopupOpen: false, imageItems: [], imageIndex: 0 }; } componentDidMount() { if (trafficOverLimit) { toaster.danger(gettext('File download is disabled: the share link traffic of owner is used up.'), { duration: 3 }); } seafileAPI.listSharedDir(token, relativePath, 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(); }).catch((error) => { let errorMsg = Utils.getErrorMsg(error); this.setState({ isLoading: false, errorMsg: errorMsg }); }); this.getShareLinkRepoTags(); } sortItems = (sortBy, sortOrder) => { this.setState({ sortBy: sortBy, sortOrder: sortOrder, items: Utils.sortDirentsInSharedDir(this.state.items, sortBy, sortOrder) }); }; getThumbnails = () => { let items = this.state.items.filter((item) => { return !item.is_dir && (Utils.imageCheck(item.file_name) || (enableVideoThumbnail && Utils.videoCheck(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); }; renderPath = () => { return ( {zipped.map((item, index) => { if (index != zipped.length - 1) { return ( {item.name} / ); } return null; }) } {zipped[zipped.length - 1].name} ); }; 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 = () => { if (!useGoFileserver) { this.setState({ isZipDialogOpen: true, zipFolderPath: relativePath, 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, relativePath, 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 itemsForSave = this.state.itemsForSave; seafileAPI.saveSharedDir(destRepoID, dstPath, token, relativePath, 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, 'src': src }; }; 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 })); }; 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 { name, size } = direntObject; const newItem = { isSelected: false, file_name: name, file_path: Utils.joinPath(relativePath, name), is_dir: false, last_modified: moment().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}); if (usedRepoTags.length != 0 && relativePath == '/') { this.setState({isRepoInfoBarShow: true}); } }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); }; render() { const isDesktop = Utils.isDesktop(); const modeBaseClass = 'btn btn-secondary btn-icon sf-view-mode-btn'; return (
logo {loginUser && }

{dirName}

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

{gettext('Current path: ')}{this.renderPath()}

{isDesktop &&
} {canUpload && ( )} {showDownloadIcon && {this.state.items.some(item => item.isSelected) ? {(canDownload && loginUser && (loginUser !== sharedBy)) && } : {(canDownload && loginUser && (loginUser !== sharedBy)) && } } }
{!noQuota && canUpload && ( this.uploader = uploader} dragAndDrop={false} token={token} path={dirPath === '/' ? dirPath : dirPath.replace(/\/+$/, '')} relativePath={relativePath === '/' ? relativePath : relativePath.replace(/\/+$/, '')} repoID={repoID} onFileUploadSuccess={this.onFileUploadSuccess} /> )} {this.state.isRepoInfoBarShow && ( )}
{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, 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' ? ( {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, items: PropTypes.array, sortItems: PropTypes.func, sortBy: PropTypes.string, sortOrder: PropTypes.string, toggleAllSelected: PropTypes.func, toggleItemSelected: PropTypes.func, zipDownloadFolder: PropTypes.func, showImagePopup: PropTypes.func, }; 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); }; render() { const { item, isDesktop } = 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} {moment(item.last_modified).fromNow()} {showDownloadIcon && } ) : ( {item.folder_name}
{moment(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)} {moment(item.last_modified).fromNow()} {showDownloadIcon && } ) : ( {thumbnailURL ? : } {item.file_name}
{Utils.bytesToSize(item.size)} {moment(item.last_modified).fromNow()} {showDownloadIcon &&
{gettext('Download')}
} ); } } } Item.propTypes = { isDesktop: PropTypes.bool, item: PropTypes.object, sortItems: PropTypes.func, sortBy: PropTypes.string, sortOrder: PropTypes.string, toggleAllSelected: PropTypes.func, toggleItemSelected: PropTypes.func, zipDownloadFolder: PropTypes.func, showImagePopup: PropTypes.func, }; 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); }; render() { const item = this.props.item; 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 = { item: PropTypes.object, zipDownloadFolder: PropTypes.func, showImagePopup: PropTypes.func, }; ReactDom.render(, document.getElementById('wrapper'));