feat(fe/rows): add rows view into files panel

This commit is contained in:
hexxa 2021-12-21 20:04:57 +08:00 committed by Hexxa
parent 87832ee1b2
commit 3550a3a77d
11 changed files with 535 additions and 130 deletions

View file

@ -135,7 +135,7 @@
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.theme-default #browser-op button { .theme-default #browser-op .left {
margin: 0 1rem 0 0; margin: 0 1rem 0 0;
} }
@ -163,6 +163,36 @@
padding: 0; padding: 0;
} }
.theme-default #item-rows {
margin-top: 2rem;
}
.theme-default #item-rows .name, .theme-default #item-rows .name a {
color: #34495e;
font-size: 1.8rem;
line-height: 2rem;
display: block;
margin: 2rem 0;
}
.theme-default #item-rows .desc {
color: #95a5a6;
font-size: 1.4rem;
line-height: 2rem;
display: block;
}
.theme-default #item-rows .hr {
height: 1px;
margin: 2rem 0;
background-color: #ecf0f1;
}
.theme-default #item-rows .card {
padding: 0.5rem 0;
text-align: left;
}
.theme-default .item-cell { .theme-default .item-cell {
height: 5rem; height: 5rem;
} }
@ -349,7 +379,7 @@
color: #34495e; color: #34495e;
background-color: #ecf0f6; background-color: #ecf0f6;
border: solid 1px #95a5a6; border: solid 1px #95a5a6;
margin: 0.5rem 0 1rem 0; /* margin: 0.5rem 0 1rem 0; */
} }
.captcha { .captcha {

View file

@ -55,6 +55,7 @@
"react": "^16.8.6", "react": "^16.8.6",
"react-copy-to-clipboard": "^5.0.1", "react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-icons": "4.3.1",
"react-svg": "^8.0.6", "react-svg": "^8.0.6",
"throttle-debounce": "^2.1.0", "throttle-debounce": "^2.1.0",
"webpack-bundle-analyzer": "^4.4.2", "webpack-bundle-analyzer": "^4.4.2",

View file

@ -18,6 +18,7 @@ import { MockSettingsClient } from "../../client/settings_mock";
import { controlName as panelTabs } from "../root_frame"; import { controlName as panelTabs } from "../root_frame";
import { settingsDialogCtrl } from "../layers"; import { settingsDialogCtrl } from "../layers";
import { settingsTabsCtrl } from "../dialog_settings"; import { settingsTabsCtrl } from "../dialog_settings";
import { filesViewCtrl } from "../panel_files";
describe("Login", () => { describe("Login", () => {
initMockWorker(); initMockWorker();
@ -122,6 +123,7 @@ describe("Login", () => {
[settingsDialogCtrl]: ctrlOff, [settingsDialogCtrl]: ctrlOff,
[settingsTabsCtrl]: "preferencePane", [settingsTabsCtrl]: "preferencePane",
[sharingCtrl]: ctrlOff, [sharingCtrl]: ctrlOff,
[filesViewCtrl]: "rows",
}), }),
options: Map<string, Set<string>>({ options: Map<string, Set<string>>({
[panelTabs]: Set<string>([ [panelTabs]: Set<string>([
@ -132,6 +134,7 @@ describe("Login", () => {
[settingsDialogCtrl]: Set<string>([ctrlOn, ctrlOff]), [settingsDialogCtrl]: Set<string>([ctrlOn, ctrlOff]),
[settingsTabsCtrl]: Set<string>(["preferencePane", "managementPane"]), [settingsTabsCtrl]: Set<string>(["preferencePane", "managementPane"]),
[sharingCtrl]: Set<string>([ctrlOn, ctrlOff]), [sharingCtrl]: Set<string>([ctrlOn, ctrlOff]),
[filesViewCtrl]: Set<string>(["rows", "table"]),
}), }),
}, },
}); });

View file

@ -4,6 +4,7 @@ import { UploadEntry } from "../worker/interface";
import { MsgPackage } from "../i18n/msger"; import { MsgPackage } from "../i18n/msger";
import { User, MetadataResp } from "../client"; import { User, MetadataResp } from "../client";
import { settingsDialogCtrl } from "./layers"; import { settingsDialogCtrl } from "./layers";
import { filesViewCtrl } from "./panel_files";
import { FilesProps } from "./panel_files"; import { FilesProps } from "./panel_files";
import { UploadingsProps } from "./panel_uploadings"; import { UploadingsProps } from "./panel_uploadings";
import { SharingsProps } from "./panel_sharings"; import { SharingsProps } from "./panel_sharings";
@ -112,6 +113,7 @@ export function initState(): ICoreState {
[settingsDialogCtrl]: "off", [settingsDialogCtrl]: "off",
[settingsTabsCtrl]: "preferencePane", [settingsTabsCtrl]: "preferencePane",
[sharingCtrl]: "off", [sharingCtrl]: "off",
[filesViewCtrl]: "rows",
}), }),
options: Map<string, Set<string>>({ options: Map<string, Set<string>>({
[panelTabs]: Set<string>([ [panelTabs]: Set<string>([
@ -122,6 +124,7 @@ export function initState(): ICoreState {
[settingsDialogCtrl]: Set<string>(["on", "off"]), [settingsDialogCtrl]: Set<string>(["on", "off"]),
[settingsTabsCtrl]: Set<string>(["preferencePane", "managementPane"]), [settingsTabsCtrl]: Set<string>(["preferencePane", "managementPane"]),
[sharingCtrl]: Set<string>(["on", "off"]), [sharingCtrl]: Set<string>(["on", "off"]),
[filesViewCtrl]: Set<string>(["rows", "table"]),
}), }),
}, },
}, },

View file

@ -0,0 +1,123 @@
import * as React from "react";
import { List, Map, Set } from "immutable";
import { RiArrowUpDownFill } from "@react-icons/all-files/ri/RiArrowUpDownFill";
import { Flexbox } from "./flexbox";
export interface Row {
elem: React.ReactNode; // element to display
val: Object; // original object value
sortVals: List<string>; // sortable values in order
}
export interface Props {
sortKeys: List<string>; // display names in order for sorting
rows: List<Row>;
id?: string;
style?: React.CSSProperties;
className?: string;
updateRows?: (rows: Object) => void; // this is a callback which update state with re-sorted rows
}
export interface State {
orders: List<boolean>; // asc = true, desc = false
}
export class Rows extends React.Component<Props, State, {}> {
constructor(p: Props) {
super(p);
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;
const sortedRows = this.props.rows.sort((row1: Row, row2: Row) => {
const val1 = row1.sortVals.get(key);
const val2 = row2.sortVals.get(key);
if (val1 == null || val2 == null) {
// elements without the sort key will be moved to the last
if (val1 == null && val2 != null) {
return 1;
} else if (val1 != null && val2 == null) {
return -1;
}
return 0;
} else if (val1 < val2) {
return expectedOrder ? -1 : 1;
} else if (val1 === val2) {
return 0;
}
return expectedOrder ? 1 : -1;
});
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 (
<button
key={`rows-${i}`}
className="float"
onClick={() => {
this.sortRows(i);
}}
>
{displayName}
</button>
);
}
);
const bodyRows = this.props.rows.map(
(row: Row, i: number): React.ReactNode => {
return <div key={`rows-r-${i}`}>{row.elem}</div>;
}
);
return (
<div
id={this.props.id}
style={this.props.style}
className={this.props.className}
>
<div className="margin-b-l">
<Flexbox
children={List([
<RiArrowUpDownFill
size="3rem"
className="black-font margin-r-m"
/>,
<span>{sortBtns}</span>,
])}
childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])}
/>
</div>
{bodyRows}
</div>
);
}
}

