import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; import { siteRoot } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import { gettext } from '../../utils/constants'; import collabServer from '../../utils/collab-server'; import toaster from '../toast'; import DirPanel from './dir-panel'; import Dirent from '../../models/dirent'; import FileTag from '../../models/file-tag'; import RepoTag from '../../models/repo-tag'; import RepoInfo from '../../models/repo-info'; const propTypes = { pathPrefix: PropTypes.array.isRequired, onTabNavClick: PropTypes.func.isRequired, onMenuClick: PropTypes.func.isRequired, }; class DirView extends React.Component { constructor(props) { super(props); this.state = { path: '/', pathExist: true, repoName: '', repoID: '', permission: true, libNeedDecrypt: false, isDirentSelected: false, isAllDirentSelected: false, isDirentListLoading: true, currentRepoInfo: null, sortBy: 'name', // 'name' or 'time' sortOrder: 'asc', // 'asc' or 'desc' direntList: [], selectedDirentList: [], dirID: '', errorMsg: '', usedRepoTags: [], }; window.onpopstate = this.onpopstate; this.lastModifyTime = new Date(); } onpopstate = (event) => { if (event.state && event.state.path) { this.updateDirentList(event.state.path); this.setState({path: event.state.path}); } } componentDidMount() { // eg: http://127.0.0.1:8000/library/repo_id/repo_name/**/**/\ let location = decodeURIComponent(window.location.href); let repoID = this.props.repoID; collabServer.watchRepo(repoID, this.onRepoUpdateEvent); seafileAPI.listRepoTags(repoID).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}); }); seafileAPI.getRepoInfo(repoID).then(res => { let repoInfo = new RepoInfo(res.data); this.setState({ currentRepoInfo: repoInfo, repoID: repoInfo.repo_id, repoName: repoInfo.repo_name, permission: repoInfo.permission === 'rw', libNeedDecrypt: res.data.lib_need_decrypt, }); let repoName = repoInfo.repo_name; let repoID = repoInfo.repo_id; let path = location.slice(location.indexOf(repoID) + repoID.length); // get the string after repoID path = path.slice(path.indexOf(repoName) + repoName.length); // get current path this.setState({path: path}); if (!res.data.lib_need_decrypt) { this.updateDirentList(path); let fileUrl = siteRoot + 'library/' + repoInfo.repo_id + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path); window.history.pushState({url: fileUrl, path: path}, path, fileUrl); } }).catch(error => { if (error.response) { if (error.response.status == 403) { this.setState({ isDirentListLoading: false, errorMsg: gettext('Permission denied') }); } else { this.setState({ isDirentListLoading: false, errorMsg: gettext('Error') }); } } else { this.setState({ isDirentListLoading: false, errorMsg: gettext('Please check the network.') }); } }); } componentWillUnmount() { collabServer.unwatchRepo(this.props.repoID, this.onRepoUpdateEvent); } componentDidUpdate() { this.lastModifyTime = new Date(); } onRepoUpdateEvent = () => { let currentTime = new Date(); if ((parseFloat(currentTime - this.lastModifyTime)/1000) <= 5) { return; } let repoID = this.props.repoID; let { path, dirID } = this.state; seafileAPI.dirMetaData(repoID, path).then((res) => { if (res.data.id !== dirID) { toaster.notify( {gettext('This folder has been updated. ')} {gettext('Refresh')} , {id: 'repo_updated', duration: 3600} ); } }) } updateUsedRepoTags = (newUsedRepoTags) => { this.setState({usedRepoTags: newUsedRepoTags}); } updateDirentList = (filePath) => { let repoID = this.state.repoID; this.setState({isDirentListLoading: true}); seafileAPI.listDir(repoID, filePath).then(res => { let direntList = res.data.map(item => { return new Dirent(item); }); this.setState({ isDirentListLoading: false, pathExist: true, direntList: Utils.sortDirents(direntList, this.state.sortBy, this.state.sortOrder), dirID: res.headers.oid, }); }).catch(() => { this.setState({ isDirentListLoading: false, pathExist: false }); }); } onItemClick = (dirent) => { this.resetSelected(); let direntPath = Utils.joinPath(this.state.path, dirent.name); if (dirent.isDir()) { this.updateDirentList(direntPath); this.setState({path: direntPath}); let fileUrl = siteRoot + 'library/' + this.state.repoID + '/' + encodeURIComponent(this.state.repoName) + Utils.encodePath(direntPath); window.history.pushState({url: fileUrl, path: direntPath}, direntPath, fileUrl); } else { const w=window.open('about:blank'); const url = siteRoot + 'lib/' + this.state.repoID + '/file' + Utils.encodePath(direntPath); w.location.href = url; } } onAddFolder = (dirPath) => { let repoID = this.state.repoID; seafileAPI.createDir(repoID, dirPath).then(() => { let name = Utils.getFileName(dirPath); let dirent = this.createDirent(name, 'dir'); let direntList = this.addItem(dirent, 'dir'); this.setState({direntList: direntList}); }); } onAddFile = (filePath, isDraft) => { let repoID = this.state.repoID; seafileAPI.createFile(repoID, filePath, isDraft).then(res => { let name = Utils.getFileName(filePath); let dirent = this.createDirent(name, 'file', res.data); let direntList = this.addItem(dirent, 'file'); this.setState({direntList: direntList}); }); } onItemDelete = (dirent) => { let repoID = this.state.repoID; let direntPath = Utils.joinPath(this.state.path, dirent.name); if (dirent.isDir()) { seafileAPI.deleteDir(repoID, direntPath).then(() => { let direntList = this.deleteItem(dirent); this.setState({direntList: direntList}); }).catch(() => { // todo }) } else { seafileAPI.deleteFile(repoID, direntPath).then(() => { let direntList = this.deleteItem(dirent); this.setState({direntList: direntList}); }).catch(() => { // todo }) } } onItemRename = (dirent, newName) => { let repoID = this.state.repoID; let direntPath = Utils.joinPath(this.state.path, dirent.name); if (dirent.isDir()) { seafileAPI.renameDir(repoID, direntPath, newName).then(() => { let direntList = this.renameItem(dirent, newName); this.setState({direntList: direntList}); }).catch(() => { //todo }); } else { seafileAPI.renameFile(repoID, direntPath, newName).then(() => { let direntList = this.renameItem(dirent, newName); this.setState({direntList: direntList}); }).catch(() => { //todo }); } } onItemMove = (destRepo, dirent, moveToDirentPath) => { let dirName = dirent.name; let repoID = this.state.repoID; seafileAPI.moveDir(repoID, destRepo.repo_id, moveToDirentPath, this.state.path, dirName).then(() => { let direntList = this.deleteItem(dirent); this.setState({direntList: direntList}); let message = gettext('Successfully moved %(name)s.'); message = message.replace('%(name)s', dirName); toaster.success(message); }).catch(() => { let message = gettext('Failed to move %(name)s'); message = message.replace('%(name)s', dirName); toaster.danger(message); }); } onItemCopy = (destRepo, dirent, copyToDirentPath) => { let dirName = dirent.name; let repoID = this.state.repoID; seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, this.state.path, dirName).then(() => { let message = gettext('Successfully copied %(name)s.'); message = message.replace('%(name)s', dirName); toaster.success(message); }).catch(() => { let message = gettext('Failed to copy %(name)s'); message = message.replace('%(name)s', dirName); toaster.danger(message); }); } onItemSelected = (dirent) => { let direntList = this.state.direntList.map(item => { if (item.name === dirent.name) { item.isSelected = !item.isSelected; } return item; }); let selectedDirentList = direntList.filter(item => { return item.isSelected; }); if (selectedDirentList.length) { this.setState({isDirentSelected: true}); if (selectedDirentList.length === direntList.length) { this.setState({ isAllDirentSelected: true, direntList: direntList, selectedDirentList: selectedDirentList, }); } else { this.setState({ isAllDirentSelected: false, direntList: direntList, selectedDirentList: selectedDirentList }); } } else { this.setState({ isDirentSelected: false, isAllDirentSelected: false, direntList: direntList, selectedDirentList: [] }); } } onItemsMove = (destRepo, destDirentPath) => { let dirNames = this.getSelectedDirentNames(); let repoID = this.state.repoID; seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(() => { let direntList = this.deleteItems(dirNames); this.setState({ direntList: direntList, isDirentSelected: false, selectedDirentList: [], }); let message = gettext('Successfully moved %(name)s.'); message = message.replace('%(name)s', dirNames); toaster.success(message); }).catch(() => { let message = gettext('Failed to move %(name)s'); message = message.replace('%(name)s', dirNames); toaster.danger(message); }); } onItemsCopy = (destRepo, destDirentPath) => { let dirNames = this.getSelectedDirentNames(); let repoID = this.state.repoID; seafileAPI.copyDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(() => { let message = gettext('Successfully copied %(name)s.'); message = message.replace('%(name)s', dirNames); toaster.success(message); }).catch(() => { let message = gettext('Failed to copy %(name)s'); message = message.replace('%(name)s', dirNames); toaster.danger(message); }); } onItemsDelete = () => { let dirNames = this.getSelectedDirentNames(); let repoID = this.state.repoID; seafileAPI.deleteMutipleDirents(repoID, this.state.path, dirNames).then(res => { let direntList = this.deleteItems(dirNames); this.setState({ direntList: direntList, isDirentSelected: false, selectedDirentList: [], }); }); } onAllItemSelected = () => { if (this.state.isAllDirentSelected) { let direntList = this.state.direntList.map(item => { item.isSelected = false; return item; }); this.setState({ isDirentSelected: false, isAllDirentSelected: false, direntList: direntList, selectedDirentList: [], }); } else { let direntList = this.state.direntList.map(item => { item.isSelected = true; return item; }); this.setState({ isDirentSelected: true, isAllDirentSelected: true, direntList: direntList, selectedDirentList: direntList, }); } } onFileTagChanged = (dirent, direntPath) => { let repoID = this.state.repoID; seafileAPI.listFileTags(repoID, direntPath).then(res => { let fileTags = res.data.file_tags.map(item => { return new FileTag(item); }); this.updateDirent(dirent, 'file_tags', fileTags); }); seafileAPI.listRepoTags(repoID).then(res => { let usedRepoTags = []; res.data.repo_tags.forEach(item => { let usedRepoTag = new RepoTag(item); if (usedRepoTag.fileCount > 0) { usedRepoTags.push(usedRepoTag); } }); this.updateUsedRepoTags(usedRepoTags); }); } onMenuClick = () => { this.props.onMenuClick(); } onPathClick = (path) => { this.updateDirentList(path); this.setState({path: path}); let fileUrl = siteRoot + 'library/' + this.state.repoID + '/' + encodeURIComponent(this.state.repoName) + Utils.encodePath(path); window.history.pushState({url: fileUrl, path: path}, path, fileUrl); } updateDirent = (dirent, paramKey, paramValue) => { let newDirentList = this.state.direntList.map(item => { if (item.name === dirent.name) { item[paramKey] = paramValue; } return item; }); this.setState({direntList: newDirentList}); } onFileUploadSuccess = (direntObject) => { let isExist = this.state.direntList.some(item => { return item.name === direntObject.name && item.type === direntObject.type; }); if (isExist) { let direntList = this.state.direntList; for (let i = 0; i < direntList.length; i++) { let dirent = direntList[i]; if (dirent.name === direntObject.name && dirent.type === direntObject.type) { let mtime = moment.unix(direntObject.mtime).fromNow(); this.updateDirent(dirent, 'mtime', mtime); // todo file size is need update too, api is not return; break; } } } else { direntObject.permission = 'rw'; let dirent = new Dirent(direntObject); if (direntObject.type === 'dir') { this.setState({direntList: [dirent, ...this.state.direntList]}); } else { this.setState({direntList: [...this.state.direntList, dirent]}); } } } onSearchedClick = (selectedItem) => { if (selectedItem.is_dir === true) { this.setState({path: selectedItem.path}); } else { let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + selectedItem.path; let newWindow = window.open('about:blank'); newWindow.location.href = url; } } resetSelected = () => { this.setState({isDirentSelected: false, isAllDirentSelected: false}); } addItem = (dirent, type) => { let direntList = this.state.direntList.map(item => {return item}); //clone if (type === 'dir') { direntList.unshift(dirent); return direntList; } direntList.push(dirent); return direntList; } deleteItem = (dirent) => { return this.state.direntList.filter(item => { return item.name !== dirent.name; }); } renameItem = (dirent, newName) => { return this.state.direntList.map(item => { if (item.name === dirent.name) { item.name = newName; } return item; }); } deleteItems = (dirNames) => { let direntList = this.state.direntList.map(item => {return item}); //clone while (dirNames.length) { for (let i = 0; i < direntList.length; i++) { if (direntList[i].name === dirNames[0]) { direntList.splice(i, 1); break; } } dirNames.shift(); } return direntList; } createDirent(name, type, direntInfo) { let data = new Date().getTime()/1000; let dirent = null; if (type === 'dir') { dirent = new Dirent({ id: '000000000000000000', name: name, type: type, mtime: data, permission: 'rw', }); } else { dirent = new Dirent({ id: '000000000000000000', name: name, type: type, mtime: data, permission: 'rw', size: direntInfo.size, starred: false, is_locked: false, lock_time: '', lock_owner: null, locked_by_me: false, modifier_name: '', modifier_email: '', modifier_contact_email: '', file_tags: [] }); } return dirent; } getSelectedDirentNames = () => { let names = []; this.state.selectedDirentList.forEach(selectedDirent => { names.push(selectedDirent.name); }); return names; } isMarkdownFile(filePath) { let index = filePath.lastIndexOf('.'); if (index === -1) { return false; } else { let type = filePath.substring(index).toLowerCase(); if (type === '.md' || type === '.markdown') { return true; } else { return false; } } } onLibDecryptDialog = () => { this.setState({ libNeedDecrypt: !this.state.libNeedDecrypt }) this.updateDirentList(this.state.path); } sortItems = (sortBy, sortOrder) => { this.setState({ sortBy: sortBy, sortOrder: sortOrder, items: Utils.sortDirents(this.state.direntList, sortBy, sortOrder) }) } render() { return ( ); } } DirView.propTypes = propTypes; export default DirView;