feat(fe/table): support sorting

This commit is contained in:
hexxa 2021-12-12 14:57:51 +08:00 committed by Hexxa
parent ee0fcb5691
commit d66b854f83
3 changed files with 243 additions and 70 deletions

View file

@ -1,60 +1,201 @@
import * as React from "react"; import * as React from "react";
import { List } from "immutable"; import { List } from "immutable";
import { updater } from "../state_updater";
export interface Cell {
elem: React.ReactNode;
val: string; // original cell value
// sortVal: string; // this value is for sorting
}
export interface Row {
cells: List<Cell>;
val: Object; // original object value
}
export interface Head {
elem: React.ReactNode;
sortable: boolean;
}
export interface Props { export interface Props {
head: List<React.ReactNode>; head: List<Head>;
rows: List<List<React.ReactNode>>; rows: List<Row>;
foot: List<React.ReactNode>; foot: List<React.ReactNode>;
colStyles?: List<React.CSSProperties>; colStyles?: List<React.CSSProperties>;
id?: string; id?: string;
style?: React.CSSProperties; style?: React.CSSProperties;
className?: string; className?: string;
originalVals?: List<Object>;
updateRows?: (rows: Object) => void;
} }
export const Table = (props: Props) => { export interface State {
const headCols = props.head.map( orders: List<boolean>; // asc = true, desc = false
(elem: React.ReactNode, i: number): React.ReactNode => { }
const style = props.colStyles != null ? props.colStyles.get(i) : {};
return (
<th key={`h-${i}`} style={style}>
{elem}
</th>
);
}
);
const bodyRows = props.rows.map(
(row: List<React.ReactNode>, i: number): React.ReactNode => {
const tds = row.map((elem: React.ReactNode, j: number) => {
const style = props.colStyles != null ? props.colStyles.get(j) : {};
return (
<td key={`rc-${i}-${j}`} style={style}>
{elem}
</td>
);
});
return <tr key={`r-${i}`}>{tds}</tr>;
}
);
const footCols = props.foot.map(
(elem: React.ReactNode, i: number): React.ReactNode => {
const style = props.colStyles != null ? props.colStyles.get(i) : {};
return (
<th key={`f-${i}`} style={style}>
{elem}
</th>
);
}
);
return ( export class Table extends React.Component<Props, State, {}> {
<table id={props.id} style={props.style} className={props.className}> constructor(p: Props) {
<thead> super(p);
<tr>{headCols}</tr> this.state = {
</thead> // TODO: if the size of column increases
<tbody>{bodyRows}</tbody> // state will be out of sync with props
<tfoot> // so this will not work
<tr>{footCols}</tr> orders: p.head.map((_, i: number) => {
</tfoot> return i === 0; // order by the first column
</table> }),
); };
}; }
sortRows = (colIndex: number) => {
if (this.props.updateRows == null) {
return;
}
const headCell = this.props.head.get(colIndex);
if (headCell == null || !headCell.sortable) {
return;
}
const currentOrder = this.state.orders.get(colIndex);
if (currentOrder == null) {
return;
}
const sortedRows = this.props.rows.sort((row1: Row, row2: Row) => {
const cell1 = row1.cells.get(colIndex);
const cell2 = row2.cells.get(colIndex);
if (cell1 == null || cell2 == null) {
// keep current order
return currentOrder ? -1 : 1;
} else if (cell1.val < cell2.val) {
return -1;
} else if (cell1.val == cell2.val) {
return 0;
} else {
return currentOrder ? 1 : -1;
}
});
const sortedItems = sortedRows.map((row: Row): Object => {
return row.val;
});
const newOrders = this.state.orders.set(colIndex, !currentOrder);
this.setState({ orders: newOrders });
this.props.updateRows(sortedItems);
};
render() {
const headCols = this.props.head.map(
(head: Head, i: number): React.ReactNode => {
const style =
this.props.colStyles != null ? this.props.colStyles.get(i) : {};
return (
<th
key={`h-${i}`}
className="title-xs clickable"
style={style}
onClick={() => {
this.sortRows(i);
}}
>
{head.elem}
</th>
);
}
);
const bodyRows = this.props.rows.map(
(row: Row, i: number): React.ReactNode => {
const tds = row.cells.map((cell: Cell, j: number) => {
const style =
this.props.colStyles != null ? this.props.colStyles.get(j) : {};
return (
<td key={`rc-${i}-${j}`} style={style}>
{cell.elem}
</td>
);
});
return <tr key={`r-${i}`}>{tds}</tr>;
}
);
const footCols = this.props.foot.map(
(elem: React.ReactNode, i: number): React.ReactNode => {
const style =
this.props.colStyles != null ? this.props.colStyles.get(i) : {};
return (
<th key={`f-${i}`} style={style}>
{elem}
</th>
);
}
);
return (
<table
id={this.props.id}
style={this.props.style}
className={this.props.className}
>
<thead>
<tr>{headCols}</tr>
</thead>
<tbody>{bodyRows}</tbody>
<tfoot>
<tr>{footCols}</tr>
</tfoot>
</table>
);
}
}
// export const Table = (props: Props) => {
// const headCols = props.head.map((head: Head, i: number): React.ReactNode => {
// const style = props.colStyles != null ? props.colStyles.get(i) : {};
// return (
// <th key={`h-${i}`} className="title-xs clickable" style={style}>
// {head.elem}
// </th>
// );
// });
// const bodyRows = props.rows.map(
// (row: List<Cell>, i: number): React.ReactNode => {
// const tds = row.map((cell: Cell, j: number) => {
// const style = props.colStyles != null ? props.colStyles.get(j) : {};
// return (
// <td key={`rc-${i}-${j}`} style={style}>
// {cell.elem}
// </td>
// );
// });
// return <tr key={`r-${i}`}>{tds}</tr>;
// }
// );
// const footCols = props.foot.map(
// (elem: React.ReactNode, i: number): React.ReactNode => {
// const style = props.colStyles != null ? props.colStyles.get(i) : {};
// return (
// <th key={`f-${i}`} style={style}>
// {elem}
// </th>
// );
// }
// );
// return (
// <table id={props.id} style={props.style} className={props.className}>
// <thead>
// <tr>{headCols}</tr>
// </thead>
// <tbody>{bodyRows}</tbody>
// <tfoot>
// <tr>{footCols}</tr>
// </tfoot>
// </table>
// );
// };

