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 * 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>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue