feat(ui): enable i18n

This commit is contained in:
hexxa 2021-08-26 15:20:41 +08:00 committed by Hexxa
parent 3531e7a265
commit eb6d9c2490
15 changed files with 408 additions and 252 deletions

View file

@ -22,14 +22,8 @@ describe("Browser", () => {
updater().setClients(usersCl, filesCl);
const browser = new Browser({
dirPath: coreState.browser.dirPath,
isSharing: coreState.browser.isSharing,
items: coreState.browser.items,
uploadings: coreState.browser.uploadings,
sharings: coreState.browser.sharings,
uploadFiles: coreState.browser.uploadFiles,
uploadValue: coreState.browser.uploadValue,
isVertical: coreState.browser.isVertical,
browser: coreState.browser,
msg: coreState.msg,
update: (updater: (prevState: ICoreState) => ICoreState) => {},
});

View file

@ -16,9 +16,8 @@ describe("Login", () => {
const coreState = newWithWorker(mockWorker);
const pane = new AuthPane({
userRole: coreState.login.userRole,
authed: coreState.login.authed,
captchaID: coreState.login.captchaID,
login: coreState.login,
msg: coreState.msg,
update: (updater: (prevState: ICoreState) => ICoreState) => {},
});

View file

@ -5,7 +5,7 @@ import FileSize from "filesize";
import { alertMsg, comfirmMsg } from "../common/env";
import { updater } from "./state_updater";
import { ICoreState } from "./core_state";
import { ICoreState, MsgProps } from "./core_state";
import { MetadataResp, UploadInfo } from "../client";
import { Up } from "../worker/upload_mgr";
import { UploadEntry } from "../worker/interface";
@ -18,7 +18,7 @@ export interface Item {
selected: boolean;
}
export interface Props {
export interface BrowserProps {
dirPath: List<string>;
isSharing: boolean;
items: List<MetadataResp>;
@ -29,7 +29,11 @@ export interface Props {
uploadValue: string;
isVertical: boolean;
}
export interface Props {
browser: BrowserProps;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -90,7 +94,7 @@ export class Browser extends React.Component<Props, State, {}> {
.deleteUpload(filePath)
.then((ok: boolean) => {
if (!ok) {
alertMsg(`Failed to delete uploading ${filePath}`);
alertMsg(this.props.msg.pkg.get("browser.upload.del.fail"));
}
return updater().refreshUploadings();
})
@ -106,19 +110,19 @@ export class Browser extends React.Component<Props, State, {}> {
onMkDir = () => {
if (this.state.inputValue === "") {
alertMsg("folder name can not be empty");
alertMsg(this.props.msg.pkg.get("browser.folder.add.fail"));
return;
}
const dirPath = getItemPath(
this.props.dirPath.join("/"),
this.props.browser.dirPath.join("/"),
this.state.inputValue
);
updater()
.mkDir(dirPath)
.then(() => {
this.setState({ inputValue: "" });
return updater().setItems(this.props.dirPath);
return updater().setItems(this.props.browser.dirPath);
})
.then(() => {
this.update(updater().updateBrowser);
@ -126,10 +130,10 @@ export class Browser extends React.Component<Props, State, {}> {
};
delete = () => {
if (this.props.dirPath.join("/") !== this.state.selectedSrc) {
alertMsg("please select file or folder to delete at first");
if (this.props.browser.dirPath.join("/") !== this.state.selectedSrc) {
alertMsg(this.props.msg.pkg.get("browser.del.fail"));
this.setState({
selectedSrc: this.props.dirPath.join("/"),
selectedSrc: this.props.browser.dirPath.join("/"),
selectedItems: Map<string, boolean>(),
});
return;
@ -141,7 +145,11 @@ export class Browser extends React.Component<Props, State, {}> {
}
updater()
.delete(this.props.dirPath, this.props.items, this.state.selectedItems)
.delete(
this.props.browser.dirPath,
this.props.browser.items,
this.state.selectedItems
)
.then(() => {
this.update(updater().updateBrowser);
this.setState({
@ -153,16 +161,16 @@ export class Browser extends React.Component<Props, State, {}> {
moveHere = () => {
const oldDir = this.state.selectedSrc;
const newDir = this.props.dirPath.join("/");
const newDir = this.props.browser.dirPath.join("/");
if (oldDir === newDir) {
alertMsg("source directory is same as destination directory");
alertMsg(this.props.msg.pkg.get("browser.move.fail"));
return;
}
updater()
.moveHere(
this.state.selectedSrc,
this.props.dirPath.join("/"),
this.props.browser.dirPath.join("/"),
this.state.selectedItems
)
.then(() => {
@ -175,11 +183,11 @@ export class Browser extends React.Component<Props, State, {}> {
};
gotoChild = (childDirName: string) => {
this.chdir(this.props.dirPath.push(childDirName));
this.chdir(this.props.browser.dirPath.push(childDirName));
};
chdir = async (dirPath: List<string>) => {
if (dirPath === this.props.dirPath) {
if (dirPath === this.props.browser.dirPath) {
return;
}
@ -199,7 +207,7 @@ export class Browser extends React.Component<Props, State, {}> {
updateProgress = (infos: Map<string, UploadEntry>) => {
updater().setUploadings(infos);
updater()
.setItems(this.props.dirPath)
.setItems(this.props.browser.dirPath)
.then(() => {
this.update(updater().updateBrowser);
});
@ -211,7 +219,7 @@ export class Browser extends React.Component<Props, State, {}> {
: this.state.selectedItems.set(itemName, true);
this.setState({
selectedSrc: this.props.dirPath.join("/"),
selectedSrc: this.props.browser.dirPath.join("/"),
selectedItems: selectedItems,
});
};
@ -220,17 +228,17 @@ export class Browser extends React.Component<Props, State, {}> {
let newSelected = Map<string, boolean>();
const someSelected = this.state.selectedItems.size === 0 ? true : false;
if (someSelected) {
this.props.items.forEach((item) => {
this.props.browser.items.forEach((item) => {
newSelected = newSelected.set(item.name, true);
});
} else {
this.props.items.forEach((item) => {
this.props.browser.items.forEach((item) => {
newSelected = newSelected.delete(item.name);
});
}
this.setState({
selectedSrc: this.props.dirPath.join("/"),
selectedSrc: this.props.browser.dirPath.join("/"),
selectedItems: newSelected,
});
};
@ -240,7 +248,7 @@ export class Browser extends React.Component<Props, State, {}> {
.addSharing()
.then((ok) => {
if (!ok) {
alertMsg("failed to enable sharing");
alertMsg(this.props.msg.pkg.get("browser.share.add.fail"));
} else {
updater().setSharing(true);
return this.listSharings();
@ -256,7 +264,7 @@ export class Browser extends React.Component<Props, State, {}> {
.deleteSharing(dirPath)
.then((ok) => {
if (!ok) {
alertMsg("failed to disable sharing");
alertMsg(this.props.msg.pkg.get("browser.share.del.fail"));
} else {
updater().setSharing(false);
return this.listSharings();
@ -278,13 +286,15 @@ export class Browser extends React.Component<Props, State, {}> {
};
render() {
const breadcrumb = this.props.dirPath.map(
const breadcrumb = this.props.browser.dirPath.map(
(pathPart: string, key: number) => {
return (
<span key={pathPart}>
<button
type="button"
onClick={() => this.chdir(this.props.dirPath.slice(0, key + 1))}
onClick={() =>
this.chdir(this.props.browser.dirPath.slice(0, key + 1))
}
className="white-font margin-r-m"
style={{ backgroundColor: "rgba(0, 0, 0, 0.7)" }}
>
@ -296,10 +306,14 @@ export class Browser extends React.Component<Props, State, {}> {
);
const nameCellClass = `item-name item-name-${
this.props.isVertical ? "vertical" : "horizontal"
this.props.browser.isVertical ? "vertical" : "horizontal"
} pointer`;
const sizeCellClass = this.props.isVertical ? `hidden margin-s` : ``;
const modTimeCellClass = this.props.isVertical ? `hidden margin-s` : ``;
const sizeCellClass = this.props.browser.isVertical
? `hidden margin-s`
: ``;
const modTimeCellClass = this.props.browser.isVertical
? `hidden margin-s`
: ``;
const ops = (
<div>
@ -310,13 +324,13 @@ export class Browser extends React.Component<Props, State, {}> {
onChange={this.onInputChange}
value={this.state.inputValue}
className="black0-font margin-r-m"
placeholder="folder name"
placeholder={this.props.msg.pkg.get("browser.folder.name")}
/>
<button
onClick={this.onMkDir}
className="grey1-bg white-font margin-r-m"
>
Create Folder
{this.props.msg.pkg.get("browser.folder.add")}
</button>
</span>
<span className="inline-block margin-t-m margin-b-m">
@ -324,13 +338,13 @@ export class Browser extends React.Component<Props, State, {}> {
onClick={this.onClickUpload}
className="green0-bg white-font"
>
Upload Files
{this.props.msg.pkg.get("browser.upload")}
</button>
<input
type="file"
onChange={this.addUploads}
multiple={true}
value={this.props.uploadValue}
value={this.props.browser.uploadValue}
ref={this.assignInput}
className="black0-font hidden"
/>
@ -345,24 +359,24 @@ export class Browser extends React.Component<Props, State, {}> {
onClick={() => this.delete()}
className="red0-bg white-font margin-t-m margin-b-m margin-r-m"
>
Delete Selected
{this.props.msg.pkg.get("browser.delete")}
</button>
<button
type="button"
onClick={() => this.moveHere()}
className="grey1-bg white-font margin-t-m margin-b-m margin-r-m"
>
Paste
{this.props.msg.pkg.get("browser.paste")}
</button>
{this.props.isSharing ? (
{this.props.browser.isSharing ? (
<button
type="button"
onClick={() => {
this.deleteSharing(this.props.dirPath.join("/"));
this.deleteSharing(this.props.browser.dirPath.join("/"));
}}
className="red0-bg white-font margin-t-m margin-b-m"
>
Stop Sharing
{this.props.msg.pkg.get("browser.share.del")}
</button>
) : (
<button
@ -370,16 +384,16 @@ export class Browser extends React.Component<Props, State, {}> {
onClick={this.addSharing}
className="green0-bg white-font margin-t-m margin-b-m"
>
Share Folder
{this.props.msg.pkg.get("browser.share.add")}
</button>
)}
</div>
</div>
);
const itemList = this.props.items.map((item: MetadataResp) => {
const itemList = this.props.browser.items.map((item: MetadataResp) => {
const isSelected = this.state.selectedItems.has(item.name);
const dirPath = this.props.dirPath.join("/");
const dirPath = this.props.browser.dirPath.join("/");
const itemPath = dirPath.endsWith("/")
? `${dirPath}${item.name}`
: `${dirPath}/${item.name}`;
@ -404,7 +418,9 @@ export class Browser extends React.Component<Props, State, {}> {
className={`white-font ${isSelected ? "blue0-bg" : "grey1-bg"}`}
style={{ width: "8rem", display: "inline-block" }}
>
{isSelected ? "Deselect" : "Select"}
{isSelected
? this.props.msg.pkg.get("browser.deselect")
: this.props.msg.pkg.get("browser.select")}
</button>
</span>
</span>
@ -435,7 +451,9 @@ export class Browser extends React.Component<Props, State, {}> {
className={`white-font ${isSelected ? "blue0-bg" : "grey1-bg"}`}
style={{ width: "8rem", display: "inline-block" }}
>
{isSelected ? "Deselect" : "Select"}
{isSelected
? this.props.msg.pkg.get("browser.deselect")
: this.props.msg.pkg.get("browser.select")}
</button>
</span>
</span>
@ -443,43 +461,45 @@ export class Browser extends React.Component<Props, State, {}> {
);
});
const uploadingList = this.props.uploadings.map((uploading: UploadInfo) => {
const pathParts = uploading.realFilePath.split("/");
const fileName = pathParts[pathParts.length - 1];
const uploadingList = this.props.browser.uploadings.map(
(uploading: UploadInfo) => {
const pathParts = uploading.realFilePath.split("/");
const fileName = pathParts[pathParts.length - 1];
return (
<div key={fileName} className="flex-list-container">
<span className="flex-list-item-l">
<span className="vbar blue2-bg"></span>
<div className={nameCellClass}>
<span className="bold">{fileName}</span>
<div className="grey1-font">
{FileSize(uploading.uploaded, { round: 0 })}
&nbsp;/&nbsp;{FileSize(uploading.size, { round: 0 })}
return (
<div key={fileName} className="flex-list-container">
<span className="flex-list-item-l">
<span className="vbar blue2-bg"></span>
<div className={nameCellClass}>
<span className="bold">{fileName}</span>
<div className="grey1-font">
{FileSize(uploading.uploaded, { round: 0 })}
&nbsp;/&nbsp;{FileSize(uploading.size, { round: 0 })}
</div>
</div>
</div>
</span>
<span className="flex-list-item-r padding-r-m">
<div className="item-op">
<button
onClick={() => this.stopUploading(uploading.realFilePath)}
className="grey1-bg white-font margin-r-m"
>
Stop
</button>
<button
onClick={() => this.deleteUpload(uploading.realFilePath)}
className="grey1-bg white-font"
>
Delete
</button>
</div>
</span>
</div>
);
});
</span>
<span className="flex-list-item-r padding-r-m">
<div className="item-op">
<button
onClick={() => this.stopUploading(uploading.realFilePath)}
className="grey1-bg white-font margin-r-m"
>
{this.props.msg.pkg.get("browser.stop")}
</button>
<button
onClick={() => this.deleteUpload(uploading.realFilePath)}
className="grey1-bg white-font"
>
{this.props.msg.pkg.get("browser.delete")}
</button>
</div>
</span>
</div>
);
}
);
const sharingList = this.props.sharings.map((dirPath: string) => {
const sharingList = this.props.browser.sharings.map((dirPath: string) => {
return (
<div key={dirPath} className="flex-list-container">
<span className="flex-list-item-l">
@ -501,7 +521,7 @@ export class Browser extends React.Component<Props, State, {}> {
}}
className="grey1-bg white-font"
>
Disable
{this.props.msg.pkg.get("browser.share.del")}
</button>
</span>
</div>
@ -525,17 +545,17 @@ export class Browser extends React.Component<Props, State, {}> {
borderRadius: "0.5rem",
}}
>
Location:
{this.props.msg.pkg.get("browser.location")}
</span>
{breadcrumb}
</div>
{this.props.uploadings.size === 0 ? null : (
{this.props.browser.uploadings.size === 0 ? null : (
<div className="container">
<div className="flex-list-container bold">
<span className="flex-list-item-l">
<span className="dot black-bg"></span>
<span>Uploading Files</span>
<span>{this.props.msg.pkg.get("browser.upload.title")}</span>
</span>
<span className="flex-list-item-r padding-r-m"></span>
</div>
@ -543,12 +563,12 @@ export class Browser extends React.Component<Props, State, {}> {
</div>
)}
{this.props.sharings.size === 0 ? null : (
{this.props.browser.sharings.size === 0 ? null : (
<div className="container">
<div className="flex-list-container bold">
<span className="flex-list-item-l">
<span className="dot black-bg"></span>
<span>Sharing Folders</span>
<span>{this.props.msg.pkg.get("browser.share.title")}</span>
</span>
<span className="flex-list-item-r padding-r-m"></span>
</div>
@ -560,9 +580,7 @@ export class Browser extends React.Component<Props, State, {}> {
<div className="flex-list-container bold">
<span className="flex-list-item-l">
<span className="dot black-bg"></span>
<span>Name</span>
{/* <span>File Size</span>
<span>Mod Time</span> */}
<span>{this.props.msg.pkg.get("browser.item.title")}</span>
</span>
<span className="flex-list-item-r padding-r-m">
<button
@ -570,7 +588,7 @@ export class Browser extends React.Component<Props, State, {}> {
className={`grey1-bg white-font`}
style={{ width: "8rem", display: "inline-block" }}
>
Select All
{this.props.msg.pkg.get("browser.selectAll")}
</button>
</span>
</div>

View file

@ -4,16 +4,20 @@ import BgWorker from "../worker/upload.bg.worker";
import { FgWorker } from "../worker/upload.fg.worker";
// import { Props as PanelProps } from "./root_frame";
import { Props as BrowserProps } from "./browser";
import { PanesProps as PanesProps } from "./panes";
import { Props as LoginProps } from "./pane_login";
import { Props as AdminProps } from "./pane_admin";
import { Props as SettingsProps } from "./pane_settings";
import { BrowserProps } from "./browser";
import { PanesProps } from "./panes";
import { LoginProps } from "./pane_login";
import { AdminProps } from "./pane_admin";
import { MsgPackage } from "../i18n/msger";
import { Item } from "./browser";
import { UploadInfo, User } from "../client";
import { initUploadMgr, IWorker } from "../worker/upload_mgr";
export interface MsgProps {
lan: string;
pkg: Map<string, string>;
}
export interface ICoreState {
// panel: PanelProps;
isVertical: boolean;
@ -21,6 +25,7 @@ export interface ICoreState {
panes: PanesProps;
login: LoginProps;
admin: AdminProps;
msg: MsgProps;
// settings: SettingsProps;
}
@ -62,6 +67,10 @@ export function initState(): ICoreState {
users: Map<string, User>(),
roles: Set<string>(),
},
msg: {
lan: "en_US",
pkg: MsgPackage.get("en_US"),
}
};
}

View file

@ -1,13 +1,19 @@
import * as React from "react";
import { Map, Set } from "immutable";
import { ICoreState } from "./core_state";
import { alertMsg } from "../common/env";
import { ICoreState, MsgProps } from "./core_state";
import { User, Quota } from "../client";
import { updater } from "./state_updater";
export interface Props {
export interface AdminProps {
users: Map<string, User>;
roles: Set<string>;
}
export interface Props {
admin: AdminProps;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -18,6 +24,7 @@ export interface UserFormProps {
role: string;
quota: Quota;
roles: Set<string>;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -90,7 +97,7 @@ export class UserForm extends React.Component<
setPwd = () => {
if (this.state.newPwd1 !== this.state.newPwd2) {
alert("2 passwords do not match, please check.");
alertMsg(this.props.msg.pkg.get("settings.pwd.notSame"));
return;
}
@ -98,9 +105,9 @@ export class UserForm extends React.Component<
.forceSetPwd(this.state.id, this.state.newPwd1)
.then((ok: boolean) => {
if (ok) {
alert("password is updated");
alertMsg(this.props.msg.pkg.get("update.ok"));
} else {
alert("failed to update password");
alertMsg(this.props.msg.pkg.get("update.fail"));
}
this.setState({
newPwd1: "",
@ -114,9 +121,9 @@ export class UserForm extends React.Component<
.setUser(this.props.id, this.state.role, this.state.quota)
.then((ok: boolean) => {
if (!ok) {
alert("failed to set user");
alertMsg(this.props.msg.pkg.get("update.fail"));
} else {
alert("user is updated");
alertMsg(this.props.msg.pkg.get("update.ok"));
}
return updater().listUsers();
})
@ -130,7 +137,7 @@ export class UserForm extends React.Component<
.delUser(this.state.id)
.then((ok: boolean) => {
if (!ok) {
alert("failed to delete user");
alertMsg(this.props.msg.pkg.get("delete.fail"));
}
return updater().listUsers();
})
@ -155,8 +162,12 @@ export class UserForm extends React.Component<
}}
className="bold item-name"
>
<div>ID: {this.props.id}</div>
<div>Name: {this.props.name}</div>
<div>
{this.props.msg.pkg.get("user.id")} {this.props.id}
</div>
<div>
{this.props.msg.pkg.get("user.name")} {this.props.name}
</div>
</div>
</div>
@ -172,7 +183,7 @@ export class UserForm extends React.Component<
onClick={this.delUser}
className="grey1-bg white-font margin-r-m"
>
Delete User
{this.props.msg.pkg.get("delete")}
</button>
</div>
</div>
@ -196,7 +207,7 @@ export class UserForm extends React.Component<
<div className="margin-t-m">
<div className="margin-r-m font-size-s grey1-font">
Space Limit
{this.props.msg.pkg.get("spaceLimit")}
</div>
<input
name={`${this.props.id}-spaceLimit`}
@ -210,7 +221,7 @@ export class UserForm extends React.Component<
<div className="margin-t-m">
<div className="margin-r-m font-size-s grey1-font">
Upload Speed Limit
{this.props.msg.pkg.get("uploadLimit")}
</div>
<input
name={`${this.props.id}-uploadSpeedLimit`}
@ -224,7 +235,7 @@ export class UserForm extends React.Component<
<div className="margin-t-m">
<div className="margin-r-m font-size-s grey1-font">
Download Speed Limit
{this.props.msg.pkg.get("downloadLimit")}
</div>
<input
name={`${this.props.id}-downloadSpeedLimit`}
@ -242,7 +253,7 @@ export class UserForm extends React.Component<
onClick={this.setUser}
className="grey1-bg white-font margin-r-m"
>
Update User
{this.props.msg.pkg.get("update")}
</button>
</div>
</div>
@ -262,7 +273,7 @@ export class UserForm extends React.Component<
onChange={this.changePwd1}
value={this.state.newPwd1}
className="black0-font margin-b-m"
placeholder="new password"
placeholder={this.props.msg.pkg.get("settings.pwd.new1")}
/>
<input
name={`${this.props.id}-pwd2`}
@ -270,7 +281,7 @@ export class UserForm extends React.Component<
onChange={this.changePwd2}
value={this.state.newPwd2}
className="black0-font margin-b-m"
placeholder="repeat password"
placeholder={this.props.msg.pkg.get("settings.pwd.new2")}
/>
</div>
@ -279,7 +290,7 @@ export class UserForm extends React.Component<
onClick={this.setPwd}
className="grey1-bg white-font margin-r-m"
>
Update
{this.props.msg.pkg.get("update")}
</button>
</div>
</div>
@ -328,7 +339,9 @@ export class AdminPane extends React.Component<Props, State, {}> {
.addRole(this.state.newRole)
.then((ok: boolean) => {
if (!ok) {
alert("failed to add role");
alertMsg(this.props.msg.pkg.get("add.fail"));
} else {
alertMsg(this.props.msg.pkg.get("add.ok"));
}
return updater().listRoles();
})
@ -340,7 +353,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
delRole = (role: string) => {
if (
!confirm(
"After deleting this role, some of users may not be able to login."
this.props.msg.pkg.get("role.delete.warning") // "After deleting this role, some of users may not be able to login."
)
) {
return;
@ -350,7 +363,9 @@ export class AdminPane extends React.Component<Props, State, {}> {
.delRole(role)
.then((ok: boolean) => {
if (!ok) {
alert("failed to delete role");
this.props.msg.pkg.get("delete.fail");
} else {
this.props.msg.pkg.get("delete.ok");
}
return updater().listRoles();
})
@ -361,7 +376,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
addUser = () => {
if (this.state.newUserPwd1 !== this.state.newUserPwd2) {
alert("2 passwords do not match, please check.");
alertMsg(this.props.msg.pkg.get("settings.pwd.notSame"));
return;
}
@ -375,7 +390,9 @@ export class AdminPane extends React.Component<Props, State, {}> {
})
.then((ok: boolean) => {
if (!ok) {
alert("failed to add user");
alertMsg(this.props.msg.pkg.get("add.fail"));
} else {
alertMsg(this.props.msg.pkg.get("add.ok"));
}
this.setState({
newUserName: "",
@ -391,7 +408,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
};
render() {
const userList = this.props.users.valueSeq().map((user: User) => {
const userList = this.props.admin.users.valueSeq().map((user: User) => {
return (
<div key={user.id} className="margin-t-m">
<UserForm
@ -400,14 +417,15 @@ export class AdminPane extends React.Component<Props, State, {}> {
name={user.name}
role={user.role}
quota={user.quota}
roles={this.props.roles}
roles={this.props.admin.roles}
msg={this.props.msg}
update={this.props.update}
/>
</div>
);
});
const roleList = this.props.roles.valueSeq().map((role: string) => {
const roleList = this.props.admin.roles.valueSeq().map((role: string) => {
return (
<div key={role} className="flex-list-container margin-b-m">
<div className="flex-list-item-l">
@ -421,7 +439,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
}}
className="grey1-bg white-font margin-r-m"
>
Delete
{this.props.msg.pkg.get("delete")}
</button>
</div>
</div>
@ -434,7 +452,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
<div className="flex-list-container bold">
<span className="flex-list-item-l">
<span className="dot black-bg"></span>
<span>Add New User</span>
<span>{this.props.msg.pkg.get("user.add")}</span>
</span>
<span className="flex-list-item-r padding-r-m"></span>
</div>
@ -452,28 +470,28 @@ export class AdminPane extends React.Component<Props, State, {}> {
onChange={this.onChangeUserName}
value={this.state.newUserName}
className="black0-font margin-b-m"
placeholder="new user name"
placeholder={this.props.msg.pkg.get("user.name")}
/>
<input
type="text"
onChange={this.onChangeUserRole}
value={this.state.newUserRole}
className="black0-font margin-b-m"
placeholder="new user role"
placeholder={this.props.msg.pkg.get("user.role")}
/>
<input
type="password"
onChange={this.onChangeUserPwd1}
value={this.state.newUserPwd1}
className="black0-font margin-b-m"
placeholder="password"
placeholder={this.props.msg.pkg.get("user.password")}
/>
<input
type="password"
onChange={this.onChangeUserPwd2}
value={this.state.newUserPwd2}
className="black0-font margin-b-m"
placeholder="repeat password"
placeholder={this.props.msg.pkg.get("settings.pwd.new2")}
/>
</div>
<div className="flex-list-item-r">
@ -481,7 +499,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
onClick={this.addUser}
className="grey1-bg white-font margin-r-m"
>
Create User
{this.props.msg.pkg.get("add")}
</button>
</div>
</div>
@ -492,7 +510,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
<div className="flex-list-container bold">
<span className="flex-list-item-l">
<span className="dot black-bg"></span>
<span>Users</span>
<span>{this.props.msg.pkg.get("admin.users")}</span>
</span>
<span className="flex-list-item-r padding-r-m"></span>
</div>
@ -504,7 +522,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
<div className="flex-list-container bold">
<span className="flex-list-item-l">
<span className="dot black-bg"></span>
<span>Add New Role</span>
<span>{this.props.msg.pkg.get("role.add")}</span>
</span>
<span className="flex-list-item-r padding-r-m"></span>
</div>
@ -517,7 +535,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
onChange={this.onChangeRole}
value={this.state.newRole}
className="black0-font margin-r-m"
placeholder="new role name"
placeholder={this.props.msg.pkg.get("role.name")}
/>
</span>
</div>
@ -526,7 +544,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
onClick={this.addRole}
className="grey1-bg white-font margin-r-m"
>
Create Role
{this.props.msg.pkg.get("add")}
</button>
</div>
</div>
@ -537,7 +555,7 @@ export class AdminPane extends React.Component<Props, State, {}> {
<div className="flex-list-container bold margin-b-m">
<span className="flex-list-item-l">
<span className="dot black-bg"></span>
<span>Roles</span>
<span>{this.props.msg.pkg.get("admin.roles")}</span>
</span>
<span className="flex-list-item-r padding-r-m"></span>
</div>

View file

@ -1,14 +1,19 @@
import * as React from "react";
import { List } from "immutable";
import { ICoreState } from "./core_state";
import { ICoreState, MsgProps } from "./core_state";
import { updater } from "./state_updater";
import { alertMsg } from "../common/env";
export interface Props {
export interface LoginProps {
userRole: string;
authed: boolean;
captchaID: string;
}
export interface Props {
login: LoginProps;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -47,7 +52,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
.login(
this.state.user,
this.state.pwd,
this.props.captchaID,
this.props.login.captchaID,
this.state.captchaInput
)
.then((ok: boolean): Promise<any> => {
@ -85,7 +90,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
if (ok) {
this.update(updater().updateLogin);
} else {
alertMsg("Failed to logout.");
alertMsg(this.props.msg.pkg.get("login.logout.fail"));
}
});
};
@ -103,7 +108,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
<span>
<div
className="container"
style={{ display: this.props.authed ? "none" : "block" }}
style={{ display: this.props.login.authed ? "none" : "block" }}
>
<div className="padding-l">
<div className="flex-list-container">
@ -114,7 +119,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
onChange={this.changeUser}
value={this.state.user}
className="black0-font margin-t-m margin-b-m margin-r-m"
placeholder="user name"
placeholder={this.props.msg.pkg.get("login.username")}
/>
<input
name="pwd"
@ -122,7 +127,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
onChange={this.changePwd}
value={this.state.pwd}
className="black0-font margin-t-m margin-b-m"
placeholder="password"
placeholder={this.props.msg.pkg.get("login.pwd")}
/>
</div>
<div className="flex-list-item-r">
@ -130,7 +135,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
onClick={this.login}
className="green0-bg white-font margin-t-m margin-b-m"
>
Log in
{this.props.msg.pkg.get("login.login")}
</button>
</div>
</div>
@ -143,10 +148,10 @@ export class AuthPane extends React.Component<Props, State, {}> {
onChange={this.changeCaptcha}
value={this.state.captchaInput}
className="black0-font margin-t-m margin-b-m margin-r-m"
placeholder="captcha"
placeholder={this.props.msg.pkg.get("login.captcha")}
/>
<img
src={`/v1/captchas/imgs?capid=${this.props.captchaID}`}
src={`/v1/captchas/imgs?capid=${this.props.login.captchaID}`}
className="captcha"
onClick={this.refreshCaptcha}
/>
@ -156,9 +161,9 @@ export class AuthPane extends React.Component<Props, State, {}> {
</div>
</div>
<span style={{ display: this.props.authed ? "inherit" : "none" }}>
<span style={{ display: this.props.login.authed ? "inherit" : "none" }}>
<button onClick={this.logout} className="grey1-bg white-font">
Log out
{this.props.msg.pkg.get("login.logout")}
</button>
</span>
</span>

View file

@ -1,12 +1,12 @@
import * as React from "react";
import { ICoreState } from "./core_state";
import { AuthPane, Props as LoginProps } from "./pane_login";
import { ICoreState, MsgProps } from "./core_state";
import { AuthPane, LoginProps } from "./pane_login";
import { updater } from "./state_updater";
import { alertMsg } from "../common/env";
export interface Props {
login: LoginProps;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -40,19 +40,19 @@ export class PaneSettings extends React.Component<Props, State, {}> {
setPwd = () => {
if (this.state.newPwd1 !== this.state.newPwd2) {
alertMsg("new passwords are not same");
alertMsg(this.props.msg.pkg.get("settings.pwd.notSame"));
} else if (this.state.newPwd1 == "") {
alertMsg("new passwords can not be empty");
alertMsg(this.props.msg.pkg.get("settings.pwd.empty"));
} else if (this.state.oldPwd == this.state.newPwd1) {
alertMsg("old and new passwords are same");
alertMsg(this.props.msg.pkg.get("settings.pwd.notChanged"));
} else {
updater()
.setPwd(this.state.oldPwd, this.state.newPwd1)
.then((ok: boolean) => {
if (ok) {
alertMsg("Password is updated");
alertMsg(this.props.msg.pkg.get("settings.pwd.updated"));
} else {
alertMsg("Failed to update password");
alertMsg(this.props.msg.pkg.get("settings.pwd.fail"));
}
this.setState({
oldPwd: "",
@ -64,36 +64,6 @@ export class PaneSettings extends React.Component<Props, State, {}> {
};
render() {
const inputs: Array<JSX.Element> = [
<input
name="old_pwd"
type="password"
onChange={this.changeOldPwd}
value={this.state.oldPwd}
className="black0-font margin-t-m margin-b-m"
placeholder="old password"
/>,
<input
name="new_pwd1"
type="password"
onChange={this.changeNewPwd1}
value={this.state.newPwd1}
className="black0-font margin-t-m margin-b-m"
placeholder="new password"
/>,
<input
name="new_pwd2"
type="password"
onChange={this.changeNewPwd2}
value={this.state.newPwd2}
className="black0-font margin-t-m margin-b-m"
placeholder="new password again"
/>,
<button onClick={this.setPwd} className="grey1-bg white-font">
Update
</button>,
];
return (
<div className="container">
<div className="padding-l">
@ -104,7 +74,7 @@ export class PaneSettings extends React.Component<Props, State, {}> {
</div>
<div className="flex-list-item-r">
<button onClick={this.setPwd} className="grey1-bg white-font">
Update
{this.props.msg.pkg.get("update")}
</button>
</div>
</div>
@ -116,7 +86,7 @@ export class PaneSettings extends React.Component<Props, State, {}> {
onChange={this.changeOldPwd}
value={this.state.oldPwd}
className="black0-font margin-t-m margin-b-m"
placeholder="old password"
placeholder={this.props.msg.pkg.get("settings.pwd.old")}
/>
</div>
<div>
@ -126,7 +96,7 @@ export class PaneSettings extends React.Component<Props, State, {}> {
onChange={this.changeNewPwd1}
value={this.state.newPwd1}
className="black0-font margin-t-m margin-b-m margin-r-m"
placeholder="new password"
placeholder={this.props.msg.pkg.get("settings.pwd.new1")}
/>
<input
name="new_pwd2"
@ -134,7 +104,7 @@ export class PaneSettings extends React.Component<Props, State, {}> {
onChange={this.changeNewPwd2}
value={this.state.newPwd2}
className="black0-font margin-t-m margin-b-m"
placeholder="new password again"
placeholder={this.props.msg.pkg.get("settings.pwd.new2")}
/>
</div>
</div>
@ -148,9 +118,8 @@ export class PaneSettings extends React.Component<Props, State, {}> {
</div>
<div className="flex-list-item-r">
<AuthPane
userRole={this.props.login.userRole}
authed={this.props.login.authed}
captchaID={this.props.login.captchaID}
login={this.props.login}
msg={this.props.msg}
update={this.update}
/>
</div>

View file

@ -2,10 +2,10 @@ import * as React from "react";
import { Set, Map } from "immutable";
import { updater } from "./state_updater";
import { ICoreState } from "./core_state";
import { ICoreState, MsgProps } from "./core_state";
import { PaneSettings } from "./pane_settings";
import { AdminPane, Props as AdminPaneProps } from "./pane_admin";
import { AuthPane, Props as AuthPaneProps } from "./pane_login";
import { AdminPane, AdminProps } from "./pane_admin";
import { AuthPane, LoginProps } from "./pane_login";
export interface PanesProps {
displaying: string;
@ -13,8 +13,9 @@ export interface PanesProps {
}
export interface Props {
panes: PanesProps;
login: AuthPaneProps;
admin: AdminPaneProps;
login: LoginProps;
admin: AdminProps;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -41,14 +42,17 @@ export class Panes extends React.Component<Props, State, {}> {
let panesMap: Map<string, JSX.Element> = Map({
settings: (
<PaneSettings login={this.props.login} update={this.props.update} />
<PaneSettings
login={this.props.login}
msg={this.props.msg}
update={this.props.update}
/>
),
login: (
<AuthPane
userRole={this.props.login.userRole}
authed={this.props.login.authed}
captchaID={this.props.login.captchaID}
login={this.props.login}
update={this.props.update}
msg={this.props.msg}
/>
),
});
@ -57,8 +61,8 @@ export class Panes extends React.Component<Props, State, {}> {
panesMap = panesMap.set(
"admin",
<AdminPane
users={this.props.admin.users}
roles={this.props.admin.roles}
admin={this.props.admin}
msg={this.props.msg}
update={this.props.update}
/>
);
@ -86,7 +90,7 @@ export class Panes extends React.Component<Props, State, {}> {
onClick={this.closePane}
className={`red0-bg white-font ${btnClass}`}
>
Close
{this.props.msg.pkg.get("panes.close")}
</button>
</div>
</div>

View file

@ -1,14 +1,18 @@
import * as React from "react";
import { ICoreState } from "./core_state";
import { Browser, Props as BrowserProps } from "./browser";
import { Props as PaneLoginProps } from "./pane_login";
import { Panes, Props as PanesProps } from "./panes";
import { ICoreState, MsgProps } from "./core_state";
import { Browser, BrowserProps } from "./browser";
import { LoginProps } from "./pane_login";
import { Panes, PanesProps } from "./panes";
import { AdminProps } from "./pane_admin";
import { TopBar } from "./topbar";
export interface Props {
browser: BrowserProps;
panes: PanesProps;
admin: AdminProps;
login: LoginProps;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -23,25 +27,24 @@ export class RootFrame extends React.Component<Props, State, {}> {
<div className="theme-white desktop">
<div id="bg" className="bg bg-img font-m">
<Panes
panes={this.props.panes.panes}
login={this.props.panes.login}
admin={this.props.panes.admin}
panes={this.props.panes}
login={this.props.login}
admin={this.props.admin}
msg={this.props.msg}
update={this.props.update}
/>
<TopBar login={this.props.panes.login} update={this.props.update}></TopBar>
<TopBar
login={this.props.login}
msg={this.props.msg}
update={this.props.update}
/>
<div className="container-center">
<Browser
dirPath={this.props.browser.dirPath}
items={this.props.browser.items}
uploadings={this.props.browser.uploadings}
sharings={this.props.browser.sharings}
isSharing={this.props.browser.isSharing}
browser={this.props.browser}
msg={this.props.msg}
update={this.props.update}
uploadFiles={this.props.browser.uploadFiles}
uploadValue={this.props.browser.uploadValue}
isVertical={this.props.browser.isVertical}
/>
</div>

View file

@ -56,7 +56,7 @@ export class StateMgr extends React.Component<Props, State, {}> {
})
.then((ok: boolean) => {
if (!ok) {
alertMsg("failed to get captcha id");
alertMsg(this.state.msg.pkg.get("stateMgr.cap.fail"));
} else {
this.update(updater().updateLogin);
}
@ -114,11 +114,10 @@ export class StateMgr extends React.Component<Props, State, {}> {
return (
<RootFrame
browser={this.state.browser}
panes={{
panes: this.state.panes,
login: this.state.login,
admin: this.state.admin,
}}
msg={this.state.msg}
panes={this.state.panes}
login={this.state.login}
admin={this.state.admin}
update={this.update}
/>
);

View file

@ -18,6 +18,8 @@ import { UploadEntry } from "../worker/interface";
import { Up } from "../worker/upload_mgr";
import { alertMsg } from "../common/env";
import { MsgPackage } from "../i18n/msger";
export class Updater {
props: ICoreState;
private usersClient: IUsersClient = new UsersClient("");
@ -220,7 +222,11 @@ export class Updater {
return resp.status === 200;
};
setUser = async (userID: string, role: string, quota: Quota): Promise<boolean> => {
setUser = async (
userID: string,
role: string,
quota: Quota
): Promise<boolean> => {
const resp = await this.usersClient.setUser(userID, role, quota);
return resp.status === 200;
};
@ -306,11 +312,9 @@ export class Updater {
};
initIsAuthed = async (): Promise<void> => {
return this
.isAuthed()
.then((isAuthed) => {
updater().setAuthed(isAuthed);
});
return this.isAuthed().then((isAuthed) => {
updater().setAuthed(isAuthed);
});
};
setAuthed = (isAuthed: boolean) => {
@ -331,6 +335,21 @@ export class Updater {
return resp.status === 200;
};
setLan = (lan: string) => {
switch (lan) {
case "en_US":
this.props.msg.lan = "en_US";
this.props.msg.pkg = MsgPackage.get(lan);
break;
case "zh_CN":
this.props.msg.lan = "zh_CN";
this.props.msg.pkg = MsgPackage.get(lan);
break;
default:
alertMsg("language package not found");
}
};
updateBrowser = (prevState: ICoreState): ICoreState => {
return {
...prevState,
@ -358,6 +377,13 @@ export class Updater {
admin: { ...prevState.admin, ...this.props.admin },
};
};
updateMsg = (prevState: ICoreState): ICoreState => {
return {
...prevState,
msg: { ...prevState.msg, ...this.props.msg },
};
};
}
export let coreUpdater = new Updater();

View file

@ -1,12 +1,13 @@
import * as React from "react";
import { ICoreState } from "./core_state";
import { Props as LoginProps } from "./pane_login";
import { ICoreState, MsgProps } from "./core_state";
import { LoginProps } from "./pane_login";
import { updater } from "./state_updater";
export interface State {}
export interface Props {
login: LoginProps;
msg: MsgProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -57,13 +58,13 @@ export class TopBar extends React.Component<Props, State, {}> {
onClick={this.showSettings}
className="grey1-bg white-font margin-r-m"
>
Settings
{this.props.msg.pkg.get("settings")}
</button>
<button
onClick={this.showAdmin}
className="grey1-bg white-font margin-r-m"
>
Admin
{this.props.msg.pkg.get("admin")}
</button>
</span>
</div>

View file

@ -1,8 +1,64 @@
import { Map } from "immutable";
const adContent = `
If you have any question, you can submit an issue in the github repo. But owner may be out of the office in the weekend 😎.`;
export const msgs: Map<string, string> = Map({
"loader.top.about": "关于",
});
"stateMgr.cap.fail": "failed to get captcha id",
"browser.upload.del.fail": "Failed to delete uploadini item",
"browser.folder.add.fail": "Folder name can not be empty",
"browser.del.fail": "Please select file or folder to delete at first",
"browser.move.fail": "Source directory is same as destination directory",
"browser.share.add.fail": "Failed to enable sharing",
"browser.share.del.fail": "Failed to disable sharing",
"browser.share.del": "Stop sharing",
"browser.share.add": "Share it",
"browser.share.title": "Sharings",
"browser.folder.name": "Folder name",
"browser.folder.add": "Add Folder",
"browser.upload": "Upload",
"browser.delete": "Delete",
"browser.paste": "Paste",
"browser.select": "Select",
"browser.deselect": "Deselect",
"browser.selectAll": "Select All",
"browser.stop": "Stop",
"browser.disable": "Disable",
"browser.location": "Location",
"browser.item.title": "Items",
"panes.close": "Close",
"login.logout.fail": "Failed to log out",
"login.username": "User Name",
"login.captcha": "Captcha",
"login.pwd": "Password",
"login.login": "Login",
"login.logout": "Logout",
"settings.pwd.notSame": "Input passwords are not identical",
"settings.pwd.empty": "Password can not be empty",
"settings.pwd.notChanged": "New Password can be identical to old password",
update: "Update",
"settings.pwd.old": "current password",
"settings.pwd.new1": "new password",
"settings.pwd.new2": "input again password",
settings: "Settings",
admin: "Admin",
"update.ok": "Succeeded to update",
"update.fail": "Failed to update",
"delete.fail": "Failed to delete",
"delete.ok": "Succeeded to delete",
delete: "Delete",
spaceLimit: "Space Limit",
uploadLimit: "Upload Speed Limit",
downloadLimit: "Download Speed Limit",
"add.fail": "Failed to create",
"add.ok": "Succeeded to create",
"role.delete.warning":
"After deleting this role, some of users may not be able to login.",
"user.id": "User ID",
"user.add": "Add User",
"user.name": "User Name",
"user.role": "User Role",
"user.password": "User Password",
add: "Add",
"admin.users": "Users",
"role.add": "Add Role",
"role.name": "Role Name",
"admin.roles": "Roles",
});

View file

@ -8,13 +8,13 @@ export class Msger {
constructor(msgs: Map<string, string>) {
this.msgs = msgs;
}
getMsg(key: string): string {
m(key: string): string {
return this.msgs.get(key, "");
}
}
export class MsgPackage {
static getPkg(key: string): Map<string, string> {
static get(key: string): Map<string, string> {
switch (key) {
case "en-US":
return Map(enMsgs);

View file

@ -1,8 +1,63 @@
import { Map } from "immutable";
const adContent = `
bug等, github库提交issue, 😎.`;
export const msgs: Map<string, string> = Map({
"loader.top.about": "关于",
});
"stateMgr.cap.fail": "获取captcha id失败",
"browser.upload.del.fail": "删除上传失败",
"browser.upload.title": "正在上传",
"browser.folder.add.fail": "文件夹名不可为空",
"browser.del.fail": "至少选择一个文件或文件夹",
"browser.move.fail": "源与目标相同",
"browser.share.add.fail": "共享失败",
"browser.share.del.fail": "删除共享失败",
"browser.share.del": "停止共享",
"browser.share.add": "开始共享",
"browser.share.title": "共享列表",
"browser.folder.name": "文件夹名",
"browser.folder.add": "添加文件夹",
"browser.upload": "上传",
"browser.delete": "删除",
"browser.paste": "粘贴",
"browser.select": "选择",
"browser.deselect": "不选",
"browser.selectAll": "选择所有",
"browser.stop": "停止",
"browser.location": "位置",
"browser.item.title": "列表",
"panes.close": "关闭",
"login.logout.fail": "登出失败",
"login.username": "用户名",
"login.captcha": "验证码",
"login.pwd": "密码",
"login.login": "登入",
"login.logout": "登出",
"settings.pwd.notSame": "两次密码不同",
"settings.pwd.empty": "密码不能为空",
"settings.pwd.notChanged": "新老密码不能相同",
update: "更新",
"settings.pwd.old": "当前密码",
"settings.pwd.new1": "新密码",
"settings.pwd.new2": "再次输入新密码",
settings: "设置",
admin: "管理",
"update.ok": "更新成功",
"update.fail": "更新失败",
"delete.fail": "删除失败",
"delete.ok": "删除成功",
delete: "删除",
spaceLimit: "空间上限",
uploadLimit: "上传速度限制",
downloadLimit: "下载速度限制",
"add.fail": "新增失败",
"add.ok": "新增成功",
"role.delete.warning": "注意删除角色后,该角色的用户可能不能登入",
"user.id": "用户ID",
"user.add": "新增用户",
"user.name": "用户名",
"user.role": "橘色",
"user.password": "密码",
add: "新增",
"admin.users": "用户列表",
"role.add": "新增角色",
"role.name": "角色名字",
"admin.roles": "角色列表",
});