feat(fe): enable loading during waiting

This commit is contained in:
hexxa 2022-01-23 13:55:22 +08:00 committed by Hexxa
parent 958872731a
commit 5a11463386
5 changed files with 276 additions and 168 deletions

View file

@ -503,11 +503,19 @@
clear: both; clear: both;
} }
.anm-rotate { .anm-rotate-s {
animation: trm-rotate 1s infinite linear; animation: trm-rotate-s 1s infinite linear;
} }
@keyframes trm-rotate { .anm-rotate-m {
animation: trm-rotate-m 1s infinite linear;
}
.anm-rotate-f {
animation: trm-rotate-f 1s infinite linear;
}
@keyframes trm-rotate-f {
20% { 20% {
transform: rotate(72deg); transform: rotate(72deg);
} }
@ -524,3 +532,39 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@keyframes trm-rotate-m {
20% {
transform: rotate(36deg);
}
40% {
transform: rotate(72deg);
}
60% {
transform: rotate(144deg);
}
80% {
transform: rotate(288deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes trm-rotate-s {
20% {
transform: rotate(18deg);
}
40% {
transform: rotate(36deg);
}
60% {
transform: rotate(72deg);
}
80% {
transform: rotate(144deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -513,13 +513,14 @@
.theme-default #loading-container { .theme-default #loading-container {
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
border-radius: 0.6rem; border-radius: 3rem;
padding: 0.5rem; padding: 0.5rem;
position: fixed; position: fixed;
right: 2rem; right: 2rem;
bottom: 2rem; bottom: 2rem;
height: 3rem;
z-index: 201; z-index: 201;
height: 5rem;
width: 5rem;
} }
.theme-default #settings-layer { .theme-default #settings-layer {

View file

@ -1,8 +1,6 @@
import * as React from "react"; import * as React from "react";
import { List } from "immutable"; import { List } from "immutable";
import { RiTimer2Line } from "@react-icons/all-files/ri/RiTimer2Line";
import { updater } from "./state_updater"; import { updater } from "./state_updater";
import { ICoreState, MsgProps, UIProps } from "./core_state"; import { ICoreState, MsgProps, UIProps } from "./core_state";
import { AdminProps } from "./pane_admin"; import { AdminProps } from "./pane_admin";
@ -13,6 +11,7 @@ import { FilesProps } from "./panel_files";
import { Flexbox } from "./layout/flexbox"; import { Flexbox } from "./layout/flexbox";
import { Container } from "./layout/container"; import { Container } from "./layout/container";
import { sharingCtrl, loadingCtrl, ctrlOn } from "../common/controls"; import { sharingCtrl, loadingCtrl, ctrlOn } from "../common/controls";
import { LoadingIcon } from "./visual/loading";
export interface Props { export interface Props {
filesInfo: FilesProps; filesInfo: FilesProps;
@ -50,6 +49,10 @@ export class Layers extends React.Component<Props, State, {}> {
return ( return (
<div id="layers"> <div id="layers">
<div id="loading-layer" className={showLoading}>
<LoadingIcon />
</div>
<div id="login-layer" className={`layer ${showLogin}`}> <div id="login-layer" className={`layer ${showLogin}`}>
<div id="root-container"> <div id="root-container">
<AuthPane <AuthPane
@ -60,12 +63,6 @@ export class Layers extends React.Component<Props, State, {}> {
</div> </div>
</div> </div>
<div id="loading-layer" className={showLoading}>
<div id="loading-container">
<RiTimer2Line size="3rem" className="cyan1-font anm-rotate" />
</div>
</div>
<div id="settings-layer" className={`layer ${showSettings}`}> <div id="settings-layer" className={`layer ${showSettings}`}>
<div id="root-container"> <div id="root-container">
<Container> <Container>

View file

@ -2,10 +2,8 @@ import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import { List, Map, Set } from "immutable"; import { List, Map, Set } from "immutable";
import FileSize from "filesize"; import FileSize from "filesize";
import QRCode from "react-qr-code";
import { RiFolder2Fill } from "@react-icons/all-files/ri/RiFolder2Fill"; import { RiFolder2Fill } from "@react-icons/all-files/ri/RiFolder2Fill";
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 { RiCheckboxFill } from "@react-icons/all-files/ri/RiCheckboxFill";
@ -30,7 +28,13 @@ 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";
import { ctrlOn, sharingCtrl, filesViewCtrl } from "../common/controls"; import {
ctrlOff,
ctrlOn,
sharingCtrl,
filesViewCtrl,
loadingCtrl,
} from "../common/controls";
export interface Item { export interface Item {
name: string; name: string;
@ -95,6 +99,11 @@ export class FilesPanel extends React.Component<Props, State, {}> {
}; };
} }
setLoading = (state: boolean) => {
updater().setControlOption(loadingCtrl, state ? ctrlOn : ctrlOff);
this.props.update(updater().updateUI);
};
updateProgress = async ( updateProgress = async (
infos: Map<string, UploadEntry>, infos: Map<string, UploadEntry>,
refresh: boolean refresh: boolean
@ -107,29 +116,24 @@ export class FilesPanel extends React.Component<Props, State, {}> {
if (infos.size === 0 || infos.size === errCount) { if (infos.size === 0 || infos.size === errCount) {
// refresh used space // refresh used space
updater() const status = await updater().self();
.self() if (status !== "") {
.then(() => { alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status));
return;
}
this.props.update(updater().updateLogin); this.props.update(updater().updateLogin);
this.props.update(updater().updateUploadingsInfo); this.props.update(updater().updateUploadingsInfo);
});
} }
if (refresh) { if (refresh) {
updater() const status = await updater().setItems(this.props.filesInfo.dirPath);
.setItems(this.props.filesInfo.dirPath)
.then((status: string) => {
if (status !== "") { if (status !== "") {
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status)); alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status));
} else { return;
}
}
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
this.props.update(updater().updateUploadingsInfo); this.props.update(updater().updateUploadingsInfo);
}
});
} else {
this.props.update(updater().updateFilesInfo);
this.props.update(updater().updateUploadingsInfo);
}
}; };
addUploads = (event: React.ChangeEvent<HTMLInputElement>) => { addUploads = (event: React.ChangeEvent<HTMLInputElement>) => {
@ -154,7 +158,7 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.setState({ newFolderName: ev.target.value }); this.setState({ newFolderName: ev.target.value });
}; };
onMkDir = () => { onMkDir = async () => {
if (this.state.newFolderName === "") { if (this.state.newFolderName === "") {
alertMsg(this.props.msg.pkg.get("browser.folder.add.fail")); alertMsg(this.props.msg.pkg.get("browser.folder.add.fail"));
return; return;
@ -164,28 +168,37 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.props.filesInfo.dirPath.join("/"), this.props.filesInfo.dirPath.join("/"),
this.state.newFolderName this.state.newFolderName
); );
updater()
.mkDir(dirPath) this.setLoading(true);
.then((status: string) => {
if (status !== "") { try {
throw status; const mkDirStatus = await updater().mkDir(dirPath);
if (mkDirStatus !== "") {
alertMsg(
getErrMsg(this.props.msg.pkg, "op.fail", mkDirStatus.toString())
);
return;
} }
const setItemsStatus = await updater().setItems(
this.props.filesInfo.dirPath
);
if (setItemsStatus !== "") {
alertMsg(
getErrMsg(this.props.msg.pkg, "op.fail", setItemsStatus.toString())
);
return;
}
this.setState({ newFolderName: "" }); this.setState({ newFolderName: "" });
return updater().setItems(this.props.filesInfo.dirPath);
})
.then((status: string) => {
if (status !== "") {
throw status;
}
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
this.props.update(updater().updateSharingsInfo); this.props.update(updater().updateSharingsInfo);
}) } finally {
.catch((status: Error) => { this.setLoading(false);
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status.toString())); }
});
}; };
delete = () => { delete = async () => {
// TODO: selected should be cleaned after change the cwd // TODO: selected should be cleaned after change the cwd
if (this.props.filesInfo.dirPath.join("/") !== this.state.selectedSrc) { if (this.props.filesInfo.dirPath.join("/") !== this.state.selectedSrc) {
alertMsg(this.props.msg.pkg.get("browser.del.fail")); alertMsg(this.props.msg.pkg.get("browser.del.fail"));
@ -207,21 +220,27 @@ export class FilesPanel extends React.Component<Props, State, {}> {
} }
} }
updater() this.setLoading(true);
.delete(
try {
const deleteStatus = await updater().delete(
this.props.filesInfo.dirPath, this.props.filesInfo.dirPath,
this.props.filesInfo.items, this.props.filesInfo.items,
this.state.selectedItems this.state.selectedItems
) );
.then((status: string) => { if (deleteStatus !== "") {
if (status !== "") { alertMsg(
throw status; getErrMsg(this.props.msg.pkg, "op.fail", deleteStatus.toString())
);
return deleteStatus;
} }
return updater().self();
}) const selfStatus = await updater().self();
.then((status: string) => { if (selfStatus !== "") {
if (status !== "") { alertMsg(
throw status; getErrMsg(this.props.msg.pkg, "op.fail", selfStatus.toString())
);
return selfStatus;
} }
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
@ -231,13 +250,12 @@ export class FilesPanel extends React.Component<Props, State, {}> {
selectedSrc: "", selectedSrc: "",
selectedItems: Map<string, boolean>(), selectedItems: Map<string, boolean>(),
}); });
}) } finally {
.catch((status: Error) => { this.setLoading(false);
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status.toString())); }
});
}; };
moveHere = () => { moveHere = async () => {
const oldDir = this.state.selectedSrc; const oldDir = this.state.selectedSrc;
const newDir = this.props.filesInfo.dirPath.join("/"); const newDir = this.props.filesInfo.dirPath.join("/");
if (oldDir === newDir) { if (oldDir === newDir) {
@ -245,26 +263,30 @@ export class FilesPanel extends React.Component<Props, State, {}> {
return; return;
} }
updater() this.setLoading(true);
.moveHere(
try {
const moveStatus = await updater().moveHere(
this.state.selectedSrc, this.state.selectedSrc,
this.props.filesInfo.dirPath.join("/"), this.props.filesInfo.dirPath.join("/"),
this.state.selectedItems this.state.selectedItems
) );
.then((status: string) => { if (moveStatus !== "") {
if (status !== "") { alertMsg(
throw status; getErrMsg(this.props.msg.pkg, "op.fail", moveStatus.toString())
);
return;
} }
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
this.props.update(updater().updateSharingsInfo); this.props.update(updater().updateSharingsInfo);
this.setState({ this.setState({
selectedSrc: "", selectedSrc: "",
selectedItems: Map<string, boolean>(), selectedItems: Map<string, boolean>(),
}); });
}) } finally {
.catch((status: Error) => { this.setLoading(false);
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status.toString())); }
});
}; };
gotoChild = async (childDirName: string) => { gotoChild = async (childDirName: string) => {
@ -272,17 +294,18 @@ export class FilesPanel extends React.Component<Props, State, {}> {
}; };
goHome = async () => { goHome = async () => {
return updater() this.setLoading(true);
.setHomeItems()
.then((status: string) => { try {
const status = await updater().setHomeItems();
if (status !== "") { if (status !== "") {
throw status; alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status));
return;
} }
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
}) } finally {
.catch((status: Error) => { this.setLoading(false);
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status.toString())); }
});
}; };
chdir = async (dirPath: List<string>) => { chdir = async (dirPath: List<string>) => {
@ -293,24 +316,25 @@ export class FilesPanel extends React.Component<Props, State, {}> {
return; return;
} }
return updater() this.setLoading(true);
.setItems(dirPath) try {
.then((status: string) => { const status = await updater().setItems(dirPath);
if (status !== "") { if (status !== "") {
throw status; alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status));
return;
} }
return updater().syncIsSharing(dirPath.join("/"));
}) const isSharingStatus = await updater().syncIsSharing(dirPath.join("/"));
.then((status: string) => { if (isSharingStatus !== "") {
if (status !== "") { alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", isSharingStatus));
throw status; return;
} }
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
this.props.update(updater().updateSharingsInfo); this.props.update(updater().updateSharingsInfo);
}) } finally {
.catch((status: Error) => { this.setLoading(false);
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status.toString())); }
});
}; };
select = (itemName: string) => { select = (itemName: string) => {
@ -356,49 +380,59 @@ export class FilesPanel extends React.Component<Props, State, {}> {
}; };
addSharing = async () => { addSharing = async () => {
return updater() this.setLoading(true);
.addSharing()
.then((status: string) => { try {
if (status !== "") { const addStatus = await updater().addSharing();
throw status; if (addStatus !== "") {
} else { alertMsg(
getErrMsg(this.props.msg.pkg, "op.fail", addStatus.toString())
);
return;
}
updater().setSharing(true); updater().setSharing(true);
return updater().listSharings(); const listStatus = await updater().listSharings();
} if (listStatus !== "") {
}) alertMsg(
.then((status: string) => { getErrMsg(this.props.msg.pkg, "op.fail", listStatus.toString())
if (status !== "") { );
throw status; return;
} }
this.props.update(updater().updateSharingsInfo); this.props.update(updater().updateSharingsInfo);
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
}) } finally {
.catch((status: Error) => { this.setLoading(false);
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status.toString())); }
});
}; };
deleteSharing = async (dirPath: string) => { deleteSharing = async (dirPath: string) => {
return updater() this.setLoading(true);
.deleteSharing(dirPath)
.then((status) => { try {
if (status !== "") { const delStatus = await updater().deleteSharing(dirPath);
throw status; if (delStatus !== "") {
} else { alertMsg(
getErrMsg(this.props.msg.pkg, "op.fail", delStatus.toString())
);
return;
}
updater().setSharing(false); updater().setSharing(false);
return updater().listSharings(); const listStatus = await updater().listSharings();
} if (listStatus !== "") {
}) alertMsg(
.then((status: string) => { getErrMsg(this.props.msg.pkg, "op.fail", listStatus.toString())
if (status !== "") { );
throw status; return;
} }
this.props.update(updater().updateSharingsInfo); this.props.update(updater().updateSharingsInfo);
this.props.update(updater().updateFilesInfo); this.props.update(updater().updateFilesInfo);
}) } finally {
.catch((status: Error) => { this.setLoading(false);
alertMsg(getErrMsg(this.props.msg.pkg, "op.fail", status.toString())); }
});
}; };
updateItems = (items: Object) => { updateItems = (items: Object) => {
@ -796,9 +830,12 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.prepareTable(this.props.filesInfo.items, showOp) this.prepareTable(this.props.filesInfo.items, showOp)
); );
const usedSpace = FileSize(parseInt(this.props.login.extInfo.usedSpace, 10), { const usedSpace = FileSize(
parseInt(this.props.login.extInfo.usedSpace, 10),
{
round: 0, round: 0,
}); }
);
const spaceLimit = FileSize( const spaceLimit = FileSize(
parseInt(this.props.login.quota.spaceLimit, 10), parseInt(this.props.login.quota.spaceLimit, 10),
{ {

View file

@ -0,0 +1,29 @@
import * as React from "react";
import { RiLoader5Fill } from "@react-icons/all-files/ri/RiLoader5Fill";
export interface Props {}
export interface State {}
export const LoadingIcon = (props: Props) => {
return (
<div id="loading-container">
<RiLoader5Fill
size="5rem"
className="cyan0-font anm-rotate-s"
style={{ position: "absolute" }}
/>
<RiLoader5Fill
size="5rem"
className="cyan1-font anm-rotate-m"
style={{ position: "absolute" }}
/>
<RiLoader5Fill
size="5rem"
className="blue1-font anm-rotate-f"
style={{ position: "absolute" }}
/>
</div>
);
};