import * as React from "react"; import * as ReactDOM from "react-dom"; import { List, Map, Set } from "immutable"; import FileSize from "filesize"; import { RiFolder2Fill } from "@react-icons/all-files/ri/RiFolder2Fill"; import { RiHomeSmileFill } from "@react-icons/all-files/ri/RiHomeSmileFill"; import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill"; import { alertMsg, confirmMsg } from "../common/env"; import { updater } from "./state_updater"; import { ICoreState, MsgProps, UIProps } from "./core_state"; import { LoginProps } from "./pane_login"; import { MetadataResp, roleVisitor, roleAdmin } from "../client"; import { Flexbox } from "./layout/flexbox"; import { Up } from "../worker/upload_mgr"; import { UploadEntry, UploadState } from "../worker/interface"; export interface Item { name: string; size: number; modTime: string; isDir: boolean; selected: boolean; sha1: string; } export interface FilesProps { dirPath: List; isSharing: boolean; items: List; } export interface Props { filesInfo: FilesProps; msg: MsgProps; login: LoginProps; ui: UIProps; update?: (updater: (prevState: ICoreState) => ICoreState) => void; } export function getItemPath(dirPath: string, itemName: string): string { return dirPath.endsWith("/") ? `${dirPath}${itemName}` : `${dirPath}/${itemName}`; } export interface State { newFolderName: string; selectedSrc: string; selectedItems: Map; showDetail: Set; uploadFiles: string; } export class FilesPanel extends React.Component { private uploadInput: Element | Text; private assignInput: (input: Element) => void; private onClickUpload: () => void; constructor(p: Props) { super(p); this.state = { newFolderName: "", selectedSrc: "", selectedItems: Map(), showDetail: Set(), uploadFiles: "", }; Up().setStatusCb(this.updateProgress); this.uploadInput = undefined; this.assignInput = (input) => { this.uploadInput = ReactDOM.findDOMNode(input); }; this.onClickUpload = () => { const uploadInput = this.uploadInput as HTMLButtonElement; uploadInput.click(); }; } updateProgress = async ( infos: Map, refresh: boolean ) => { updater().setUploadings(infos); let errCount = 0; infos.valueSeq().forEach((entry: UploadEntry) => { errCount += entry.state === UploadState.Error ? 1 : 0; }); if (infos.size === 0 || infos.size === errCount) { // refresh used space updater() .self() .then(() => { this.props.update(updater().updateLogin); }); } if (refresh) { updater() .setItems(this.props.filesInfo.dirPath) .then(() => { this.props.update(updater().updateFilesInfo); }); } else { this.props.update(updater().updateFilesInfo); } }; addUploads = (event: React.ChangeEvent) => { if (event.target.files.length > 1000) { alertMsg(this.props.msg.pkg.get("err.tooManyUploads")); return; } let fileList = List(); for (let i = 0; i < event.target.files.length; i++) { fileList = fileList.push(event.target.files[i]); } updater().addUploads(fileList); this.props.update(updater().updateFilesInfo); this.props.update(updater().updateSharingsInfo); }; onNewFolderNameChange = (ev: React.ChangeEvent) => { this.setState({ newFolderName: ev.target.value }); }; onMkDir = () => { if (this.state.newFolderName === "") { alertMsg(this.props.msg.pkg.get("browser.folder.add.fail")); return; } const dirPath = getItemPath( this.props.filesInfo.dirPath.join("/"), this.state.newFolderName ); updater() .mkDir(dirPath) .then(() => { this.setState({ newFolderName: "" }); return updater().setItems(this.props.filesInfo.dirPath); }) .then(() => { this.props.update(updater().updateFilesInfo); this.props.update(updater().updateSharingsInfo); }); }; delete = () => { // TODO: selected should be cleaned after change the cwd if (this.props.filesInfo.dirPath.join("/") !== this.state.selectedSrc) { alertMsg(this.props.msg.pkg.get("browser.del.fail")); this.setState({ selectedSrc: this.props.filesInfo.dirPath.join("/"), selectedItems: Map(), }); return; } else { const filesToDel = this.state.selectedItems.keySeq().join(", "); if ( !confirmMsg( `${this.props.msg.pkg.get("op.confirm")} [${ this.state.selectedItems.size }]: ${filesToDel}` ) ) { return; } } updater() .delete( this.props.filesInfo.dirPath, this.props.filesInfo.items, this.state.selectedItems ) .then(() => { return updater().self(); }) .then(() => { this.props.update(updater().updateFilesInfo); this.props.update(updater().updateSharingsInfo); this.props.update(updater().updateLogin); this.setState({ selectedSrc: "", selectedItems: Map(), }); }); }; moveHere = () => { const oldDir = this.state.selectedSrc; const newDir = this.props.filesInfo.dirPath.join("/"); if (oldDir === newDir) { alertMsg(this.props.msg.pkg.get("browser.move.fail")); return; } updater() .moveHere( this.state.selectedSrc, this.props.filesInfo.dirPath.join("/"), this.state.selectedItems ) .then(() => { this.props.update(updater().updateFilesInfo); this.props.update(updater().updateSharingsInfo); this.setState({ selectedSrc: "", selectedItems: Map(), }); }); }; gotoChild = async (childDirName: string) => { return this.chdir(this.props.filesInfo.dirPath.push(childDirName)); }; chdir = async (dirPath: List) => { if (dirPath === this.props.filesInfo.dirPath) { return; } else if (this.props.login.userRole !== roleAdmin && dirPath.size <= 1) { alertMsg(this.props.msg.pkg.get("unauthed")); return; } return updater() .setItems(dirPath) .then(() => { return updater().listSharings(); }) .then(() => { return updater().isSharing(dirPath.join("/")); }) .then(() => { this.props.update(updater().updateFilesInfo); this.props.update(updater().updateSharingsInfo); }); }; select = (itemName: string) => { const selectedItems = this.state.selectedItems.has(itemName) ? this.state.selectedItems.delete(itemName) : this.state.selectedItems.set(itemName, true); this.setState({ selectedSrc: this.props.filesInfo.dirPath.join("/"), selectedItems: selectedItems, }); }; selectAll = () => { let newSelected = Map(); const someSelected = this.state.selectedItems.size === 0 ? true : false; if (someSelected) { this.props.filesInfo.items.forEach((item) => { newSelected = newSelected.set(item.name, true); }); } else { this.props.filesInfo.items.forEach((item) => { newSelected = newSelected.delete(item.name); }); } this.setState({ selectedSrc: this.props.filesInfo.dirPath.join("/"), selectedItems: newSelected, }); }; toggleDetail = (name: string) => { const showDetail = this.state.showDetail.has(name) ? this.state.showDetail.delete(name) : this.state.showDetail.add(name); this.setState({ showDetail }); }; generateHash = async (filePath: string): Promise => { alertMsg(this.props.msg.pkg.get("refresh-hint")); return updater().generateHash(filePath); }; addSharing = async () => { return updater() .addSharing() .then((ok) => { if (!ok) { alertMsg(this.props.msg.pkg.get("browser.share.add.fail")); } else { updater().setSharing(true); } }) .then(() => { this.props.update(updater().updateFilesInfo); this.props.update(updater().updateSharingsInfo); }); }; deleteSharing = async (dirPath: string) => { return updater() .deleteSharing(dirPath) .then((ok) => { if (!ok) { alertMsg(this.props.msg.pkg.get("browser.share.del.fail")); } else { updater().setSharing(false); } }) .then(() => { this.props.update(updater().updateFilesInfo); this.props.update(updater().updateSharingsInfo); }); }; render() { const showOp = this.props.login.userRole === roleVisitor ? "hidden" : ""; const breadcrumb = this.props.filesInfo.dirPath.map( (pathPart: string, key: number) => { return ( ); } ); const nameWidthClass = `item-name item-name-${ this.props.ui.isVertical ? "vertical" : "horizontal" } pointer`; const ops = (
); const sortedItems = this.props.filesInfo.items.sort( (item1: MetadataResp, item2: MetadataResp) => { if (item1.isDir && !item2.isDir) { return -1; } else if (!item1.isDir && item2.isDir) { return 1; } return 0; } ); const itemList = sortedItems.map((item: MetadataResp) => { const isSelected = this.state.selectedItems.has(item.name); const dirPath = this.props.filesInfo.dirPath.join("/"); const itemPath = dirPath.endsWith("/") ? `${dirPath}${item.name}` : `${dirPath}/${item.name}`; return item.isDir ? ( , this.gotoChild(item.name)} > {item.name}
{item.modTime.slice(0, item.modTime.indexOf("T"))}
, ])} childrenStyles={List([ { flex: "0 0 auto" }, { flex: "0 0 auto" }, ])} /> , , ])} childrenStyles={List([ { flex: "0 0 auto", width: "60%" }, { flex: "0 0 auto", justifyContent: "flex-end", width: "40%" }, ])} /> ) : (
, {item.name}
{item.modTime.slice(0, item.modTime.indexOf("T"))}  /  {FileSize(item.size, { round: 0 })}
, ])} childrenStyles={List([ { flex: "0 0 auto" }, { flex: "0 0 auto" }, ])} />, , ])} childrenStyles={List([ { flex: "0 0 auto", width: "60%" }, { flex: "0 0 auto", justifyContent: "flex-end", width: "40%" }, ])} />
SHA1: {` ${item.sha1}`} , , ])} className={`grey2-bg grey3-font detail margin-r-m`} childrenStyles={List([{}, { justifyContent: "flex-end" }])} />
); }); const usedSpace = FileSize(parseInt(this.props.login.usedSpace, 10), { round: 0, }); const spaceLimit = FileSize( parseInt(this.props.login.quota.spaceLimit, 10), { round: 0, } ); const itemListPane = (
{ops}
{this.props.filesInfo.isSharing ? ( ) : ( )} , {this.state.selectedItems.size > 0 ? ( ) : null} , {`${this.props.msg.pkg.get( "browser.used" )} ${usedSpace} / ${spaceLimit}`} , ])} childrenStyles={List([ { flex: "0 0 auto" }, { flex: "0 0 auto" }, { justifyContent: "flex-end" }, ])} />
, , ])} childrenStyles={List([ { flex: "0 0 auto" }, { flex: "0 0 auto" }, ])} /> , , ])} childrenStyles={List([{}, { justifyContent: "flex-end" }])} /> {itemList}
); return
{itemListPane}
; } }