View file

@ -7,6 +7,11 @@ import { RiFolder2Fill } from "@react-icons/all-files/ri/RiFolder2Fill";
import { RiArchiveDrawerFill } from "@react-icons/all-files/ri/RiArchiveDrawerFill"; import { RiArchiveDrawerFill } from "@react-icons/all-files/ri/RiArchiveDrawerFill";
import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill"; import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";
import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill"; import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill";
import { RiCheckboxFill } from "@react-icons/all-files/ri/RiCheckboxFill";
import { RiCheckboxBlankFill } from "@react-icons/all-files/ri/RiCheckboxBlankFill";
import { RiInformationFill } from "@react-icons/all-files/ri/RiInformationFill";
import { BiTable } from "@react-icons/all-files/bi/BiTable";
import { BiListUl } from "@react-icons/all-files/bi/BiListUl";
import { alertMsg, confirmMsg } from "../common/env"; import { alertMsg, confirmMsg } from "../common/env";
import { getErrMsg } from "../common/utils"; import { getErrMsg } from "../common/utils";
@ -17,10 +22,13 @@ 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, Cell, Head } from "./layout/table"; import { Table, Cell, Head } from "./layout/table";
import { Rows, Row } from "./layout/rows";
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";
export const filesViewCtrl = "filesView";
export interface Item { export interface Item {
name: string; name: string;
size: number; size: number;
@ -396,74 +404,10 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
}; };
render() { prepareTable = (
const showOp = this.props.login.userRole === roleVisitor ? "hidden" : ""; sortedItems: List<MetadataResp>,
const breadcrumb = this.props.filesInfo.dirPath.map( showOp: string
(pathPart: string, key: number) => { ): React.ReactNode => {
return (
<button
key={pathPart}
onClick={() =>
this.chdir(this.props.filesInfo.dirPath.slice(0, key + 1))
}
className="item"
>
<span className="content">{pathPart}</span>
</button>
);
}
);
const ops = (
<div id="upload-op">
<Flexbox
children={List([
<div>
<button onClick={this.onMkDir} className="float cyan-btn">
{this.props.msg.pkg.get("browser.folder.add")}
</button>
<input
type="text"
onChange={this.onNewFolderNameChange}
value={this.state.newFolderName}
placeholder={this.props.msg.pkg.get("browser.folder.name")}
className="float"
/>
</div>,
<div>
<button onClick={this.onClickUpload} className="cyan-btn">
{this.props.msg.pkg.get("browser.upload")}
</button>
<input
type="file"
onChange={this.addUploads}
multiple={true}
value={this.state.uploadFiles}
ref={this.assignInput}
className="hidden"
/>
</div>,
])}
childrenStyles={List([
{ flex: "0 0 70%" },
{ flex: "0 0 30%", justifyContent: "flex-end" },
])}
/>
</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 items = sortedItems.map((item: MetadataResp) => { const items = sortedItems.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("/");
@ -590,6 +534,243 @@ export class FilesPanel extends React.Component<Props, State, {}> {
}, },
]); ]);
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 = (
sortedItems: List<MetadataResp>,
showOp: string
): React.ReactNode => {
const sortKeys = List<string>([
this.props.msg.pkg.get("item.type"),
this.props.msg.pkg.get("item.name"),
]);
const rows = sortedItems.map((item: MetadataResp): Row => {
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 selectedIconColor = isSelected ? "cyan0-font" : "grey0-font";
const descIconColor = this.state.showDetail.has(item.name)
? "cyan0-font"
: "grey0-font";
const icon = item.isDir ? (
<RiFolder2Fill size="1.8rem" className="yellow0-font" />
) : (
<RiFile2Fill size="1.8rem" className="cyan0-font" />
);
const fileType = item.isDir
? this.props.msg.pkg.get("item.type.folder")
: this.props.msg.pkg.get("item.type.file");
const name = item.isDir ? (
<span className="clickable" onClick={() => this.gotoChild(item.name)}>
{item.name}
</span>
) : (
<a
className="title-m clickable"
href={`/v1/fs/files?fp=${itemPath}`}
target="_blank"
>
{item.name}
</a>
);
const op = item.isDir ? (
<div className={`v-mid item-op ${showOp}`}>
<RiCheckboxFill
size="1.8rem"
className={selectedIconColor}
onClick={() => this.select(item.name)}
/>
</div>
) : (
<div className={`v-mid item-op ${showOp}`}>
<RiInformationFill
size="1.8rem"
className={`${descIconColor} margin-r-m`}
onClick={() => this.toggleDetail(item.name)}
/>
<RiCheckboxFill
size="1.8rem"
className={selectedIconColor}
onClick={() => this.select(item.name)}
/>
</div>
);
const pathTitle = this.props.msg.pkg.get("item.path");
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
? `${pathTitle}: ${itemPath} | ${modTimeTitle}: ${item.modTime}`
: `${pathTitle}: ${itemPath} | ${modTimeTitle}: ${item.modTime} | ${sizeTitle}: ${itemSize} | sha1: ${item.sha1}`;
const details = (
<div>
<div className="card">
<span className="title-m black-font">{pathTitle}</span>
<span>{itemPath}</span>
</div>
<div className="card">
<span className="title-m black-font">{modTimeTitle}</span>
<span>{item.modTime}</span>
</div>
<div className="card">
<span className="title-m black-font">{sizeTitle}</span>
<span>{itemSize}</span>
</div>
<div className="card">
<span className="title-m black-font">SHA1</span>
<Flexbox
children={List([
<input type="text" readOnly={true} value={`${item.sha1}`} />,
<button onClick={() => this.generateHash(itemPath)}>
{this.props.msg.pkg.get("refresh")}
</button>,
])}
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
/>
</div>
</div>
);
const desc = this.state.showDetail.has(item.name) ? details : compact;
const elem = (
<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>
<div className="hr"></div>
</div>
);
const sortVals = List<string>([item.isDir ? "d" : "f", itemPath]);
return {
elem,
sortVals,
val: item,
};
});
return (
<Rows
sortKeys={sortKeys}
rows={List(rows)}
updateRows={this.updateItems}
/>
);
};
setView = (opt: string) => {
if (opt === "rows" || opt === "table") {
updater().setControlOption(filesViewCtrl, opt);
this.props.update(updater().updateUI);
return;
}
// TODO: log error
};
render() {
const showOp = this.props.login.userRole === roleVisitor ? "hidden" : "";
const breadcrumb = this.props.filesInfo.dirPath.map(
(pathPart: string, key: number) => {
return (
<button
key={pathPart}
onClick={() =>
this.chdir(this.props.filesInfo.dirPath.slice(0, key + 1))
}
className="item"
>
<span className="content">{pathPart}</span>
</button>
);
}
);
const ops = (
<div id="upload-op">
<Flexbox
children={List([
<div>
<button onClick={this.onMkDir} className="float cyan-btn">
{this.props.msg.pkg.get("browser.folder.add")}
</button>
<input
type="text"
onChange={this.onNewFolderNameChange}
value={this.state.newFolderName}
placeholder={this.props.msg.pkg.get("browser.folder.name")}
className="float"
/>
</div>,
<div>
<button onClick={this.onClickUpload} className="cyan-btn">
{this.props.msg.pkg.get("browser.upload")}
</button>
<input
type="file"
onChange={this.addUploads}
multiple={true}
value={this.state.uploadFiles}
ref={this.assignInput}
className="hidden"
/>
</div>,
])}
childrenStyles={List([
{ flex: "0 0 70%" },
{ flex: "0 0 30%", justifyContent: "flex-end" },
])}
/>
</div>
);
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 usedSpace = FileSize(parseInt(this.props.login.usedSpace, 10), { const usedSpace = FileSize(parseInt(this.props.login.usedSpace, 10), {
round: 0, round: 0,
}); });
@ -619,7 +800,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.props.filesInfo.dirPath.join("/") this.props.filesInfo.dirPath.join("/")
); );
}} }}
className="red-btn" className="red-btn left"
> >
{this.props.msg.pkg.get("browser.share.del")} {this.props.msg.pkg.get("browser.share.del")}
</button> </button>
@ -627,7 +808,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
<button <button
type="button" type="button"
onClick={this.addSharing} onClick={this.addSharing}
className="cyan-btn" className="cyan-btn left"
> >
{this.props.msg.pkg.get("browser.share.add")} {this.props.msg.pkg.get("browser.share.add")}
</button> </button>
@ -640,7 +821,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
<button <button
type="button" type="button"
onClick={() => this.delete()} onClick={() => this.delete()}
className="red-btn" className="red-btn left"
> >
{this.props.msg.pkg.get("browser.delete")} {this.props.msg.pkg.get("browser.delete")}
</button> </button>
@ -652,14 +833,33 @@ export class FilesPanel extends React.Component<Props, State, {}> {
) : null} ) : null}
</span>, </span>,
<span> <Flexbox
<span children={List([
id="space-used" <BiListUl
className="desc-m grey0-font" size="1.4rem"
>{`${this.props.msg.pkg.get( className="black-font margin-r-s"
"browser.used" onClick={() => {
)} ${usedSpace} / ${spaceLimit}`}</span> this.setView("rows");
</span>, }}
/>,
<BiTable
size="1.4rem"
className="black-font margin-r-s"
onClick={() => {
this.setView("table");
}}
/>,
<span className={`${showOp}`}>
<button
onClick={() => this.selectAll()}
className="select-btn"
>
{this.props.msg.pkg.get("browser.selectAll")}
</button>
</span>,
])}
/>,
])} ])}
childrenStyles={List([ childrenStyles={List([
{ flex: "0 0 auto" }, { flex: "0 0 auto" },
@ -689,27 +889,19 @@ export class FilesPanel extends React.Component<Props, State, {}> {
/> />
</span>, </span>,
<span className={`${showOp}`}> <span>
<button onClick={() => this.selectAll()} className="select-btn"> <span
{this.props.msg.pkg.get("browser.selectAll")} id="space-used"
</button> className="desc-m grey0-font"
>{`${this.props.msg.pkg.get(
"browser.used"
)} ${usedSpace} / ${spaceLimit}`}</span>
</span>, </span>,
])} ])}
childrenStyles={List([{}, { justifyContent: "flex-end" }])} childrenStyles={List([{}, { justifyContent: "flex-end" }])}
/> />
<Table {view}
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}
/>
</Container> </Container>
</div> </div>
); );

