feat(fe/table): support sorting
This commit is contained in:
parent
ee0fcb5691
commit
d66b854f83
3 changed files with 243 additions and 70 deletions
|
@ -1,60 +1,201 @@
|
|||
import * as React from "react";
|
||||
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 {
|
||||
head: List<React.ReactNode>;
|
||||
rows: List<List<React.ReactNode>>;
|
||||
head: List<Head>;
|
||||
rows: List<Row>;
|
||||
foot: List<React.ReactNode>;
|
||||
colStyles?: List<React.CSSProperties>;
|
||||
id?: string;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
originalVals?: List<Object>;
|
||||
updateRows?: (rows: Object) => void;
|
||||
}
|
||||
|
||||
export const Table = (props: Props) => {
|
||||
const headCols = props.head.map(
|
||||
(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>
|
||||
);
|
||||
}
|
||||
);
|
||||
export interface State {
|
||||
orders: List<boolean>; // asc = true, desc = false
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
export class Table extends React.Component<Props, State, {}> {
|
||||
constructor(p: Props) {
|
||||
super(p);
|
||||
this.state = {
|
||||
// TODO: if the size of column increases
|
||||
// state will be out of sync with props
|
||||
// so this will not work
|
||||
orders: p.head.map((_, i: number) => {
|
||||
return i === 0; // order by the first column
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
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>
|
||||
// );
|
||||
// };
|
||||
|
|
|
@ -15,7 +15,7 @@ import { LoginProps } from "./pane_login";
|
|||
import { MetadataResp, roleVisitor, roleAdmin } from "../client";
|
||||
import { Flexbox } from "./layout/flexbox";
|
||||
import { Container } from "./layout/container";
|
||||
import { Table } from "./layout/table";
|
||||
import { Table, Cell, Head } from "./layout/table";
|
||||
import { Up } from "../worker/upload_mgr";
|
||||
import { UploadEntry, UploadState } from "../worker/interface";
|
||||
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() {
|
||||
const showOp = this.props.login.userRole === roleVisitor ? "hidden" : "";
|
||||
const breadcrumb = this.props.filesInfo.dirPath.map(
|
||||
|
@ -381,18 +387,18 @@ export class FilesPanel extends React.Component<Props, State, {}> {
|
|||
</div>
|
||||
);
|
||||
|
||||
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 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 items = sortedItems.map((item: MetadataResp) => {
|
||||
const items = this.props.filesInfo.items.map((item: MetadataResp) => {
|
||||
const isSelected = this.state.selectedItems.has(item.name);
|
||||
const dirPath = this.props.filesInfo.dirPath.join("/");
|
||||
const itemPath = dirPath.endsWith("/")
|
||||
|
@ -412,7 +418,10 @@ export class FilesPanel extends React.Component<Props, State, {}> {
|
|||
const content = item.isDir ? (
|
||||
<div className={`v-mid item-cell`}>
|
||||
<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}
|
||||
</div>
|
||||
<div className="desc-m grey0-font">
|
||||
|
@ -486,9 +495,35 @@ export class FilesPanel extends React.Component<Props, State, {}> {
|
|||
</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), {
|
||||
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 = (
|
||||
<div>
|
||||
<div className={showOp}>
|
||||
|
@ -615,6 +642,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
|
|||
head={tableTitles}
|
||||
foot={List()}
|
||||
rows={items}
|
||||
updateRows={this.updateItems}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
|
|
|
@ -247,6 +247,10 @@ export class Updater {
|
|||
return false;
|
||||
};
|
||||
|
||||
updateItems = (items: List<MetadataResp>) => {
|
||||
this.props.filesInfo.items = items;
|
||||
};
|
||||
|
||||
moveHere = async (
|
||||
srcDir: string,
|
||||
dstDir: string,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue