fix(panel_files): add column layout as the only view

This commit is contained in:
hexxa 2022-03-14 20:50:48 +08:00 committed by Hexxa
parent 1b4dec878b
commit ffe3922916
4 changed files with 125 additions and 237 deletions

View file

@ -461,7 +461,6 @@
.title-m {
font-size: 1.4rem;
/* font-weight: bold; */
line-height: 2rem;
overflow: hidden;
white-space: nowrap;
@ -470,6 +469,14 @@
display: block;
}
.title-m-wrap {
font-size: 1.4rem;
line-height: 2rem;
overflow: hidden;
overflow-wrap: break-word;
display: block;
}
.desc-m {
font-size: 1.2rem;
line-height: 2rem;
@ -491,6 +498,13 @@
justify-content: flex-start;
}
.v-mid-r {
display: flex;
flex: 0 0 auto;
align-items: center;
justify-content: flex-end;
}
.full-width {
width: 100%;
}
@ -568,3 +582,15 @@
transform: rotate(360deg);
}
}
.float-l {
float: left;
}
.txt-align-l {
text-align: left;
}
.txt-align-r {
text-align: right;
}

View file

@ -14,9 +14,12 @@
color: #f1c40f;
}
.light-bg {
.lightest-bg {
background-color: white;
}
.light-bg {
background-color: #f6f6f6;
}
.normal-bg {
background-color: #ecf0f6;
}
@ -276,7 +279,7 @@ input:focus {
font-size: 1.8rem;
line-height: 2rem;
display: block;
margin: 2rem 0;
/* margin: 2rem 0; */
word-break: break-all;
}
@ -286,6 +289,9 @@ input:focus {
line-height: 2rem;
display: block;
word-break: break-all;
padding: 2rem;
margin-top: 1rem;
border-radius: 0.8rem;
}
.theme-default #item-rows .hr {
@ -503,10 +509,10 @@ input:focus {
margin: auto 1rem auto auto;
}
.theme-default .float-l {
/* .theme-default .float-l {
display: inline-block;
margin: auto 1rem auto auto;
}
} */
.theme-default .float-r {
display: inline-block;
@ -569,7 +575,6 @@ input:focus {
.theme-default .card {
padding: 1rem;
/* background-color: #e8e8e8; */
text-align: center;
border-radius: 0.8rem;
}

View file

@ -81,7 +81,10 @@ export class Tabs extends React.Component<Props, State, {}> {
});
return (
<div className={`tabs control-${this.props.targetControl}`}>{tabs}</div>
<div className={`tabs control-${this.props.targetControl}`}>
{tabs}
<div className="fix"></div>
</div>
);
}
}

View file

@ -26,6 +26,7 @@ import { Table, Cell, Head } from "./layout/table";
import { BtnList } from "./control/btn_list";
import { Segments } from "./layout/segments";
import { Rows } from "./layout/rows";
import { Columns } from "./layout/columns";
import { Up } from "../worker/upload_mgr";
import { UploadEntry, UploadState } from "../worker/interface";
import { getIcon } from "./visual/icons";
@ -104,7 +105,6 @@ export class FilesPanel extends React.Component<Props, State, {}> {
if (!this.props.enabled) {
return;
}
const uploadInput = this.uploadInput as HTMLButtonElement;
uploadInput.click();
};
@ -132,14 +132,16 @@ export class FilesPanel extends React.Component<Props, State, {}> {
document.removeEventListener("keyup", this.hotkeyHandler.handle);
}
onNewFolderNameChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ newFolderName: ev.target.value });
};
setLoading = (state: boolean) => {
updater().setControlOption(loadingCtrl, state ? ctrlOn : ctrlOff);
this.props.update(updater().updateUI);
};
updateProgress = async (
infos: Map<string, UploadEntry>
) => {
updateProgress = async (infos: Map<string, UploadEntry>) => {
updater().setUploads(infos);
let errCount = 0;
infos.valueSeq().forEach((entry: UploadEntry) => {
@ -159,7 +161,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
};
addUploads = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files.length > 1000) {
if (event.target.files.length > 200) {
alertMsg(this.props.msg.pkg.get("err.tooManyUploads"));
return;
}
@ -176,11 +178,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.props.update(updater().updateUploadingsInfo);
};
onNewFolderNameChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ newFolderName: ev.target.value });
};
onMkDir = async () => {
mkDir = async () => {
if (this.state.newFolderName === "") {
alertMsg(this.props.msg.pkg.get("browser.folder.add.fail"));
return;
@ -237,7 +235,8 @@ export class FilesPanel extends React.Component<Props, State, {}> {
const filesToDel = this.state.selectedItems.keySeq().join(", ");
if (
!confirmMsg(
`${this.props.msg.pkg.get("op.confirm")} [${this.state.selectedItems.size
`${this.props.msg.pkg.get("op.confirm")} [${
this.state.selectedItems.size
}]: ${filesToDel}`
)
) {
@ -474,134 +473,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.props.update(updater().updateFilesInfo);
};
prepareTable = (
sortedItems: List<MetadataResp>,
showOp: string
): React.ReactNode => {
const items = 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}`;
const icon = item.isDir ? (
<div className="v-mid item-cell">
<RiFolder2Fill size="3rem" className="yellow0-font" />
</div>
) : (
<div className="v-mid item-cell">
<RiFile2Fill size="3rem" className="cyan1-font" />
</div>
);
const modTimeTitle = this.props.msg.pkg.get("item.modTime");
const sizeTitle = this.props.msg.pkg.get("item.size");
const itemSize = FileSize(item.size, { round: 0 });
const content = item.isDir ? (
<div className={`v-mid item-cell`}>
<div className="full-width">
<div
className="title-m clickable"
onClick={() => this.gotoChild(item.name)}
>
{item.name}
</div>
<div className="desc-m grey0-font">
<span>
<span className="grey3-font">{`${modTimeTitle}: `}</span>
<span>{`${item.modTime}`}</span>
</span>
</div>
</div>
</div>
) : (
<div>
<div className={`v-mid item-cell`}>
<div className="full-width">
<a
className="title-m clickable"
href={`/v1/fs/files?fp=${itemPath}`}
target="_blank"
>
{item.name}
</a>
<div className="desc-m grey0-font">
<span className="grey3-font">{`${sizeTitle}: `}</span>
<span>{`${itemSize} | `}</span>
<span className="grey3-font">{`SHA1: `}</span>
<span>{item.sha1}</span>
</div>
</div>
</div>
</div>
);
const op = item.isDir ? (
<div className={`v-mid item-cell item-op ${showOp}`}>
<span onClick={() => this.select(item.name)} className="float-l">
{isSelected
? getIcon("RiCheckboxFill", "1.8rem", "cyan1")
: getIcon("RiCheckboxBlankLine", "1.8rem", "black1")}
</span>
</div>
) : (
<div className={`v-mid item-cell item-op ${showOp}`}>
<span onClick={() => this.select(item.name)} className="float-l">
{isSelected
? getIcon("RiCheckboxFill", "1.8rem", "cyan1")
: getIcon("RiCheckboxBlankLine", "1.8rem", "black1")}
</span>
</div>
);
return {
val: item,
cells: List<Cell>([
{ elem: icon, val: item.isDir ? "d" : "f" },
{ elem: content, val: itemPath },
{ elem: op, val: "" },
]),
};
});
const tableTitles = List<Head>([
{
elem: (
<div className="font-s grey0-font">
<RiFileList2Fill size="3rem" className="black-font" />
</div>
),
sortable: true,
},
{
elem: <div className="font-s grey0-font">Name</div>,
sortable: true,
},
{
elem: <div className="font-s grey0-font">Action</div>,
sortable: false,
},
]);
return (
<Table
colStyles={List([
{ width: "3rem", paddingRight: "1rem" },
{ width: "calc(100% - 12rem)", textAlign: "left" },
{ width: "8rem", textAlign: "right" },
])}
id="item-table"
head={tableTitles}
foot={List()}
rows={items}
updateRows={this.updateItems}
/>
);
};
prepareRows = (
prepareColumns = (
sortedItems: List<MetadataResp>,
showOp: string
): React.ReactNode => {
@ -617,55 +489,60 @@ export class FilesPanel extends React.Component<Props, State, {}> {
? `${dirPath}${item.name}`
: `${dirPath}/${item.name}`;
const selectedIconColor = isSelected ? "cyan1-font" : "black1-font";
const selectedIconColor = isSelected ? "highlight-font" : "dark-font";
const descIconColor = this.state.showDetail.has(item.name)
? "cyan1-font"
: "grey0-font";
? "highlight-font"
: "light-font";
const icon = item.isDir ? (
<RiFolder2Fill size="1.8rem" className="yellow0-font" />
<RiFolder2Fill size="2rem" className="yellow0-font margin-r-m" />
) : (
<RiFile2Fill size="1.8rem" className="cyan1-font" />
<RiFile2Fill size="2rem" className="cyan1-font margin-r-m" />
);
const fileType = item.isDir
? this.props.msg.pkg.get("item.type.folder")
: this.props.msg.pkg.get("item.type.file");
const modTimeDate = new Date(item.modTime);
const modTimeFormatted = `${modTimeDate.getFullYear()}-${
modTimeDate.getMonth() + 1
}-${modTimeDate.getDate()}`;
const downloadPath = `/v1/fs/files?fp=${itemPath}`;
const name = item.isDir ? (
<span className="clickable" onClick={() => this.gotoChild(item.name)}>
{item.name}
<span className="title-m-wrap">
<span className="clickable" onClick={() => this.gotoChild(item.name)}>
{item.name}
</span>
<span className="light-font">{` - ${modTimeFormatted}`}</span>
</span>
) : (
<a className="title-m clickable" href={downloadPath} target="_blank">
{item.name}
</a>
<span className="title-m-wrap">
<a className="clickable" href={downloadPath} target="_blank">
{item.name}
</a>
<span className="light-font">{` - ${modTimeFormatted}`}</span>
</span>
);
const checkIcon = isSelected ? (
<RiCheckboxFill
size="1.8rem"
size="2rem"
className={`${selectedIconColor} ${shareModeClass}`}
onClick={() => this.select(item.name)}
/>
) : (
<RiCheckboxBlankLine
size="1.8rem"
size="2rem"
className={`${selectedIconColor} ${shareModeClass}`}
onClick={() => this.select(item.name)}
/>
);
const op = item.isDir ? (
<div className={`v-mid item-op ${showOp}`}>{checkIcon}</div>
<div className={`item-op ${showOp}`}>{checkIcon}</div>
) : (
<div className={`v-mid item-op ${showOp}`}>
<div className={`item-op ${showOp}`}>
<RiMore2Fill
size="1.8rem"
className={`${descIconColor} margin-r-m`}
onClick={() => this.toggleDetail(item.name)}
/>
{checkIcon}
</div>
);
@ -674,42 +551,23 @@ export class FilesPanel extends React.Component<Props, State, {}> {
const pathTitle = this.props.msg.pkg.get("item.downloadURL");
const modTimeTitle = this.props.msg.pkg.get("item.modTime");
const sizeTitle = this.props.msg.pkg.get("item.size");
const fileTypeTitle = this.props.msg.pkg.get("item.type");
const itemSize = FileSize(item.size, { round: 0 });
const compact = item.isDir ? (
<span>
<span className="grey3-font">{`${modTimeTitle}: `}</span>
<span>{item.modTime}</span>
</span>
) : (
<span>
<span className="grey3-font">{`${pathTitle}: `}</span>
<span>{`${absDownloadURL} | `}</span>
<span className="grey3-font">{`${modTimeTitle}: `}</span>
<span>{`${item.modTime} | `}</span>
<span className="grey3-font">{`${sizeTitle}: `}</span>
<span>{`${itemSize} | `}</span>
<span className="grey3-font">{`SHA1: `}</span>
<span>{item.sha1}</span>
</span>
);
const details = (
<div>
<div className="desc light-bg">
<div className="column">
<div className="card">
<span className="title-m black-font">{pathTitle}</span>
<span className="title-m dark-font">{pathTitle}</span>
<span>{absDownloadURL}</span>
</div>
</div>
<div className="column">
<div className="card margin-l-m">
<span className="title-m black-font">{modTimeTitle}</span>
<span>{item.modTime}</span>
<span className="title-m dark-font">{modTimeTitle}</span>
<span>{modTimeFormatted}</span>
</div>
<div className="card margin-l-m">
<span className="title-m black-font">{sizeTitle}</span>
<span className="title-m dark-font">{sizeTitle}</span>
<span>{itemSize}</span>
</div>
</div>
@ -718,11 +576,11 @@ export class FilesPanel extends React.Component<Props, State, {}> {
<div className="card">
<Flexbox
children={List([
<span className="title-m black-font">SHA1</span>,
<span className="title-m dark-font">SHA1</span>,
<RiRestartFill
onClick={() => this.generateHash(itemPath)}
size={"2rem"}
className={`grey3-font ${shareModeClass}`}
className={`dark-font ${shareModeClass}`}
/>,
])}
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
@ -732,34 +590,33 @@ export class FilesPanel extends React.Component<Props, State, {}> {
</div>
</div>
);
const desc = this.state.showDetail.has(item.name) ? details : compact;
const desc = this.state.showDetail.has(item.name) ? details : null;
const elem = (
const cells = List<React.ReactNode>([
icon,
<div>{name}</div>,
<div className="title-m light-font">{itemSize}</div>,
<div className="txt-align-r">{op}</div>,
]);
const tableCols = (
<Columns
rows={List([cells])}
widths={List(["3rem", "calc(100% - 13rem)", "5rem", "5rem"])}
childrenClassNames={List(["", "", "", ""])}
/>
);
return (
<div>
<Flexbox
children={List([
<div>
<div className="v-mid">
{icon}
<span className="margin-l-m desc-l">{`${fileTypeTitle}`}</span>
&nbsp;
<span className="desc-l grey0-font">{`- ${fileType}`}</span>
</div>
</div>,
<div>{op}</div>,
])}
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
/>
<div className="name">{name}</div>
<div className="desc">{desc}</div>
{tableCols}
{desc}
<div className="hr"></div>
</div>
);
return elem;
});
return <Rows rows={List(rows)} />;
return <div>{rows}</div>;
};
setView = (opt: string) => {
@ -835,7 +692,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
<Flexbox
children={List([
<div>
<button onClick={this.onMkDir} className="float cyan-btn">
<button onClick={this.mkDir} className="float cyan-btn">
{this.props.msg.pkg.get("browser.folder.add")}
</button>
<input
@ -892,21 +749,18 @@ export class FilesPanel extends React.Component<Props, State, {}> {
/>
);
const viewType = this.props.ui.control.controls.get(filesViewCtrl);
const view =
viewType === "rows" ? (
<div id="item-rows">
{this.prepareRows(this.props.filesInfo.items, showOp)}
</div>
) : (
this.prepareTable(this.props.filesInfo.items, showOp)
);
const view = (
<div id="item-rows">
{this.prepareColumns(this.props.filesInfo.items, showOp)}
</div>
); // TODO: support better views in the future
const usedSpace = FileSize(
// TODO: this a work around before transaction is introduced
Math.trunc(
parseInt(this.props.login.extInfo.usedSpace, 10) / (1024 * 1024)
) *
(1024 * 1024),
(1024 * 1024),
{
round: 0,
}
@ -916,7 +770,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
Math.trunc(
parseInt(this.props.login.quota.spaceLimit, 10) / (1024 * 1024)
) *
(1024 * 1024),
(1024 * 1024),
{
round: 0,
}
@ -987,20 +841,20 @@ export class FilesPanel extends React.Component<Props, State, {}> {
<Flexbox
children={List([
<BiListUl
size="2rem"
className={`${rowsViewColorClass} margin-r-s`}
onClick={() => {
this.setView("rows");
}}
/>,
<BiTable
size="2rem"
className={`${tableViewColorClass} margin-r-s`}
onClick={() => {
this.setView("table");
}}
/>,
// <BiListUl
// size="2rem"
// className={`${rowsViewColorClass} margin-r-s`}
// onClick={() => {
// this.setView("rows");
// }}
// />,
// <BiTable
// size="2rem"
// className={`${tableViewColorClass} margin-r-s`}
// onClick={() => {
// this.setView("table");
// }}
// />,
<span className={`${showOp}`}>
<button