View file

@ -313,15 +313,37 @@ export class Updater {
const isAuthed = this.props.login.authed; const isAuthed = this.props.login.authed;
const isSharing = const isSharing =
this.props.ui.control.controls.get(sharingCtrl) === ctrlOn; this.props.ui.control.controls.get(sharingCtrl) === ctrlOn;
const leftControls = this.props.ui.control.controls.filter(
(_: string, key: string): boolean => {
return (
key !== panelTabs &&
key !== settingsDialogCtrl &&
key !== settingsTabsCtrl &&
key !== sharingCtrl
);
}
);
const leftOpts = this.props.ui.control.options.filter(
(_: Set<string>, key: string): boolean => {
return (
key !== panelTabs &&
key !== settingsDialogCtrl &&
key !== settingsTabsCtrl &&
key !== sharingCtrl
);
}
);
let newControls: Map<string, string> = undefined;
let newOptions: Map<string, Set<string>> = undefined;
if (isAuthed) { if (isAuthed) {
this.props.ui.control.controls = Map<string, string>({ newControls = Map<string, string>({
[panelTabs]: "filesPanel", [panelTabs]: "filesPanel",
[settingsDialogCtrl]: ctrlOff, [settingsDialogCtrl]: ctrlOff,
[settingsTabsCtrl]: "preferencePane", [settingsTabsCtrl]: "preferencePane",
[sharingCtrl]: isSharing ? ctrlOn : ctrlOff, [sharingCtrl]: isSharing ? ctrlOn : ctrlOff,
}); });
this.props.ui.control.options = Map<string, Set<string>>({ newOptions = Map<string, Set<string>>({
[panelTabs]: Set<string>([ [panelTabs]: Set<string>([
"filesPanel", "filesPanel",
"uploadingsPanel", "uploadingsPanel",
@ -333,33 +355,33 @@ export class Updater {
}); });
if (this.props.login.userRole == roleAdmin) { if (this.props.login.userRole == roleAdmin) {
this.props.ui.control.options = this.props.ui.control.options.set( newOptions = newOptions.set(
settingsTabsCtrl, settingsTabsCtrl,
Set<string>(["preferencePane", "managementPane"]) Set<string>(["preferencePane", "managementPane"])
); );
} }
} else { } else {
if (isSharing) { if (isSharing) {
this.props.ui.control.controls = Map<string, string>({ newControls = Map<string, string>({
[panelTabs]: "filesPanel", [panelTabs]: "filesPanel",
[settingsDialogCtrl]: ctrlHidden, [settingsDialogCtrl]: ctrlHidden,
[settingsTabsCtrl]: ctrlHidden, [settingsTabsCtrl]: ctrlHidden,
[sharingCtrl]: ctrlOn, [sharingCtrl]: ctrlOn,
}); });
this.props.ui.control.options = Map<string, Set<string>>({ newOptions = Map<string, Set<string>>({
[panelTabs]: Set<string>(["filesPanel"]), [panelTabs]: Set<string>(["filesPanel"]),
[settingsDialogCtrl]: Set<string>([ctrlHidden]), [settingsDialogCtrl]: Set<string>([ctrlHidden]),
[settingsTabsCtrl]: Set<string>([ctrlHidden]), [settingsTabsCtrl]: Set<string>([ctrlHidden]),
[sharingCtrl]: Set<string>([ctrlOn]), [sharingCtrl]: Set<string>([ctrlOn]),
}); });
} else { } else {
this.props.ui.control.controls = Map<string, string>({ newControls = Map<string, string>({
[panelTabs]: ctrlHidden, [panelTabs]: ctrlHidden,
[settingsDialogCtrl]: ctrlHidden, [settingsDialogCtrl]: ctrlHidden,
[settingsTabsCtrl]: ctrlHidden, [settingsTabsCtrl]: ctrlHidden,
[sharingCtrl]: ctrlOff, [sharingCtrl]: ctrlOff,
}); });
this.props.ui.control.options = Map<string, Set<string>>({ newOptions = Map<string, Set<string>>({
[panelTabs]: Set<string>([ctrlHidden]), [panelTabs]: Set<string>([ctrlHidden]),
[settingsDialogCtrl]: Set<string>([ctrlHidden]), [settingsDialogCtrl]: Set<string>([ctrlHidden]),
[settingsTabsCtrl]: Set<string>([ctrlHidden]), [settingsTabsCtrl]: Set<string>([ctrlHidden]),
@ -367,6 +389,9 @@ export class Updater {
}); });
} }
} }
this.props.ui.control.controls = newControls.merge(leftControls);
this.props.ui.control.options = newOptions.merge(leftOpts);
}; };
initStateForVisitor = async (): Promise<any> => { initStateForVisitor = async (): Promise<any> => {

View file

@ -14,8 +14,9 @@ import { RiInformationFill } from "@react-icons/all-files/ri/RiInformationFill";
import { RiDeleteBin2Fill } from "@react-icons/all-files/ri/RiDeleteBin2Fill"; import { RiDeleteBin2Fill } from "@react-icons/all-files/ri/RiDeleteBin2Fill";
import { RiArchiveDrawerFill } from "@react-icons/all-files/ri/RiArchiveDrawerFill"; import { RiArchiveDrawerFill } from "@react-icons/all-files/ri/RiArchiveDrawerFill";
import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill"; import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill";
import { RiArrowUpDownFill } from "@react-icons/all-files/ri/RiArrowUpDownFill";
import { BiTable } from "@react-icons/all-files/bi/BiTable";
import { BiListUl } from "@react-icons/all-files/bi/BiListUl";
import { colorClass } from "./colors"; import { colorClass } from "./colors";
@ -38,6 +39,9 @@ const icons = Map<string, IconType>({
RiInformationFill: RiInformationFill, RiInformationFill: RiInformationFill,
RiDeleteBin2Fill: RiDeleteBin2Fill, RiDeleteBin2Fill: RiDeleteBin2Fill,
RiArchiveDrawerFill: RiArchiveDrawerFill, RiArchiveDrawerFill: RiArchiveDrawerFill,
RiArrowUpDownFill: RiArrowUpDownFill,
BiTable: BiTable,
BiListUl: BiListUl,
}); });
export function getIconWithProps( export function getIconWithProps(

View file

@ -118,4 +118,11 @@ export const msgs: Map<string, string> = Map({
"err.server": "The operation failed in the server", "err.server": "The operation failed in the server",
"err.script.cors": "script error with CORS", "err.script.cors": "script error with CORS",
"err.unknown": "unknown error", "err.unknown": "unknown error",
"item.type": "Item Type",
"item.type.folder": "Folder",
"item.type.file": "File",
"item.name": "Item Name",
"item.path": "Path",
"item.modTime": "Mod Time",
"item.size": "Size",
}); });

View file

@ -115,4 +115,11 @@ export const msgs: Map<string, string> = Map({
"err.server": "服务器端操作失败", "err.server": "服务器端操作失败",
"err.script.cors": "跨域脚本错误", "err.script.cors": "跨域脚本错误",
"err.unknown": "未知错误", "err.unknown": "未知错误",
"item.type": "类型",
"item.type.folder": "文件夹",
"item.type.file": "文件",
"item.name": "名称",
"item.path": "路径",
"item.modTime": "修改时间",
"item.size": "大小",
}); });

View file

@ -1149,9 +1149,9 @@
chalk "^4.0.0" chalk "^4.0.0"
"@polka/url@^1.0.0-next.20": "@polka/url@^1.0.0-next.20":
version "1.0.0-next.20" version "1.0.0-next.21"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.20.tgz#111b5db0f501aa89b05076fa31f0ea0e0c292cd3" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
integrity sha512-88p7+M0QGxKpmnkfXjS4V26AnoC/eiqZutE8GLdaI5X12NY75bXSdTY9NkmYb2Xyk1O+MmkuO6Frmsj84V6I8Q== integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
"@react-icons/all-files@^4.1.0": "@react-icons/all-files@^4.1.0":
version "4.1.0" version "4.1.0"
@ -1615,7 +1615,12 @@ acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1: acorn@^8.0.4:
version "8.6.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895"
integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==
acorn@^8.2.4, acorn@^8.4.1:
version "8.5.0" version "8.5.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
@ -2063,12 +2068,7 @@ commander@^4.1.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
commander@^6.2.0: commander@^7.0.0, commander@^7.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commander@^7.0.0:
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
@ -3667,11 +3667,6 @@ mime-types@^2.1.12, mime-types@^2.1.27:
dependencies: dependencies:
mime-db "1.49.0" mime-db "1.49.0"
mime@^2.3.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
mimic-fn@^2.1.0: mimic-fn@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@ -3696,6 +3691,11 @@ mkdirp@^0.5.1:
dependencies: dependencies:
minimist "^1.2.5" minimist "^1.2.5"
mrmime@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b"
integrity sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==
ms@2.1.2: ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -4062,6 +4062,11 @@ react-dom@^16.8.6:
prop-types "^15.6.2" prop-types "^15.6.2"
scheduler "^0.19.1" scheduler "^0.19.1"
react-icons@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"
integrity sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==
react-is@^16.8.1: react-is@^16.8.1:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -4336,12 +4341,12 @@ signal-exit@^3.0.2, signal-exit@^3.0.3:
integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q== integrity sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==
sirv@^1.0.7: sirv@^1.0.7:
version "1.0.17" version "1.0.19"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.17.tgz#86e2c63c612da5a1dace1c16c46f524aaa26ac45" resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49"
integrity sha512-qx9go5yraB7ekT7bCMqUHJ5jEaOC/GXBxUWv+jeWnb7WzHUFdcQPGWk7YmAwFBaQBrogpuSqd/azbC2lZRqqmw== integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==
dependencies: dependencies:
"@polka/url" "^1.0.0-next.20" "@polka/url" "^1.0.0-next.20"
mime "^2.3.1" mrmime "^1.0.0"
totalist "^1.0.0" totalist "^1.0.0"
sisteransi@^1.0.5: sisteransi@^1.0.5:
@ -4855,14 +4860,14 @@ webidl-conversions@^6.1.0:
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
webpack-bundle-analyzer@^4.4.2: webpack-bundle-analyzer@^4.4.2:
version "4.4.2" version "4.5.0"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.2.tgz#39898cf6200178240910d629705f0f3493f7d666" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5"
integrity sha512-PIagMYhlEzFfhMYOzs5gFT55DkUdkyrJi/SxJp8EF3YMWhS+T9vvs2EoTetpk5qb6VsCq02eXTlRDOydRhDFAQ== integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==
dependencies: dependencies:
acorn "^8.0.4" acorn "^8.0.4"
acorn-walk "^8.0.0" acorn-walk "^8.0.0"
chalk "^4.1.0" chalk "^4.1.0"
commander "^6.2.0" commander "^7.2.0"
gzip-size "^6.0.0" gzip-size "^6.0.0"
lodash "^4.17.20" lodash "^4.17.20"
opener "^1.5.2" opener "^1.5.2"
@ -5031,7 +5036,12 @@ write-file-atomic@^3.0.0:
signal-exit "^3.0.2" signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5" typedarray-to-buffer "^3.1.5"
ws@^7.3.1, ws@^7.4.6: ws@^7.3.1:
version "7.5.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@^7.4.6:
version "7.5.5" version "7.5.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==