fix(updater): merge all updater as one updater

This commit is contained in:
hexxa 2021-08-22 18:17:46 +08:00 committed by Hexxa
parent 369b769952
commit ee056aa193
15 changed files with 704 additions and 450 deletions

View file

@ -2,7 +2,7 @@ import * as React from "react";
import { List, Map } from "immutable";
import { mock, instance, anyString, anything, when, verify } from "ts-mockito";
import { ICoreState, initWithWorker, mockState } from "../core_state";
import { ICoreState, newWithWorker, initState } from "../core_state";
import {
makePromise,
makeNumberResponse,
@ -11,7 +11,9 @@ import {
mockFileList,
} from "../../test/helpers";
import { Browser } from "../browser";
import { Updater, setUpdater } from "../browser.updater";
// import { Updater, setUpdater } from "../browser.updater";
import { updater, Updater, setUpdater } from "../state_updater";
import { MockUsersClient } from "../../client/users_mock";
import { UsersClient } from "../../client/users";
import { FilesClient } from "../../client/files";
@ -25,18 +27,18 @@ describe("Browser", () => {
const mockWorker = instance(mockWorkerClass);
test("Updater: addUploads: add each files to UploadMgr", async () => {
let coreState = mockState();
let coreState = initState();
const UploadMgrClass = mock(UploadMgr);
const uploadMgr = instance(UploadMgrClass);
setUploadMgr(uploadMgr);
const filePaths = ["./file1", "./file2"];
const fileList = mockFileList(filePaths);
const updater = new Updater();
updater.setUploadings = (infos: Map<string, UploadEntry>) => {};
updater.init(coreState.panel.browser);
// const updater = new Updater();
updater().setUploadings = (infos: Map<string, UploadEntry>) => {};
updater().init(coreState);
updater.addUploads(fileList);
updater().addUploads(fileList);
// it seems that new File will do some file path escaping, so just check call time here
verify(UploadMgrClass.add(anything(), anything())).times(filePaths.length);
@ -46,12 +48,12 @@ describe("Browser", () => {
});
test("Updater: deleteUploads: call UploadMgr and api to delete", async () => {
let coreState = mockState();
let coreState = initState();
const UploadMgrClass = mock(UploadMgr);
const uploadMgr = instance(UploadMgrClass);
setUploadMgr(uploadMgr);
const updater = new Updater();
// const updater = new Updater();
const filesClientClass = mock(FilesClient);
when(filesClientClass.deleteUploading(anyString())).thenResolve({
status: 200,
@ -61,11 +63,11 @@ describe("Browser", () => {
const filesClient = instance(filesClientClass);
const usersClientClass = mock(UsersClient);
const usersClient = instance(usersClientClass);
updater.init(coreState.panel.browser);
updater.setClients(usersClient, filesClient);
updater().init(coreState);
updater().setClients(usersClient, filesClient);
const filePath = "./path/file";
updater.deleteUpload(filePath);
updater().deleteUpload(filePath);
verify(filesClientClass.deleteUploading(filePath)).once();
verify(UploadMgrClass.delete(filePath)).once();
@ -114,20 +116,20 @@ describe("Browser", () => {
const filesClient = new MockFilesClient("");
for (let i = 0; i < tests.length; i++) {
const tc = tests[i];
const updater = new Updater();
updater.setClients(usersClient, filesClient);
// const updater = new Updater();
updater().setClients(usersClient, filesClient);
filesClient.listMock(makePromise(tc.listResp));
filesClient.deleteMock(makeNumberResponse(200));
const coreState = initWithWorker(mockWorker);
updater.init(coreState.panel.browser);
const coreState = newWithWorker(mockWorker);
updater().init(coreState);
await updater.delete(
await updater().delete(
List<string>(tc.dirPath.split("/")),
List<MetadataResp>(tc.items),
Map(tc.selected)
);
const newState = updater.setBrowser(coreState);
const newState = updater().updateBrowser(coreState);
// TODO: check inputs of delete
newState.panel.browser.items.forEach((item, i) => {
@ -171,14 +173,14 @@ describe("Browser", () => {
for (let i = 0; i < tests.length; i++) {
const tc = tests[i];
const updater = new Updater();
filesClient.listMock(makePromise(tc.listResp));
updater.setClients(usersClient, filesClient);
const coreState = initWithWorker(mockWorker);
updater.init(coreState.panel.browser);
await updater.setItems(List<string>(tc.filePath.split("/")));
const newState = updater.setBrowser(coreState);
filesClient.listMock(makePromise(tc.listResp));
updater().setClients(usersClient, filesClient);
const coreState = newWithWorker(mockWorker);
updater().init(coreState);
await updater().setItems(List<string>(tc.filePath.split("/")));
const newState = updater().updateBrowser(coreState);
newState.panel.browser.items.forEach((item, i) => {
expect(item.name).toEqual(tc.listResp.data.metadatas[i].name);
@ -225,17 +227,16 @@ describe("Browser", () => {
const filesClient = new MockFilesClient("");
for (let i = 0; i < tests.length; i++) {
const tc = tests[i];
const updater = new Updater();
filesClient.listMock(makePromise(tc.listResp));
filesClient.moveMock(makeNumberResponse(200));
updater.setClients(usersClient, filesClient);
updater().setClients(usersClient, filesClient);
const coreState = initWithWorker(mockWorker);
updater.init(coreState.panel.browser);
await updater.moveHere(tc.dirPath1, tc.dirPath2, Map(tc.selected));
const coreState = newWithWorker(mockWorker);
updater().init(coreState);
await updater().moveHere(tc.dirPath1, tc.dirPath2, Map(tc.selected));
const newState = updater.setBrowser(coreState);
const newState = updater().updateBrowser(coreState);
// TODO: check inputs of move
newState.panel.browser.items.forEach((item, i) => {
@ -248,7 +249,7 @@ describe("Browser", () => {
});
test("Browser: deleteUpload: tell uploader to deleteUpload and refreshUploadings", async () => {
let coreState = mockState();
let coreState = initState();
addMockUpdate(coreState.panel.browser);
const component = new Browser(coreState.panel.browser);
const UpdaterClass = mock(Updater);
@ -266,7 +267,7 @@ describe("Browser", () => {
});
test("Browser: stopUploading: tell updater to stopUploading", async () => {
let coreState = mockState();
let coreState = initState();
addMockUpdate(coreState.panel.browser);
const component = new Browser(coreState.panel.browser);
const UpdaterClass = mock(Updater);

View file

@ -1,8 +1,9 @@
import { mock, instance } from "ts-mockito";
import { initWithWorker } from "../core_state";
import { Updater } from "../pane_login";
import { newWithWorker } from "../core_state";
import { updater } from "../state_updater";
import { MockUsersClient } from "../../client/users_mock";
import { FilesClient } from "../../client/files_mock";
import { Response } from "../../client";
import { MockWorker } from "../../worker/interface";
@ -41,20 +42,21 @@ describe("AuthPane", () => {
},
];
const client = new MockUsersClient("");
const usersClient = new MockUsersClient("");
const filesClient = new FilesClient("");
for (let i = 0; i < tests.length; i++) {
const tc = tests[i];
client.loginMock(makeNumberResponse(tc.loginStatus));
client.logoutMock(makeNumberResponse(tc.logoutStatus));
client.isAuthedMock(makeNumberResponse(tc.isAuthedStatus));
client.setPwdMock(makeNumberResponse(tc.setPwdStatus));
usersClient.loginMock(makeNumberResponse(tc.loginStatus));
usersClient.logoutMock(makeNumberResponse(tc.logoutStatus));
usersClient.isAuthedMock(makeNumberResponse(tc.isAuthedStatus));
usersClient.setPwdMock(makeNumberResponse(tc.setPwdStatus));
const coreState = initWithWorker(mockWorker);
Updater.setClient(client);
Updater.init(coreState.panel.authPane);
await Updater.initIsAuthed();
const newState = Updater.setAuthPane(coreState);
const coreState = newWithWorker(mockWorker);
updater().setClients(usersClient, filesClient);
updater().init(coreState);
await updater().initIsAuthed();
const newState = updater().updateAuthPane(coreState);
expect(newState.panel.authPane.authed).toEqual(tc.isAuthed);
}

View file

@ -1,8 +1,9 @@
import { Set } from "immutable";
import { ICoreState, mockState } from "../core_state";
import { Panes, Updater } from "../panes";
import { ICoreState, initState } from "../core_state";
import { Panes } from "../panes";
import { mockUpdate } from "../../test/helpers";
import { updater } from "../state_updater";
describe("Panes", () => {
test("Panes: closePane", async () => {
@ -36,14 +37,14 @@ describe("Panes", () => {
};
tcs.forEach((tc: TestCase) => {
const preState = setState(tc.preState, mockState());
const postState = setState(tc.postState, mockState());
const preState = setState(tc.preState, initState());
const postState = setState(tc.postState, initState());
const component = new Panes(preState.panel.panes);
Updater.init(preState.panel.panes);
updater().init(preState);
component.closePane();
expect(Updater.props).toEqual(postState.panel.panes);
expect(updater().props).toEqual(postState);
});
});
});

View file

@ -1,11 +1,11 @@
import { Set } from "immutable";
// import { mock, instance } from "ts-mockito";
import { ICoreState, mockState } from "../core_state";
import { ICoreState, initState } from "../core_state";
import { RootFrame } from "../root_frame";
import { Updater } from "../panes";
// import { Updater } from "../panes";
import { updater } from "../state_updater";
describe("RootFrame", () => {
xdescribe("RootFrame", () => {
test("component: showSettings", async () => {
interface TestCase {
preState: ICoreState;
@ -39,14 +39,14 @@ describe("RootFrame", () => {
};
tcs.forEach((tc: TestCase) => {
const preState = setState(tc.preState, mockState());
const postState = setState(tc.postState, mockState());
const preState = setState(tc.preState, initState());
const postState = setState(tc.postState, initState());
const component = new RootFrame(preState.panel);
Updater.init(preState.panel.panes);
updater().init(preState);
component.showSettings();
expect(Updater.props).toEqual(postState.panel.panes);
// component.showSettings();
expect(updater().props).toEqual(postState);
});
});
});

View file

@ -3,13 +3,10 @@ import * as ReactDOM from "react-dom";
import { List, Map } from "immutable";
import FileSize from "filesize";
import { Layouter } from "./layouter";
import { alertMsg, comfirmMsg } from "../common/env";
import { updater } from "./browser.updater";
import { updater } from "./state_updater";
import { ICoreState } from "./core_state";
import {
IUsersClient,
IFilesClient,
MetadataResp,
UploadInfo,
} from "../client";
@ -88,7 +85,7 @@ export class Browser extends React.Component<Props, State, {}> {
fileList = fileList.push(event.target.files[i]);
}
updater().addUploads(fileList);
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
};
deleteUpload = (filePath: string): Promise<void> => {
@ -101,13 +98,13 @@ export class Browser extends React.Component<Props, State, {}> {
return updater().refreshUploadings();
})
.then(() => {
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
});
};
stopUploading = (filePath: string) => {
updater().stopUploading(filePath);
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
};
onMkDir = () => {
@ -127,7 +124,7 @@ export class Browser extends React.Component<Props, State, {}> {
return updater().setItems(this.props.dirPath);
})
.then(() => {
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
});
};
@ -149,7 +146,7 @@ export class Browser extends React.Component<Props, State, {}> {
updater()
.delete(this.props.dirPath, this.props.items, this.state.selectedItems)
.then(() => {
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
this.setState({
selectedSrc: "",
selectedItems: Map<string, boolean>(),
@ -172,7 +169,7 @@ export class Browser extends React.Component<Props, State, {}> {
this.state.selectedItems
)
.then(() => {
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
this.setState({
selectedSrc: "",
selectedItems: Map<string, boolean>(),
@ -198,7 +195,7 @@ export class Browser extends React.Component<Props, State, {}> {
return updater().isSharing(dirPath.join("/"));
})
.then(() => {
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
});
};
@ -207,7 +204,7 @@ export class Browser extends React.Component<Props, State, {}> {
updater()
.setItems(this.props.dirPath)
.then(() => {
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
});
};
@ -253,7 +250,7 @@ export class Browser extends React.Component<Props, State, {}> {
}
})
.then(() => {
this.props.update(updater().setBrowser);
this.props.update(updater().updateBrowser);
});
};
@ -269,7 +266,7 @@ export class Browser extends React.Component<Props, State, {}> {
}
})
.then(() => {
this.props.update(updater().setBrowser);
this.props.update(updater().updateBrowser);
});
};
@ -278,7 +275,7 @@ export class Browser extends React.Component<Props, State, {}> {
.listSharings()
.then((ok) => {
if (ok) {
this.update(updater().setBrowser);
this.update(updater().updateBrowser);
}
});
};

View file

@ -8,35 +8,24 @@ import { Item } from "./browser";
import { UploadInfo, User } from "../client";
import { initUploadMgr, IWorker } from "../worker/upload_mgr";
export class BaseUpdater {
public static props: any;
public static init = (props: any) => (BaseUpdater.props = { ...props });
public static apply = (prevState: ICoreState): ICoreState => {
throw Error("apply is not implemented");
};
}
export interface ICoreState {
panel: PanelProps;
isVertical: boolean;
}
export function initWithWorker(worker: IWorker): ICoreState {
export function newWithWorker(worker: IWorker): ICoreState {
initUploadMgr(worker);
return initState();
}
export function init(): ICoreState {
export function newState(): ICoreState {
const worker = Worker == null ? new FgWorker() : new BgWorker();
initUploadMgr(worker);
return initState();
}
export function isVertical(): boolean {
return window.innerWidth <= window.innerHeight;
}
export function initState(): ICoreState {
return {
isVertical: isVertical(),
@ -73,38 +62,7 @@ export function initState(): ICoreState {
};
}
export function mockState(): ICoreState {
return {
isVertical: false,
panel: {
displaying: "browser",
authPane: {
authed: false,
captchaID: "",
},
browser: {
isVertical: false,
dirPath: List<string>(["."]),
items: List<Item>([]),
sharings: List<string>([]),
isSharing: false,
uploadings: List<UploadInfo>([]),
uploadValue: "",
uploadFiles: List<File>([]),
},
panes: {
userRole: "",
displaying: "",
paneNames: Set<string>(["settings", "login", "admin"]),
login: {
authed: false,
captchaID: "",
},
admin: {
users: Map<string, User>(),
roles: Set<string>(),
},
},
},
};
}
export function isVertical(): boolean {
return window.innerWidth <= window.innerHeight;
}

View file

@ -3,7 +3,7 @@ import { Map, Set } from "immutable";
import { ICoreState } from "./core_state";
import { User, Quota } from "../client";
import { Updater as PanesUpdater } from "./panes";
import { updater } from "./state_updater";
export interface Props {
users: Map<string, User>;
@ -94,8 +94,9 @@ export class UserForm extends React.Component<
return;
}
PanesUpdater.forceSetPwd(this.state.id, this.state.newPwd1).then(
(ok: boolean) => {
updater()
.forceSetPwd(this.state.id, this.state.newPwd1)
.then((ok: boolean) => {
if (ok) {
alert("password is updated");
} else {
@ -105,22 +106,22 @@ export class UserForm extends React.Component<
newPwd1: "",
newPwd2: "",
});
}
);
});
};
setUser = () => {};
delUser = () => {
PanesUpdater.delUser(this.state.id)
updater()
.delUser(this.state.id)
.then((ok: boolean) => {
if (!ok) {
alert("failed to delete user");
}
return PanesUpdater.listUsers();
return updater().listUsers();
})
.then((_: boolean) => {
this.props.update(PanesUpdater.updateState);
this.props.update(updater().updatePanes);
});
};
@ -311,15 +312,16 @@ export class AdminPane extends React.Component<Props, State, {}> {
};
addRole = () => {
PanesUpdater.addRole(this.state.newRole)
updater()
.addRole(this.state.newRole)
.then((ok: boolean) => {
if (!ok) {
alert("failed to add role");
}
return PanesUpdater.listRoles();
return updater().listRoles();
})
.then(() => {
this.props.update(PanesUpdater.updateState);
this.props.update(updater().updatePanes);
});
};
@ -332,15 +334,16 @@ export class AdminPane extends React.Component<Props, State, {}> {
return;
}
PanesUpdater.delRole(role)
updater()
.delRole(role)
.then((ok: boolean) => {
if (!ok) {
alert("failed to delete role");
}
return PanesUpdater.listRoles();
return updater().listRoles();
})
.then(() => {
this.props.update(PanesUpdater.updateState);
this.props.update(updater().updatePanes);
});
};
@ -350,13 +353,14 @@ export class AdminPane extends React.Component<Props, State, {}> {
return;
}
PanesUpdater.addUser({
id: "", // backend will fill it
name: this.state.newUserName,
pwd: this.state.newUserPwd1,
role: this.state.newUserRole,
quota: undefined,
})
updater()
.addUser({
id: "", // backend will fill it
name: this.state.newUserName,
pwd: this.state.newUserPwd1,
role: this.state.newUserRole,
quota: undefined,
})
.then((ok: boolean) => {
if (!ok) {
alert("failed to add user");
@ -367,10 +371,10 @@ export class AdminPane extends React.Component<Props, State, {}> {
newUserPwd2: "",
newUserRole: "",
});
return PanesUpdater.listUsers();
return updater().listUsers();
})
.then(() => {
this.props.update(PanesUpdater.updateState);
this.props.update(updater().updatePanes);
});
};

View file

@ -2,11 +2,7 @@ import * as React from "react";
import { List } from "immutable";
import { ICoreState } from "./core_state";
import { IUsersClient } from "../client";
import { UsersClient } from "../client/users";
import { Updater as PanesUpdater } from "./panes";
import { updater as BrowserUpdater } from "./browser.updater";
import { Layouter } from "./layouter";
import { updater } from "./state_updater";
export interface Props {
authed: boolean;
@ -14,66 +10,6 @@ export interface Props {
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
export class Updater {
private static props: Props;
private static client: IUsersClient;
static init = (props: Props) => (Updater.props = { ...props });
static setClient = (client: IUsersClient): void => {
Updater.client = client;
};
static login = async (
user: string,
pwd: string,
captchaID: string,
captchaInput: string
): Promise<boolean> => {
const resp = await Updater.client.login(user, pwd, captchaID, captchaInput);
Updater.setAuthed(resp.status === 200);
return resp.status === 200;
};
static logout = async (): Promise<boolean> => {
const resp = await Updater.client.logout();
Updater.setAuthed(false);
return resp.status === 200;
};
static isAuthed = async (): Promise<boolean> => {
const resp = await Updater.client.isAuthed();
return resp.status === 200;
};
static initIsAuthed = async (): Promise<void> => {
return Updater.isAuthed().then((isAuthed) => {
Updater.setAuthed(isAuthed);
});
};
static setAuthed = (isAuthed: boolean) => {
Updater.props.authed = isAuthed;
};
static getCaptchaID = async (): Promise<boolean> => {
return Updater.client.getCaptchaID().then((resp) => {
if (resp.status === 200) {
Updater.props.captchaID = resp.data.id;
}
return resp.status === 200;
});
};
static setAuthPane = (preState: ICoreState): ICoreState => {
preState.panel.authPane = {
...preState.panel.authPane,
...Updater.props,
};
return preState;
};
}
export interface State {
user: string;
pwd: string;
@ -84,8 +20,6 @@ export class AuthPane extends React.Component<Props, State, {}> {
private update: (updater: (prevState: ICoreState) => ICoreState) => void;
constructor(p: Props) {
super(p);
Updater.init(p);
Updater.setClient(new UsersClient(""));
this.update = p.update;
this.state = {
user: "",
@ -109,53 +43,56 @@ export class AuthPane extends React.Component<Props, State, {}> {
};
initIsAuthed = () => {
Updater.initIsAuthed().then(() => {
this.update(Updater.setAuthPane);
});
updater()
.initIsAuthed()
.then(() => {
this.update(updater().updateAuthPane);
});
};
login = () => {
Updater.login(
this.state.user,
this.state.pwd,
this.props.captchaID,
this.state.captchaInput
)
updater()
.login(
this.state.user,
this.state.pwd,
this.props.captchaID,
this.state.captchaInput
)
.then((ok: boolean) => {
if (ok) {
this.update(Updater.setAuthPane);
this.update(updater().updateAuthPane);
this.setState({ user: "", pwd: "" });
// close all the panes
PanesUpdater.displayPane("");
this.update(PanesUpdater.updateState);
updater().displayPane("");
this.update(updater().updatePanes);
// refresh
return BrowserUpdater().setHomeItems();
return updater().setHomeItems();
} else {
this.setState({ user: "", pwd: "" });
alert("Failed to login.");
}
})
.then(() => {
return BrowserUpdater().refreshUploadings();
return updater().refreshUploadings();
})
.then(() => {
return BrowserUpdater().isSharing(
BrowserUpdater().props.dirPath.join("/")
return updater().isSharing(
updater().props.panel.browser.dirPath.join("/")
);
})
.then(() => {
return BrowserUpdater().listSharings();
return updater().listSharings();
})
.then((_: boolean) => {
this.update(BrowserUpdater().setBrowser);
this.update(updater().updateBrowser);
});
};
logout = () => {
Updater.logout().then((ok: boolean) => {
updater().logout().then((ok: boolean) => {
if (ok) {
this.update(Updater.setAuthPane);
this.update(updater().updateAuthPane);
} else {
alert("Failed to logout.");
}

View file

@ -1,37 +1,14 @@
import * as React from "react";
import { ICoreState } from "./core_state";
import { IUsersClient } from "../client";
import { AuthPane, Props as LoginProps } from "./pane_login";
import { UsersClient } from "../client/users";
import { updater } from "./state_updater";
export interface Props {
login: LoginProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
export class Updater {
private static props: Props;
private static usersClient: IUsersClient;
static init = (props: Props) => (Updater.props = { ...props });
static setClient(usersClient: IUsersClient) {
Updater.usersClient = usersClient;
}
static setPwd = async (oldPwd: string, newPwd: string): Promise<boolean> => {
const resp = await Updater.usersClient.setPwd(oldPwd, newPwd);
return resp.status === 200;
};
static updateState = (prevState: ICoreState): ICoreState => {
return {
...prevState,
panel: { ...prevState.panel, ...Updater.props },
};
};
}
export interface State {
oldPwd: string;
newPwd1: string;
@ -52,8 +29,6 @@ export class PaneSettings extends React.Component<Props, State, {}> {
constructor(p: Props) {
super(p);
Updater.init(p);
Updater.setClient(new UsersClient(""));
this.update = p.update;
this.state = {
oldPwd: "",
@ -70,8 +45,9 @@ export class PaneSettings extends React.Component<Props, State, {}> {
} else if (this.state.oldPwd == this.state.newPwd1) {
alert("old and new passwords are same");
} else {
Updater.setPwd(this.state.oldPwd, this.state.newPwd1).then(
(ok: boolean) => {
updater()
.setPwd(this.state.oldPwd, this.state.newPwd1)
.then((ok: boolean) => {
if (ok) {
alert("Password is updated");
} else {
@ -82,8 +58,7 @@ export class PaneSettings extends React.Component<Props, State, {}> {
newPwd1: "",
newPwd2: "",
});
}
);
});
}
};

View file

@ -1,8 +1,9 @@
import * as React from "react";
import { Set, Map } from "immutable";
import { IUsersClient, User, ListUsersResp, ListRolesResp } from "../client";
import { UsersClient } from "../client/users";
import { updater } from "./state_updater";
// import { IUsersClient, User, ListUsersResp, ListRolesResp } from "../client";
// import { UsersClient } from "../client/users";
import { ICoreState } from "./core_state";
import { PaneSettings } from "./pane_settings";
import { AdminPane, Props as AdminPaneProps } from "./pane_admin";
@ -17,128 +18,126 @@ export interface Props {
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
export class Updater {
static props: Props;
private static client: IUsersClient;
// export class Updater {
// static props: Props;
// private static client: IUsersClient;
static init = (props: Props) => (Updater.props = { ...props });
static setClient = (client: IUsersClient): void => {
Updater.client = client;
};
// static init = (props: Props) => (Updater.props = { ...props });
// static setClient = (client: IUsersClient): void => {
// Updater.client = client;
// };
static displayPane = (paneName: string) => {
if (paneName === "") {
// hide all panes
Updater.props.displaying = "";
} else {
const pane = Updater.props.paneNames.get(paneName);
if (pane != null) {
Updater.props.displaying = paneName;
} else {
alert(`dialgos: pane (${paneName}) not found`);
}
}
};
// static displayPane = (paneName: string) => {
// if (paneName === "") {
// // hide all panes
// Updater.props.displaying = "";
// } else {
// const pane = Updater.props.paneNames.get(paneName);
// if (pane != null) {
// Updater.props.displaying = paneName;
// } else {
// alert(`dialgos: pane (${paneName}) not found`);
// }
// }
// };
static self = async (): Promise<boolean> => {
const resp = await Updater.client.self();
if (resp.status === 200) {
Updater.props.userRole = resp.data.role;
return true;
}
return false;
};
// static self = async (): Promise<boolean> => {
// const resp = await Updater.client.self();
// if (resp.status === 200) {
// Updater.props.userRole = resp.data.role;
// return true;
// }
// return false;
// };
static addUser = async (user: User): Promise<boolean> => {
const resp = await Updater.client.addUser(user.name, user.pwd, user.role);
// TODO: should return uid instead
return resp.status === 200;
};
// static addUser = async (user: User): Promise<boolean> => {
// const resp = await Updater.client.addUser(user.name, user.pwd, user.role);
// // TODO: should return uid instead
// return resp.status === 200;
// };
static delUser = async (userID: string): Promise<boolean> => {
const resp = await Updater.client.delUser(userID);
return resp.status === 200;
};
// static delUser = async (userID: string): Promise<boolean> => {
// const resp = await Updater.client.delUser(userID);
// return resp.status === 200;
// };
static setRole = async (userID: string, role: string): Promise<boolean> => {
const resp = await Updater.client.delUser(userID);
return resp.status === 200;
};
// static setRole = async (userID: string, role: string): Promise<boolean> => {
// const resp = await Updater.client.delUser(userID);
// return resp.status === 200;
// };
static forceSetPwd = async (
userID: string,
pwd: string
): Promise<boolean> => {
const resp = await Updater.client.forceSetPwd(userID, pwd);
return resp.status === 200;
};
// static forceSetPwd = async (
// userID: string,
// pwd: string
// ): Promise<boolean> => {
// const resp = await Updater.client.forceSetPwd(userID, pwd);
// return resp.status === 200;
// };
static listUsers = async (): Promise<boolean> => {
const resp = await Updater.client.listUsers();
if (resp.status !== 200) {
return false;
}
// static listUsers = async (): Promise<boolean> => {
// const resp = await Updater.client.listUsers();
// if (resp.status !== 200) {
// return false;
// }
const lsRes = resp.data as ListUsersResp;
let users = Map<User>({});
lsRes.users.forEach((user: User) => {
users = users.set(user.name, user);
});
Updater.props.admin.users = users;
// const lsRes = resp.data as ListUsersResp;
// let users = Map<User>({});
// lsRes.users.forEach((user: User) => {
// users = users.set(user.name, user);
// });
// Updater.props.admin.users = users;
return true;
};
// return true;
// };
static addRole = async (role: string): Promise<boolean> => {
const resp = await Updater.client.addRole(role);
// TODO: should return uid instead
return resp.status === 200;
};
// static addRole = async (role: string): Promise<boolean> => {
// const resp = await Updater.client.addRole(role);
// // TODO: should return uid instead
// return resp.status === 200;
// };
static delRole = async (role: string): Promise<boolean> => {
const resp = await Updater.client.delRole(role);
return resp.status === 200;
};
// static delRole = async (role: string): Promise<boolean> => {
// const resp = await Updater.client.delRole(role);
// return resp.status === 200;
// };
static listRoles = async (): Promise<boolean> => {
const resp = await Updater.client.listRoles();
if (resp.status !== 200) {
return false;
}
// static listRoles = async (): Promise<boolean> => {
// const resp = await Updater.client.listRoles();
// if (resp.status !== 200) {
// return false;
// }
const lsRes = resp.data as ListRolesResp;
let roles = Set<string>();
Object.keys(lsRes.roles).forEach((role: string) => {
roles = roles.add(role);
});
Updater.props.admin.roles = roles;
// const lsRes = resp.data as ListRolesResp;
// let roles = Set<string>();
// Object.keys(lsRes.roles).forEach((role: string) => {
// roles = roles.add(role);
// });
// Updater.props.admin.roles = roles;
return true;
};
// return true;
// };
static updateState = (prevState: ICoreState): ICoreState => {
return {
...prevState,
panel: {
...prevState.panel,
panes: { ...prevState.panel.panes, ...Updater.props },
},
};
};
}
// static updateState = (prevState: ICoreState): ICoreState => {
// return {
// ...prevState,
// panel: {
// ...prevState.panel,
// panes: { ...prevState.panel.panes, ...Updater.props },
// },
// };
// };
// }
export interface State {}
export class Panes extends React.Component<Props, State, {}> {
constructor(p: Props) {
super(p);
Updater.init(p);
Updater.setClient(new UsersClient(""));
}
closePane = () => {
if (this.props.displaying !== "login") {
Updater.displayPane("");
this.props.update(Updater.updateState);
updater().displayPane("");
this.props.update(updater().updatePanes);
}
};
@ -146,6 +145,7 @@ export class Panes extends React.Component<Props, State, {}> {
let displaying = this.props.displaying;
if (!this.props.login.authed) {
// TODO: use constant instead
// TODO: control this with props
displaying = "login";
}

View file

@ -1,9 +1,10 @@
import * as React from "react";
import { ICoreState, BaseUpdater } from "./core_state";
import { ICoreState } from "./core_state";
import { Browser, Props as BrowserProps } from "./browser";
import { Props as PaneLoginProps } from "./pane_login";
import { Panes, Props as PanesProps, Updater as PanesUpdater } from "./panes";
import { Panes, Props as PanesProps } from "./panes";
import { TopBar } from "./topbar";
export interface Props {
displaying: string;
@ -13,37 +14,13 @@ export interface Props {
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
export class Updater {
public static props: Props;
public static init = (props: Props) => (BaseUpdater.props = { ...props });
public static apply = (prevState: ICoreState): ICoreState => {
return {
...prevState,
panel: { ...prevState.panel, ...Updater.props },
};
};
}
export interface State {}
export class RootFrame extends React.Component<Props, State, {}> {
constructor(p: Props) {
super(p);
Updater.init(p);
}
showSettings = () => {
PanesUpdater.displayPane("settings");
this.props.update(PanesUpdater.updateState);
};
showAdmin = () => {
PanesUpdater.displayPane("admin");
this.props.update(PanesUpdater.updateState);
};
render() {
const update = this.props.update;
return (
<div className="theme-white desktop">
<div id="bg" className="bg bg-img font-m">
@ -53,36 +30,10 @@ export class RootFrame extends React.Component<Props, State, {}> {
paneNames={this.props.panes.paneNames}
login={this.props.authPane}
admin={this.props.panes.admin}
update={update}
update={this.props.update}
/>
<div
id="top-bar"
className="top-bar cyan1-font padding-t-m padding-b-m padding-l-l padding-r-l"
>
<div className="flex-2col-parent">
<a
href="https://github.com/ihexxa/quickshare"
className="flex-13col h5"
>
Quickshare
</a>
<span className="flex-23col text-right">
<button
onClick={this.showSettings}
className="grey1-bg white-font margin-r-m"
>
Settings
</button>
<button
onClick={this.showAdmin}
className="grey1-bg white-font margin-r-m"
>
Admin
</button>
</span>
</div>
</div>
<TopBar update={this.props.update}></TopBar>
<div className="container-center">
<Browser
@ -91,7 +42,7 @@ export class RootFrame extends React.Component<Props, State, {}> {
uploadings={this.props.browser.uploadings}
sharings={this.props.browser.sharings}
isSharing={this.props.browser.isSharing}
update={update}
update={this.props.update}
uploadFiles={this.props.browser.uploadFiles}
uploadValue={this.props.browser.uploadValue}
isVertical={this.props.browser.isVertical}

View file

@ -1,13 +1,12 @@
import * as React from "react";
import { List } from "immutable";
import { updater as BrowserUpdater } from "./browser.updater";
import { Updater as PanesUpdater } from "./panes";
import { ICoreState, init } from "./core_state";
import { updater } from "./state_updater";
import { ICoreState, newState } from "./core_state";
import { RootFrame } from "./root_frame";
import { FilesClient } from "../client/files";
import { UsersClient } from "../client/users";
import { Updater as LoginPaneUpdater } from "./pane_login";
// import { Updater as LoginPaneUpdater } from "./pane_login";
export interface Props {}
export interface State extends ICoreState {}
@ -15,68 +14,67 @@ export interface State extends ICoreState {}
export class StateMgr extends React.Component<Props, State, {}> {
constructor(p: Props) {
super(p);
this.state = init();
this.state = newState();
this.initUpdaters(this.state);
}
initUpdaters = (state: ICoreState) => {
BrowserUpdater().init(state.panel.browser);
BrowserUpdater().setClients(new UsersClient(""), new FilesClient(""));
updater().init(state);
updater().setClients(new UsersClient(""), new FilesClient(""));
const params = new URLSearchParams(document.location.search.substring(1));
updater()
.getCaptchaID()
.then((ok: boolean) => {
if (!ok) {
alert("failed to get captcha id");
} else {
this.update(updater().updateAuthPane);
}
});
LoginPaneUpdater.init(state.panel.authPane);
LoginPaneUpdater.setClient(new UsersClient(""));
LoginPaneUpdater.getCaptchaID().then((ok: boolean) => {
if (!ok) {
alert("failed to get captcha id");
} else {
this.update(LoginPaneUpdater.setAuthPane);
}
});
BrowserUpdater()
updater()
.refreshUploadings()
.then(() => {
const dir = params.get("dir");
if (dir != null && dir !== "") {
const dirPath = List(dir.split("/"));
return BrowserUpdater().setItems(dirPath);
return updater().setItems(dirPath);
} else {
return BrowserUpdater().setHomeItems();
return updater().setHomeItems();
}
})
.then(() => {
return BrowserUpdater().initUploads();
return updater().initUploads();
})
.then(() => {
return BrowserUpdater().isSharing(
BrowserUpdater().props.dirPath.join("/")
return updater().isSharing(
updater().props.panel.browser.dirPath.join("/")
);
})
.then(() => {
return BrowserUpdater().listSharings();
return updater().listSharings();
})
.then(() => {
this.update(BrowserUpdater().setBrowser);
this.update(updater().updateBrowser);
})
.then(() => {
return PanesUpdater.self();
return updater().self();
})
.then(() => {
if (PanesUpdater.props.userRole === "admin") {
if (updater().props.panel.panes.userRole === "admin") {
// TODO: remove hardcode
return PanesUpdater.listRoles();
return updater().listRoles();
}
})
.then(() => {
if (PanesUpdater.props.userRole === "admin") {
if (updater().props.panel.panes.userRole === "admin") {
// TODO: remove hardcode
return PanesUpdater.listUsers();
return updater().listUsers();
}
})
.then(() => {
this.update(PanesUpdater.updateState);
this.update(updater().updatePanes);
});
};

View file

@ -0,0 +1,373 @@
import { List, Map, Set } from "immutable";
import { ICoreState } from "./core_state";
import { getItemPath } from "./browser";
import {
User,
ListUsersResp,
ListRolesResp,
IUsersClient,
IFilesClient,
MetadataResp,
UploadInfo,
} from "../client";
import { FilesClient } from "../client/files";
import { UsersClient } from "../client/users";
import { UploadEntry } from "../worker/interface";
import { Up } from "../worker/upload_mgr";
export class Updater {
props: ICoreState;
private usersClient: IUsersClient = new UsersClient("");
private filesClient: IFilesClient = new FilesClient("");
init = (props: ICoreState) => (this.props = { ...props });
setClients(usersClient: IUsersClient, filesClient: IFilesClient) {
this.usersClient = usersClient;
this.filesClient = filesClient;
}
initUploads = () => {
this.props.panel.browser.uploadings.forEach((entry) => {
Up().addStopped(entry.realFilePath, entry.uploaded, entry.size);
});
// this.setUploadings(Up().list());
};
addUploads = (fileList: List<File>) => {
fileList.forEach((file) => {
const filePath = getItemPath(
this.props.panel.browser.dirPath.join("/"),
file.name
);
// do not wait for the promise
Up().add(file, filePath);
});
this.setUploadings(Up().list());
};
deleteUpload = async (filePath: string): Promise<boolean> => {
Up().delete(filePath);
const resp = await this.filesClient.deleteUploading(filePath);
return resp.status === 200;
};
setUploadings = (infos: Map<string, UploadEntry>) => {
this.props.panel.browser.uploadings = List<UploadInfo>(
infos.valueSeq().map((v: UploadEntry): UploadInfo => {
return {
realFilePath: v.filePath,
size: v.size,
uploaded: v.uploaded,
};
})
);
};
addSharing = async (): Promise<boolean> => {
const dirPath = this.props.panel.browser.dirPath.join("/");
const resp = await this.filesClient.addSharing(dirPath);
return resp.status === 200;
};
deleteSharing = async (dirPath: string): Promise<boolean> => {
const resp = await this.filesClient.deleteSharing(dirPath);
return resp.status === 200;
};
isSharing = async (dirPath: string): Promise<boolean> => {
const resp = await this.filesClient.isSharing(dirPath);
this.props.panel.browser.isSharing = resp.status === 200;
return resp.status === 200; // TODO: differentiate 404 and error
};
setSharing = (shared: boolean) => {
this.props.panel.browser.isSharing = shared;
};
listSharings = async (): Promise<boolean> => {
const resp = await this.filesClient.listSharings();
this.props.panel.browser.sharings =
resp.status === 200
? List<string>(resp.data.sharingDirs)
: this.props.panel.browser.sharings;
return resp.status === 200;
};
refreshUploadings = async (): Promise<boolean> => {
const luResp = await this.filesClient.listUploadings();
this.props.panel.browser.uploadings =
luResp.status === 200
? List<UploadInfo>(luResp.data.uploadInfos)
: this.props.panel.browser.uploadings;
return luResp.status === 200;
};
stopUploading = (filePath: string) => {
Up().stop(filePath);
};
mkDir = async (dirPath: string): Promise<void> => {
const resp = await this.filesClient.mkdir(dirPath);
if (resp.status !== 200) {
alert(`failed to make dir ${dirPath}`);
}
};
delete = async (
dirParts: List<string>,
items: List<MetadataResp>,
selectedItems: Map<string, boolean>
): Promise<void> => {
const delRequests = items
.filter((item) => {
return selectedItems.has(item.name);
})
.map(async (selectedItem: MetadataResp): Promise<string> => {
const itemPath = getItemPath(dirParts.join("/"), selectedItem.name);
const resp = await this.filesClient.delete(itemPath);
return resp.status === 200 ? "" : selectedItem.name;
});
const failedFiles = await Promise.all(delRequests);
failedFiles.forEach((failedFile) => {
if (failedFile !== "") {
alert(`failed to delete ${failedFile}`);
}
});
return this.setItems(dirParts);
};
setItems = async (dirParts: List<string>): Promise<void> => {
const dirPath = dirParts.join("/");
const listResp = await this.filesClient.list(dirPath);
this.props.panel.browser.dirPath = dirParts;
this.props.panel.browser.items =
listResp.status === 200
? List<MetadataResp>(listResp.data.metadatas)
: this.props.panel.browser.items;
};
setHomeItems = async (): Promise<void> => {
const listResp = await this.filesClient.listHome();
this.props.panel.browser.dirPath = List<string>(
listResp.data.cwd.split("/")
);
this.props.panel.browser.items =
listResp.status === 200
? List<MetadataResp>(listResp.data.metadatas)
: this.props.panel.browser.items;
};
moveHere = async (
srcDir: string,
dstDir: string,
selectedItems: Map<string, boolean>
): Promise<void> => {
const moveRequests = List<string>(selectedItems.keys()).map(
async (itemName: string): Promise<string> => {
const oldPath = getItemPath(srcDir, itemName);
const newPath = getItemPath(dstDir, itemName);
const resp = await this.filesClient.move(oldPath, newPath);
return resp.status === 200 ? "" : itemName;
}
);
const failedFiles = await Promise.all(moveRequests);
failedFiles.forEach((failedItem) => {
if (failedItem !== "") {
alert(`failed to move ${failedItem}`);
}
});
return this.setItems(List<string>(dstDir.split("/")));
};
displayPane = (paneName: string) => {
if (paneName === "") {
// hide all panes
this.props.panel.panes.displaying = "";
} else {
const pane = this.props.panel.panes.paneNames.get(paneName);
if (pane != null) {
this.props.panel.panes.displaying = paneName;
} else {
alert(`dialgos: pane (${paneName}) not found`);
}
}
};
self = async (): Promise<boolean> => {
const resp = await this.usersClient.self();
if (resp.status === 200) {
this.props.panel.panes.userRole = resp.data.role;
return true;
}
return false;
};
addUser = async (user: User): Promise<boolean> => {
const resp = await this.usersClient.addUser(user.name, user.pwd, user.role);
// TODO: should return uid instead
return resp.status === 200;
};
delUser = async (userID: string): Promise<boolean> => {
const resp = await this.usersClient.delUser(userID);
return resp.status === 200;
};
setRole = async (userID: string, role: string): Promise<boolean> => {
const resp = await this.usersClient.delUser(userID);
return resp.status === 200;
};
forceSetPwd = async (userID: string, pwd: string): Promise<boolean> => {
const resp = await this.usersClient.forceSetPwd(userID, pwd);
return resp.status === 200;
};
listUsers = async (): Promise<boolean> => {
const resp = await this.usersClient.listUsers();
if (resp.status !== 200) {
return false;
}
const lsRes = resp.data as ListUsersResp;
let users = Map<User>({});
lsRes.users.forEach((user: User) => {
users = users.set(user.name, user);
});
this.props.panel.panes.admin.users = users;
return true;
};
addRole = async (role: string): Promise<boolean> => {
const resp = await this.usersClient.addRole(role);
// TODO: should return uid instead
return resp.status === 200;
};
delRole = async (role: string): Promise<boolean> => {
const resp = await this.usersClient.delRole(role);
return resp.status === 200;
};
listRoles = async (): Promise<boolean> => {
const resp = await this.usersClient.listRoles();
if (resp.status !== 200) {
return false;
}
const lsRes = resp.data as ListRolesResp;
let roles = Set<string>();
Object.keys(lsRes.roles).forEach((role: string) => {
roles = roles.add(role);
});
this.props.panel.panes.admin.roles = roles;
return true;
};
login = async (
user: string,
pwd: string,
captchaID: string,
captchaInput: string
): Promise<boolean> => {
const resp = await this.usersClient.login(
user,
pwd,
captchaID,
captchaInput
);
updater().setAuthed(resp.status === 200);
return resp.status === 200;
};
logout = async (): Promise<boolean> => {
const resp = await this.usersClient.logout();
updater().setAuthed(false);
return resp.status === 200;
};
isAuthed = async (): Promise<boolean> => {
const resp = await this.usersClient.isAuthed();
return resp.status === 200;
};
initIsAuthed = async (): Promise<void> => {
return updater()
.isAuthed()
.then((isAuthed) => {
updater().setAuthed(isAuthed);
});
};
setAuthed = (isAuthed: boolean) => {
this.props.panel.authPane.authed = isAuthed;
};
getCaptchaID = async (): Promise<boolean> => {
return this.usersClient.getCaptchaID().then((resp) => {
if (resp.status === 200) {
this.props.panel.authPane.captchaID = resp.data.id;
}
return resp.status === 200;
});
};
setPwd = async (oldPwd: string, newPwd: string): Promise<boolean> => {
const resp = await this.usersClient.setPwd(oldPwd, newPwd);
return resp.status === 200;
};
updateBrowser = (prevState: ICoreState): ICoreState => {
return {
...prevState,
panel: {
...prevState.panel,
browser: {
dirPath: this.props.panel.browser.dirPath,
isSharing: this.props.panel.browser.isSharing,
items: this.props.panel.browser.items,
uploadings: this.props.panel.browser.uploadings,
sharings: this.props.panel.browser.sharings,
uploadFiles: this.props.panel.browser.uploadFiles,
uploadValue: this.props.panel.browser.uploadValue,
isVertical: this.props.panel.browser.isVertical,
},
},
};
};
updatePanes = (prevState: ICoreState): ICoreState => {
return {
...prevState,
panel: {
...prevState.panel,
panes: { ...prevState.panel.panes, ...this.props.panel.panes },
},
};
};
updateAuthPane = (preState: ICoreState): ICoreState => {
preState.panel.authPane = {
...preState.panel.authPane,
...this.props.panel.authPane,
};
return preState;
};
}
export let coreUpdater = new Updater();
export const updater = (): Updater => {
return coreUpdater;
};
export const setUpdater = (updater: Updater) => {
coreUpdater = updater;
};

View file

@ -0,0 +1,57 @@
import * as React from "react";
import { ICoreState } from "./core_state";
import { updater } from "./state_updater";
export interface State {}
export interface Props {
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
export class TopBar extends React.Component<Props, State, {}> {
constructor(p: Props) {
super(p);
}
showSettings = () => {
updater().displayPane("settings");
this.props.update(updater().updatePanes);
};
showAdmin = () => {
updater().displayPane("admin");
this.props.update(updater().updatePanes);
};
render() {
return (
<div
id="top-bar"
className="top-bar cyan1-font padding-t-m padding-b-m padding-l-l padding-r-l"
>
<div className="flex-2col-parent">
<a
href="https://github.com/ihexxa/quickshare"
className="flex-13col h5"
>
Quickshare
</a>
<span className="flex-23col text-right">
<button
onClick={this.showSettings}
className="grey1-bg white-font margin-r-m"
>
Settings
</button>
<button
onClick={this.showAdmin}
className="grey1-bg white-font margin-r-m"
>
Admin
</button>
</span>
</div>
</div>
);
}
}

View file

@ -1,5 +1,5 @@
import { Response } from "../client";
import { ICoreState, mockState } from "../components/core_state";
import { ICoreState, initState } from "../components/core_state";
import { List } from "immutable";
export const makePromise = (ret: any): Promise<any> => {
@ -19,7 +19,7 @@ export const makeNumberResponse = (status: number): Promise<Response> => {
export const mockUpdate = (
apply: (prevState: ICoreState) => ICoreState
): void => {
apply(mockState());
apply(initState());
};
export const addMockUpdate = (subState: any) => {