View file

@ -15,7 +15,7 @@ import { LoginProps } from "./pane_login";
import { MetadataResp, roleVisitor, roleAdmin } from "../client"; import { MetadataResp, roleVisitor, roleAdmin } from "../client";
import { Flexbox } from "./layout/flexbox"; import { Flexbox } from "./layout/flexbox";
import { Container } from "./layout/container"; import { Container } from "./layout/container";
import { Table } from "./layout/table"; import { Table, Cell, Head } from "./layout/table";
import { Up } from "../worker/upload_mgr"; import { Up } from "../worker/upload_mgr";
import { UploadEntry, UploadState } from "../worker/interface"; import { UploadEntry, UploadState } from "../worker/interface";
import { getIcon } from "./visual/icons"; import { getIcon } from "./visual/icons";
@ -332,6 +332,12 @@ export class FilesPanel extends React.Component<Props, State, {}> {
}); });
}; };
updateItems = (items: Object) => {
const metadataResps = items as List<MetadataResp>;
updater().updateItems(metadataResps);
this.props.update(updater().updateFilesInfo);
};
render() { render() {
const showOp = this.props.login.userRole === roleVisitor ? "hidden" : ""; const showOp = this.props.login.userRole === roleVisitor ? "hidden" : "";
const breadcrumb = this.props.filesInfo.dirPath.map( const breadcrumb = this.props.filesInfo.dirPath.map(
@ -381,18 +387,18 @@ export class FilesPanel extends React.Component<Props, State, {}> {
</div> </div>
); );
const sortedItems = this.props.filesInfo.items.sort( // const sortedItems = this.props.filesInfo.items.sort(
(item1: MetadataResp, item2: MetadataResp) => { // (item1: MetadataResp, item2: MetadataResp) => {
if (item1.isDir && !item2.isDir) { // if (item1.isDir && !item2.isDir) {
return -1; // return -1;
} else if (!item1.isDir && item2.isDir) { // } else if (!item1.isDir && item2.isDir) {
return 1; // return 1;
} // }
return 0; // return 0;
} // }
); // );
const items = sortedItems.map((item: MetadataResp) => { const items = this.props.filesInfo.items.map((item: MetadataResp) => {
const isSelected = this.state.selectedItems.has(item.name); const isSelected = this.state.selectedItems.has(item.name);
const dirPath = this.props.filesInfo.dirPath.join("/"); const dirPath = this.props.filesInfo.dirPath.join("/");
const itemPath = dirPath.endsWith("/") const itemPath = dirPath.endsWith("/")
@ -412,7 +418,10 @@ export class FilesPanel extends React.Component<Props, State, {}> {
const content = item.isDir ? ( const content = item.isDir ? (
<div className={`v-mid item-cell`}> <div className={`v-mid item-cell`}>
<div className="full-width"> <div className="full-width">
<div className="title-m clickable" onClick={() => this.gotoChild(item.name)}> <div
className="title-m clickable"
onClick={() => this.gotoChild(item.name)}
>
{item.name} {item.name}
</div> </div>
<div className="desc-m grey0-font"> <div className="desc-m grey0-font">
@ -486,9 +495,35 @@ export class FilesPanel extends React.Component<Props, State, {}> {
</div> </div>
); );
return List([icon, content, op]); 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,
},
]);
const usedSpace = FileSize(parseInt(this.props.login.usedSpace, 10), { const usedSpace = FileSize(parseInt(this.props.login.usedSpace, 10), {
round: 0, round: 0,
}); });
@ -499,14 +534,6 @@ export class FilesPanel extends React.Component<Props, State, {}> {
} }
); );
const tableTitles = List([
<div className="font-s grey0-font">
<RiFileList2Fill size="3rem" className="black-font" />
</div>,
<div className="font-s grey0-font">Name</div>,
<div className="font-s grey0-font">Action</div>,
]);
const itemListPane = ( const itemListPane = (
<div> <div>
<div className={showOp}> <div className={showOp}>
@ -615,6 +642,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
head={tableTitles} head={tableTitles}
foot={List()} foot={List()}
rows={items} rows={items}
updateRows={this.updateItems}
/> />
</Container> </Container>
</div> </div>

View file

@ -247,6 +247,10 @@ export class Updater {
return false; return false;
}; };
updateItems = (items: List<MetadataResp>) => {
this.props.filesInfo.items = items;
};
moveHere = async ( moveHere = async (
srcDir: string, srcDir: string,
dstDir: string, dstDir: string,