diff --git a/src/client/web/src/common/utils.ts b/src/client/web/src/common/utils.ts index cb3a205..b411a6a 100644 --- a/src/client/web/src/common/utils.ts +++ b/src/client/web/src/common/utils.ts @@ -1,6 +1,11 @@ import { List, Map } from "immutable"; -import { Row } from "../components/layout/rows"; + +export interface Row { + // elem: React.ReactNode; // element to display + val: Object; // original object value + sortVals: List; // sortable values in order +} export function getItemPath(dirPath: string, itemName: string): string { return dirPath.endsWith("/") diff --git a/src/client/web/src/components/control/btn_list.tsx b/src/client/web/src/components/control/btn_list.tsx new file mode 100644 index 0000000..f3d365d --- /dev/null +++ b/src/client/web/src/components/control/btn_list.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import { List } from "immutable"; + +import { Flexbox } from "../layout/flexbox"; +import { getIconWithProps } from "../visual/icons"; + +export type BtnListCallBack = () => void; +export interface Props { + titleIcon?: string; + btnNames: List; + btnCallbacks: List; +} + +export const BtnList = (props: Props) => { + const titleIcon = + props.titleIcon != null ? ( + getIconWithProps(props.titleIcon, { + size: "3rem", + className: "black-font margin-r-m", + }) + ) : ( + + ); + + const btns = props.btnNames.map((btnName: string, i: number) => { + const cb = props.btnCallbacks.get(i); + return ( + + ); + }); + + return ( +
+ {btns}])} + childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])} + /> +
+ ); +}; diff --git a/src/client/web/src/components/control/tabs.tsx b/src/client/web/src/components/control/tabs.tsx index 4d48906..2de3148 100644 --- a/src/client/web/src/components/control/tabs.tsx +++ b/src/client/web/src/components/control/tabs.tsx @@ -1,14 +1,11 @@ import * as React from "react"; -import { List, Map, update } from "immutable"; +import { List, Map } from "immutable"; import { updater } from "../state_updater"; -import { getIcon } from "../visual/icons"; import { Flexbox } from "../layout/flexbox"; import { ICoreState, MsgProps, UIProps } from "../core_state"; -import { AdminProps } from "../pane_admin"; -import { LoginProps } from "../pane_login"; import { alertMsg } from "../../common/env"; -import { IconProps } from "../visual/icons"; +import { IconProps, getIcon } from "../visual/icons"; import { colorClass } from "../visual/colors"; const defaultIconProps: IconProps = { @@ -20,8 +17,7 @@ const defaultIconProps: IconProps = { export interface Props { targetControl: string; tabIcons: Map; // option name -> icon name - login: LoginProps; - admin: AdminProps; + titleIcon?: string; ui: UIProps; msg: MsgProps; update?: (updater: (prevState: ICoreState) => ICoreState) => void; @@ -45,6 +41,10 @@ export class Tabs extends React.Component { this.props.targetControl ); + const titleIcon = + this.props.titleIcon != null + ? getIcon(this.props.titleIcon, "2rem", "black") + : null; const options = this.props.ui.control.options.get(this.props.targetControl); const tabs = options.map((option: string) => { const iconProps = this.props.tabIcons.has(option) @@ -66,6 +66,7 @@ export class Tabs extends React.Component { > {titleIcon}, {icon}, {this.props.msg.pkg.get( diff --git a/src/client/web/src/components/core_state.ts b/src/client/web/src/components/core_state.ts index c321a40..d14a393 100644 --- a/src/client/web/src/components/core_state.ts +++ b/src/client/web/src/components/core_state.ts @@ -55,18 +55,29 @@ export function newState(): ICoreState { } export function initState(): ICoreState { + const defaultLanPackage = MsgPackage.get("en_US") + const filesOrderBy = defaultLanPackage.get("item.name"); + const uploadingsOrderBy = defaultLanPackage.get("item.path"); + const sharingsOrderBy = defaultLanPackage.get("item.path"); + return { filesInfo: { dirPath: List([]), items: List([]), isSharing: false, + orderBy: filesOrderBy, + order: true, }, uploadingsInfo: { uploadings: List([]), uploadFiles: List([]), + orderBy: uploadingsOrderBy, + order: true, }, sharingsInfo: { sharings: Map(), + orderBy: sharingsOrderBy, + order: true, }, admin: { users: Map(), @@ -100,7 +111,7 @@ export function initState(): ICoreState { }, msg: { lan: "en_US", - pkg: MsgPackage.get("en_US"), + pkg: defaultLanPackage, }, ui: { isVertical: isVertical(), diff --git a/src/client/web/src/components/dialog_settings.tsx b/src/client/web/src/components/dialog_settings.tsx index 0787974..7aa3bd5 100644 --- a/src/client/web/src/components/dialog_settings.tsx +++ b/src/client/web/src/components/dialog_settings.tsx @@ -52,8 +52,6 @@ export class SettingsDialog extends React.Component { color: "cyan1", }, })} - login={this.props.login} - admin={this.props.admin} ui={this.props.ui} msg={this.props.msg} update={this.props.update} diff --git a/src/client/web/src/components/layout/rows.tsx b/src/client/web/src/components/layout/rows.tsx index 151b1cc..e7b24b9 100644 --- a/src/client/web/src/components/layout/rows.tsx +++ b/src/client/web/src/components/layout/rows.tsx @@ -13,86 +13,86 @@ export interface Row { } export interface Props { - sortKeys: List; // display names in order for sorting - rows: List; + // sortKeys: List; // display names in order for sorting + rows: List; id?: string; style?: React.CSSProperties; className?: string; - updateRows?: (rows: Object) => void; // this is a callback which update state with re-sorted rows + // updateRows?: (rows: Object) => void; // this is a callback which update state with re-sorted rows } -export interface State { - orders: List; // asc = true, desc = false -} +export interface State {} +// // orders: List; // asc = true, desc = false +// } export class Rows extends React.Component { constructor(p: Props) { super(p); - this.state = { - orders: p.sortKeys.map((_: string, i: number) => { - return false; - }), - }; + // this.state = { + // orders: p.sortKeys.map((_: string, i: number) => { + // return false; + // }), + // }; } - sortRows = (key: number) => { - if (this.props.updateRows == null) { - return; - } - const sortOption = this.props.sortKeys.get(key); - if (sortOption == null) { - return; - } - const currentOrder = this.state.orders.get(key); - if (currentOrder == null) { - return; - } - const expectedOrder = !currentOrder; + // sortRows = (key: number) => { + // if (this.props.updateRows == null) { + // return; + // } + // const sortOption = this.props.sortKeys.get(key); + // if (sortOption == null) { + // return; + // } + // const currentOrder = this.state.orders.get(key); + // if (currentOrder == null) { + // return; + // } + // const expectedOrder = !currentOrder; - const sortedRows = sortRows(this.props.rows, key, expectedOrder); - const sortedItems = sortedRows.map((row: Row): Object => { - return row.val; - }); - const newOrders = this.state.orders.set(key, !currentOrder); - this.setState({ orders: newOrders }); - this.props.updateRows(sortedItems); - }; + // const sortedRows = sortRows(this.props.rows, key, expectedOrder); + // const sortedItems = sortedRows.map((row: Row): Object => { + // return row.val; + // }); + // const newOrders = this.state.orders.set(key, !currentOrder); + // this.setState({ orders: newOrders }); + // this.props.updateRows(sortedItems); + // }; render() { - const sortBtns = this.props.sortKeys.map( - (displayName: string, i: number): React.ReactNode => { - return ( - - ); - } - ); + // const sortBtns = this.props.sortKeys.map( + // (displayName: string, i: number): React.ReactNode => { + // return ( + // + // ); + // } + // ); const bodyRows = this.props.rows.map( - (row: Row, i: number): React.ReactNode => { - return
{row.elem}
; + (row: React.ReactNode, i: number): React.ReactNode => { + return
{row}
; } ); - const orderByList = - sortBtns.size > 0 ? ( -
- , - {sortBtns}, - ])} - childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])} - /> -
- ) : null; + // const orderByList = + // sortBtns.size > 0 ? ( + //
+ // , + // {sortBtns}, + // ])} + // childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])} + // /> + //
+ // ) : null; return (
{ style={this.props.style} className={this.props.className} > - {orderByList} + {/* {orderByList} */} {bodyRows}
); diff --git a/src/client/web/src/components/pane_settings.tsx b/src/client/web/src/components/pane_settings.tsx index ac1d4d9..1ddaaa0 100644 --- a/src/client/web/src/components/pane_settings.tsx +++ b/src/client/web/src/components/pane_settings.tsx @@ -168,8 +168,8 @@ export class PaneSettings extends React.Component { } }; - prepareErrorRows = (): List => { - let errRows = List(); + prepareErrorRows = (): List => { + let errRows = List(); ErrorLogger() .readErrs() @@ -180,14 +180,10 @@ export class PaneSettings extends React.Component {
); - const val = clientErr; - const sortVals = List([]); + // const val = clientErr; + // const sortVals = List([]); - errRows = errRows.push({ - elem, - val, - sortVals, - }); + errRows = errRows.push(elem); }); return errRows; @@ -218,7 +214,7 @@ export class PaneSettings extends React.Component {
- + ) : null; diff --git a/src/client/web/src/components/panel_files.tsx b/src/client/web/src/components/panel_files.tsx index 3506d8d..5514739 100644 --- a/src/client/web/src/components/panel_files.tsx +++ b/src/client/web/src/components/panel_files.tsx @@ -23,8 +23,9 @@ import { MetadataResp, roleVisitor, roleAdmin } from "../client"; import { Flexbox } from "./layout/flexbox"; import { Container } from "./layout/container"; import { Table, Cell, Head } from "./layout/table"; +import { BtnList } from "./control/btn_list"; import { Segments } from "./layout/segments"; -import { Rows, Row } from "./layout/rows"; +import { Rows } from "./layout/rows"; import { Up } from "../worker/upload_mgr"; import { UploadEntry, UploadState } from "../worker/interface"; import { getIcon } from "./visual/icons"; @@ -51,6 +52,8 @@ export interface FilesProps { dirPath: List; isSharing: boolean; items: List; + orderBy: string; + order: boolean; } export interface Props { @@ -602,12 +605,7 @@ export class FilesPanel extends React.Component { ? "hidden" : ""; - const sortKeys = List([ - this.props.msg.pkg.get("item.type"), - this.props.msg.pkg.get("item.name"), - ]); - - const rows = sortedItems.map((item: MetadataResp): Row => { + const rows = sortedItems.map((item: MetadataResp): React.ReactNode => { const isSelected = this.state.selectedItems.has(item.name); const dirPath = this.props.filesInfo.dirPath.join("/"); const itemPath = dirPath.endsWith("/") @@ -753,21 +751,16 @@ export class FilesPanel extends React.Component { ); - const sortVals = List([item.isDir ? "d" : "f", itemPath]); - return { - elem, - sortVals, - val: item, - }; + // const sortVals = List([item.isDir ? "d" : "f", itemPath]); + return elem; + // return { + // elem, + // sortVals, + // val: item, + // }; }); - return ( - - ); + return ; }; setView = (opt: string) => { @@ -779,6 +772,11 @@ export class FilesPanel extends React.Component { this.props.update(updater().updateUI); }; + orderBy = (columnName: string) => { + updater().sortFiles(columnName); + this.props.update(updater().updateFilesInfo); + }; + render() { const showEndpoints = this.props.login.userRole === roleAdmin ? "" : "hidden"; @@ -867,6 +865,28 @@ export class FilesPanel extends React.Component { ); + const orderByCallbacks = List([ + () => { + this.orderBy(this.props.msg.pkg.get("item.name")); + }, + () => { + this.orderBy(this.props.msg.pkg.get("item.type")); + }, + () => { + this.orderBy(this.props.msg.pkg.get("item.modTime")); + }, + ]); + const orderByButtons = ( + + ); const viewType = this.props.ui.control.controls.get(filesViewCtrl); const view = viewType === "rows" ? ( @@ -1012,7 +1032,7 @@ export class FilesPanel extends React.Component { />
- + {orderByButtons} {view} diff --git a/src/client/web/src/components/panel_sharings.tsx b/src/client/web/src/components/panel_sharings.tsx index eb7cf95..03bf2a9 100644 --- a/src/client/web/src/components/panel_sharings.tsx +++ b/src/client/web/src/components/panel_sharings.tsx @@ -4,6 +4,7 @@ import { List, Map } from "immutable"; import { RiShareBoxLine } from "@react-icons/all-files/ri/RiShareBoxLine"; import { RiCloudOffFill } from "@react-icons/all-files/ri/RiCloudOffFill"; +import { BtnList } from "./control/btn_list"; import { QRCodeIcon } from "./visual/qrcode"; import { getErrMsg } from "../common/utils"; import { alertMsg, confirmMsg } from "../common/env"; @@ -19,6 +20,8 @@ import { CronTable } from "../common/cron"; export interface SharingsProps { sharings: Map; + orderBy: string; + order: boolean; } export interface Props { @@ -86,8 +89,8 @@ export class SharingsPanel extends React.Component { } }; - makeRows = (sharings: Map): List => { - const sharingRows = sharings.keySeq().map((dirPath: string) => { + makeRows = (sharings: Map): List => { + const sharingRows = sharings.keySeq().map((dirPath: string):React.ReactNode => { const shareID = sharings.get(dirPath); const sharingURL = `${ document.location.href.split("?")[0] @@ -130,11 +133,12 @@ export class SharingsPanel extends React.Component { ); - return { - elem, - sortVals: List([dirPath]), - val: dirPath, - }; + return elem; + // return { + // elem, + // sortVals: List([dirPath]), + // val: dirPath, + // }; }); return sharingRows.toList(); @@ -151,13 +155,31 @@ export class SharingsPanel extends React.Component { this.props.update(updater().updateSharingsInfo); }; + orderBy = (columnName: string) => { + updater().sortSharings(columnName); + this.props.update(updater().updateSharingsInfo); + }; + render() { + const orderByCallbacks = List([ + () => { + this.orderBy(this.props.msg.pkg.get("item.path")); + }, + ]); + const orderByButtons = ( + + ); + const sharingRows = this.makeRows(this.props.sharingsInfo.sharings); const view = ( ); const noSharingView = ( @@ -212,6 +234,7 @@ export class SharingsPanel extends React.Component { className="margin-b-l" /> + {orderByButtons} {view} ); diff --git a/src/client/web/src/components/panel_uploadings.tsx b/src/client/web/src/components/panel_uploadings.tsx index 2231cee..2495868 100644 --- a/src/client/web/src/components/panel_uploadings.tsx +++ b/src/client/web/src/components/panel_uploadings.tsx @@ -5,6 +5,7 @@ import FileSize from "filesize"; import { RiUploadCloudFill } from "@react-icons/all-files/ri/RiUploadCloudFill"; import { RiCloudOffFill } from "@react-icons/all-files/ri/RiCloudOffFill"; +import { BtnList } from "./control/btn_list"; import { alertMsg } from "../common/env"; import { getErrMsg } from "../common/utils"; import { updater } from "./state_updater"; @@ -20,6 +21,8 @@ import { HotkeyHandler } from "../common/hotkeys"; export interface UploadingsProps { uploadings: List; uploadFiles: List; + orderBy: string; + order: boolean; } export interface Props { uploadingsInfo: UploadingsProps; @@ -85,8 +88,8 @@ export class UploadingsPanel extends React.Component { this.props.update(updater().updateUploadingsInfo); }; - makeRowsInputs = (uploadings: List): List => { - const uploadingRows = uploadings.map((uploading: UploadEntry) => { + makeRowsInputs = (uploadings: List): List => { + return uploadings.map((uploading: UploadEntry) => { const pathParts = uploading.filePath.split("/"); const fileName = pathParts[pathParts.length - 1]; const progress = @@ -171,15 +174,9 @@ export class UploadingsPanel extends React.Component { ); // file path - const sortVals = List([uploading.filePath]); - return { - elem, - sortVals, - val: uploading, - }; + // const sortVals = List([uploading.filePath]); + return elem; }); - - return uploadingRows; }; updateUploadings = (uploadings: Object) => { @@ -188,16 +185,34 @@ export class UploadingsPanel extends React.Component { this.props.update(updater().updateUploadingsInfo); }; + orderBy = (columnName: string) => { + updater().sortUploadings(columnName); + this.props.update(updater().updateUploadingsInfo); + }; + render() { + const orderByCallbacks = List([ + () => { + this.orderBy(this.props.msg.pkg.get("item.path")); + }, + ]); + const orderByButtons = ( + + ); + const uploadingRows = this.makeRowsInputs( this.props.uploadingsInfo.uploadings ); - const sortKeys = List([this.props.msg.pkg.get("item.path")]); + // const sortKeys = List([this.props.msg.pkg.get("item.path")]); const view = ( ); @@ -254,6 +269,7 @@ export class UploadingsPanel extends React.Component { ])} /> + {orderByButtons} {view} ); diff --git a/src/client/web/src/components/root_frame.tsx b/src/client/web/src/components/root_frame.tsx index e0a1caa..feb698a 100644 --- a/src/client/web/src/components/root_frame.tsx +++ b/src/client/web/src/components/root_frame.tsx @@ -101,8 +101,6 @@ export class RootFrame extends React.Component { color: "cyan1", }, })} - login={this.props.login} - admin={this.props.admin} ui={this.props.ui} msg={this.props.msg} update={this.props.update} diff --git a/src/client/web/src/components/state_updater.ts b/src/client/web/src/components/state_updater.ts index dd0967d..a82a854 100644 --- a/src/client/web/src/components/state_updater.ts +++ b/src/client/web/src/components/state_updater.ts @@ -1,7 +1,7 @@ import { List, Map, Set } from "immutable"; import { ICoreState } from "./core_state"; -import { getItemPath, sortRows } from "../common/utils"; +import { getItemPath, sortRows, Row } from "../common/utils"; import { User, ListUsersResp, @@ -241,7 +241,7 @@ export class Updater { const status = await this.setItems(this.props.filesInfo.dirPath); if (status !== "") { return status; - }; + } // TODO: this part is duplicated in the panel_files.tsx const sortKeys = List([ @@ -865,6 +865,100 @@ export class Updater { return ""; }; + setFilesOrderBy = (orderBy: string, order: boolean) => { + this.props.filesInfo.orderBy = orderBy; + this.props.filesInfo.order = order; + }; + + setUploadingsOrderBy = (orderBy: string, order: boolean) => { + this.props.uploadingsInfo.orderBy = orderBy; + this.props.uploadingsInfo.order = order; + }; + + setSharingsOrderBy = (orderBy: string, order: boolean) => { + this.props.sharingsInfo.orderBy = orderBy; + this.props.sharingsInfo.order = order; + }; + + sortFiles = (columnName: string) => { + let orderByKey = 0; + switch (columnName) { + case this.props.msg.pkg.get("item.name"): + orderByKey = 0; + break; + case this.props.msg.pkg.get("item.type"): + orderByKey = 1; + break; + default: + orderByKey = 2; + } + const order = + this.props.filesInfo.orderBy === columnName + ? !this.props.filesInfo.order + : true; + const rows = this.props.filesInfo.items.map((item: MetadataResp): Row => { + return { + val: item, + sortVals: List([item.name, item.isDir ? "d" : "f", item.modTime]), + }; + }); + + const sortedFiles = sortRows(rows, orderByKey, order).map( + (row): MetadataResp => { + return row.val as MetadataResp; + } + ); + + this.setFilesOrderBy(columnName, order); + this.updateItems(sortedFiles); + }; + + sortUploadings = (columnName: string) => { + const orderByKey = 0; + const order = !this.props.uploadingsInfo.order; + const rows = this.props.uploadingsInfo.uploadings.map( + (uploading: UploadEntry): Row => { + return { + val: uploading, + sortVals: List([uploading.filePath]), + }; + } + ); + + const sorted = sortRows(rows, orderByKey, order).map((row) => { + return row.val as UploadEntry; + }); + + this.setUploadingsOrderBy(columnName, order); + this.updateUploadings(sorted); + }; + + sortSharings = (columnName: string) => { + const orderByKey = 0; + const order = !this.props.sharingsInfo.order; + const rows = this.props.sharingsInfo.sharings + .keySeq() + .map((sharingPath: string): Row => { + return { + val: sharingPath, + sortVals: List([sharingPath]), + }; + }) + .toList(); + + let sorted = Map(); + sortRows(rows, orderByKey, order).forEach((row) => { + const sharingPath = row.val as string; + sorted = sorted.set( + sharingPath, + this.props.sharingsInfo.sharings.get(sharingPath) + ); + }); + + this.setSharingsOrderBy(columnName, order); + this.updateSharings(sorted); + }; + updateAll = (prevState: ICoreState): ICoreState => { return { filesInfo: { ...prevState.filesInfo, ...this.props.filesInfo }, diff --git a/src/client/web/src/components/visual/icons.tsx b/src/client/web/src/components/visual/icons.tsx index 23ec290..f50eae6 100644 --- a/src/client/web/src/components/visual/icons.tsx +++ b/src/client/web/src/components/visual/icons.tsx @@ -19,6 +19,7 @@ import { BiTable } from "@react-icons/all-files/bi/BiTable"; import { BiListUl } from "@react-icons/all-files/bi/BiListUl"; import { RiMore2Fill } from "@react-icons/all-files/ri/RiMore2Fill"; import { RiCheckboxBlankLine } from "@react-icons/all-files/ri/RiCheckboxBlankLine"; +import { BiSortUp } from "@react-icons/all-files/bi/BiSortUp"; import { colorClass } from "./colors"; @@ -46,6 +47,7 @@ const icons = Map({ BiListUl: BiListUl, RiMore2Fill: RiMore2Fill, RiCheckboxBlankLine: RiCheckboxBlankLine, + BiSortUp: BiSortUp, }); export function getIconWithProps(