diff --git a/src/client/web/src/components/__test__/browser.test.tsx b/src/client/web/src/components/__test__/browser.test.tsx index 86227a0..1451869 100644 --- a/src/client/web/src/components/__test__/browser.test.tsx +++ b/src/client/web/src/components/__test__/browser.test.tsx @@ -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) => {}; - updater.init(coreState.panel.browser); + // const updater = new Updater(); + updater().setUploadings = (infos: Map) => {}; + 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(tc.dirPath.split("/")), List(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(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(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); diff --git a/src/client/web/src/components/__test__/pane_login.test.tsx b/src/client/web/src/components/__test__/pane_login.test.tsx index 6315565..65f3402 100644 --- a/src/client/web/src/components/__test__/pane_login.test.tsx +++ b/src/client/web/src/components/__test__/pane_login.test.tsx @@ -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); } diff --git a/src/client/web/src/components/__test__/panes.test.tsx b/src/client/web/src/components/__test__/panes.test.tsx index d5726e9..da366d0 100644 --- a/src/client/web/src/components/__test__/panes.test.tsx +++ b/src/client/web/src/components/__test__/panes.test.tsx @@ -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); }); }); }); diff --git a/src/client/web/src/components/__test__/root_frame.test.tsx b/src/client/web/src/components/__test__/root_frame.test.tsx index c81160c..7bd99ab 100644 --- a/src/client/web/src/components/__test__/root_frame.test.tsx +++ b/src/client/web/src/components/__test__/root_frame.test.tsx @@ -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); }); }); }); diff --git a/src/client/web/src/components/browser.tsx b/src/client/web/src/components/browser.tsx index a9ceb78..e1e0cfa 100644 --- a/src/client/web/src/components/browser.tsx +++ b/src/client/web/src/components/browser.tsx @@ -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 { fileList = fileList.push(event.target.files[i]); } updater().addUploads(fileList); - this.update(updater().setBrowser); + this.update(updater().updateBrowser); }; deleteUpload = (filePath: string): Promise => { @@ -101,13 +98,13 @@ export class Browser extends React.Component { 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 { 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 { 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(), @@ -172,7 +169,7 @@ export class Browser extends React.Component { this.state.selectedItems ) .then(() => { - this.update(updater().setBrowser); + this.update(updater().updateBrowser); this.setState({ selectedSrc: "", selectedItems: Map(), @@ -198,7 +195,7 @@ export class Browser extends React.Component { return updater().isSharing(dirPath.join("/")); }) .then(() => { - this.update(updater().setBrowser); + this.update(updater().updateBrowser); }); }; @@ -207,7 +204,7 @@ export class Browser extends React.Component { updater() .setItems(this.props.dirPath) .then(() => { - this.update(updater().setBrowser); + this.update(updater().updateBrowser); }); }; @@ -253,7 +250,7 @@ export class Browser extends React.Component { } }) .then(() => { - this.props.update(updater().setBrowser); + this.props.update(updater().updateBrowser); }); }; @@ -269,7 +266,7 @@ export class Browser extends React.Component { } }) .then(() => { - this.props.update(updater().setBrowser); + this.props.update(updater().updateBrowser); }); }; @@ -278,7 +275,7 @@ export class Browser extends React.Component { .listSharings() .then((ok) => { if (ok) { - this.update(updater().setBrowser); + this.update(updater().updateBrowser); } }); }; diff --git a/src/client/web/src/components/core_state.ts b/src/client/web/src/components/core_state.ts index be99bdf..7cc92da 100644 --- a/src/client/web/src/components/core_state.ts +++ b/src/client/web/src/components/core_state.ts @@ -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(["."]), - items: List([]), - sharings: List([]), - isSharing: false, - uploadings: List([]), - uploadValue: "", - uploadFiles: List([]), - }, - panes: { - userRole: "", - displaying: "", - paneNames: Set(["settings", "login", "admin"]), - login: { - authed: false, - captchaID: "", - }, - admin: { - users: Map(), - roles: Set(), - }, - }, - }, - }; -} + +export function isVertical(): boolean { + return window.innerWidth <= window.innerHeight; +} \ No newline at end of file diff --git a/src/client/web/src/components/pane_admin.tsx b/src/client/web/src/components/pane_admin.tsx index 0587f43..ea41d9e 100644 --- a/src/client/web/src/components/pane_admin.tsx +++ b/src/client/web/src/components/pane_admin.tsx @@ -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; @@ -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 { }; 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 { 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 { 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 { newUserPwd2: "", newUserRole: "", }); - return PanesUpdater.listUsers(); + return updater().listUsers(); }) .then(() => { - this.props.update(PanesUpdater.updateState); + this.props.update(updater().updatePanes); }); }; diff --git a/src/client/web/src/components/pane_login.tsx b/src/client/web/src/components/pane_login.tsx index e3a490d..f66d18e 100644 --- a/src/client/web/src/components/pane_login.tsx +++ b/src/client/web/src/components/pane_login.tsx @@ -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 => { - const resp = await Updater.client.login(user, pwd, captchaID, captchaInput); - Updater.setAuthed(resp.status === 200); - return resp.status === 200; - }; - - static logout = async (): Promise => { - const resp = await Updater.client.logout(); - Updater.setAuthed(false); - return resp.status === 200; - }; - - static isAuthed = async (): Promise => { - const resp = await Updater.client.isAuthed(); - return resp.status === 200; - }; - - static initIsAuthed = async (): Promise => { - return Updater.isAuthed().then((isAuthed) => { - Updater.setAuthed(isAuthed); - }); - }; - - static setAuthed = (isAuthed: boolean) => { - Updater.props.authed = isAuthed; - }; - - static getCaptchaID = async (): Promise => { - 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 { 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 { }; 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."); } diff --git a/src/client/web/src/components/pane_settings.tsx b/src/client/web/src/components/pane_settings.tsx index 2d8c2e1..d816deb 100644 --- a/src/client/web/src/components/pane_settings.tsx +++ b/src/client/web/src/components/pane_settings.tsx @@ -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 => { - 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 { 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 { } 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 { newPwd1: "", newPwd2: "", }); - } - ); + }); } }; diff --git a/src/client/web/src/components/panes.tsx b/src/client/web/src/components/panes.tsx index d9b1c6f..13299a9 100644 --- a/src/client/web/src/components/panes.tsx +++ b/src/client/web/src/components/panes.tsx @@ -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 => { - const resp = await Updater.client.self(); - if (resp.status === 200) { - Updater.props.userRole = resp.data.role; - return true; - } - return false; - }; +// static self = async (): Promise => { +// 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 => { - 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 => { +// 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 => { - const resp = await Updater.client.delUser(userID); - return resp.status === 200; - }; +// static delUser = async (userID: string): Promise => { +// const resp = await Updater.client.delUser(userID); +// return resp.status === 200; +// }; - static setRole = async (userID: string, role: string): Promise => { - const resp = await Updater.client.delUser(userID); - return resp.status === 200; - }; +// static setRole = async (userID: string, role: string): Promise => { +// const resp = await Updater.client.delUser(userID); +// return resp.status === 200; +// }; - static forceSetPwd = async ( - userID: string, - pwd: string - ): Promise => { - const resp = await Updater.client.forceSetPwd(userID, pwd); - return resp.status === 200; - }; +// static forceSetPwd = async ( +// userID: string, +// pwd: string +// ): Promise => { +// const resp = await Updater.client.forceSetPwd(userID, pwd); +// return resp.status === 200; +// }; - static listUsers = async (): Promise => { - const resp = await Updater.client.listUsers(); - if (resp.status !== 200) { - return false; - } +// static listUsers = async (): Promise => { +// const resp = await Updater.client.listUsers(); +// if (resp.status !== 200) { +// return false; +// } - const lsRes = resp.data as ListUsersResp; - let users = Map({}); - 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({}); +// 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 => { - const resp = await Updater.client.addRole(role); - // TODO: should return uid instead - return resp.status === 200; - }; +// static addRole = async (role: string): Promise => { +// const resp = await Updater.client.addRole(role); +// // TODO: should return uid instead +// return resp.status === 200; +// }; - static delRole = async (role: string): Promise => { - const resp = await Updater.client.delRole(role); - return resp.status === 200; - }; +// static delRole = async (role: string): Promise => { +// const resp = await Updater.client.delRole(role); +// return resp.status === 200; +// }; - static listRoles = async (): Promise => { - const resp = await Updater.client.listRoles(); - if (resp.status !== 200) { - return false; - } +// static listRoles = async (): Promise => { +// const resp = await Updater.client.listRoles(); +// if (resp.status !== 200) { +// return false; +// } - const lsRes = resp.data as ListRolesResp; - let roles = Set(); - 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(); +// 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 { 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 { let displaying = this.props.displaying; if (!this.props.login.authed) { // TODO: use constant instead + // TODO: control this with props displaying = "login"; } diff --git a/src/client/web/src/components/root_frame.tsx b/src/client/web/src/components/root_frame.tsx index aa22428..7517ab7 100644 --- a/src/client/web/src/components/root_frame.tsx +++ b/src/client/web/src/components/root_frame.tsx @@ -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 { 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 (
@@ -53,36 +30,10 @@ export class RootFrame extends React.Component { paneNames={this.props.panes.paneNames} login={this.props.authPane} admin={this.props.panes.admin} - update={update} + update={this.props.update} /> -
-
- - Quickshare - - - - - -
-
+
{ 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} diff --git a/src/client/web/src/components/state_mgr.tsx b/src/client/web/src/components/state_mgr.tsx index 6063f8f..a7c4147 100644 --- a/src/client/web/src/components/state_mgr.tsx +++ b/src/client/web/src/components/state_mgr.tsx @@ -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 { 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); }); }; diff --git a/src/client/web/src/components/state_updater.ts b/src/client/web/src/components/state_updater.ts new file mode 100644 index 0000000..cea377a --- /dev/null +++ b/src/client/web/src/components/state_updater.ts @@ -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) => { + 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 => { + Up().delete(filePath); + const resp = await this.filesClient.deleteUploading(filePath); + return resp.status === 200; + }; + + setUploadings = (infos: Map) => { + this.props.panel.browser.uploadings = List( + infos.valueSeq().map((v: UploadEntry): UploadInfo => { + return { + realFilePath: v.filePath, + size: v.size, + uploaded: v.uploaded, + }; + }) + ); + }; + + addSharing = async (): Promise => { + const dirPath = this.props.panel.browser.dirPath.join("/"); + const resp = await this.filesClient.addSharing(dirPath); + return resp.status === 200; + }; + + deleteSharing = async (dirPath: string): Promise => { + const resp = await this.filesClient.deleteSharing(dirPath); + return resp.status === 200; + }; + + isSharing = async (dirPath: string): Promise => { + 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 => { + const resp = await this.filesClient.listSharings(); + this.props.panel.browser.sharings = + resp.status === 200 + ? List(resp.data.sharingDirs) + : this.props.panel.browser.sharings; + return resp.status === 200; + }; + + refreshUploadings = async (): Promise => { + const luResp = await this.filesClient.listUploadings(); + + this.props.panel.browser.uploadings = + luResp.status === 200 + ? List(luResp.data.uploadInfos) + : this.props.panel.browser.uploadings; + return luResp.status === 200; + }; + + stopUploading = (filePath: string) => { + Up().stop(filePath); + }; + + mkDir = async (dirPath: string): Promise => { + const resp = await this.filesClient.mkdir(dirPath); + if (resp.status !== 200) { + alert(`failed to make dir ${dirPath}`); + } + }; + + delete = async ( + dirParts: List, + items: List, + selectedItems: Map + ): Promise => { + const delRequests = items + .filter((item) => { + return selectedItems.has(item.name); + }) + .map(async (selectedItem: MetadataResp): Promise => { + 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): Promise => { + 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(listResp.data.metadatas) + : this.props.panel.browser.items; + }; + + setHomeItems = async (): Promise => { + const listResp = await this.filesClient.listHome(); + + this.props.panel.browser.dirPath = List( + listResp.data.cwd.split("/") + ); + this.props.panel.browser.items = + listResp.status === 200 + ? List(listResp.data.metadatas) + : this.props.panel.browser.items; + }; + + moveHere = async ( + srcDir: string, + dstDir: string, + selectedItems: Map + ): Promise => { + const moveRequests = List(selectedItems.keys()).map( + async (itemName: string): Promise => { + 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(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 => { + 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 => { + 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 => { + const resp = await this.usersClient.delUser(userID); + return resp.status === 200; + }; + + setRole = async (userID: string, role: string): Promise => { + const resp = await this.usersClient.delUser(userID); + return resp.status === 200; + }; + + forceSetPwd = async (userID: string, pwd: string): Promise => { + const resp = await this.usersClient.forceSetPwd(userID, pwd); + return resp.status === 200; + }; + + listUsers = async (): Promise => { + const resp = await this.usersClient.listUsers(); + if (resp.status !== 200) { + return false; + } + + const lsRes = resp.data as ListUsersResp; + let users = Map({}); + 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 => { + const resp = await this.usersClient.addRole(role); + // TODO: should return uid instead + return resp.status === 200; + }; + + delRole = async (role: string): Promise => { + const resp = await this.usersClient.delRole(role); + return resp.status === 200; + }; + + listRoles = async (): Promise => { + const resp = await this.usersClient.listRoles(); + if (resp.status !== 200) { + return false; + } + + const lsRes = resp.data as ListRolesResp; + let roles = Set(); + 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 => { + const resp = await this.usersClient.login( + user, + pwd, + captchaID, + captchaInput + ); + updater().setAuthed(resp.status === 200); + return resp.status === 200; + }; + + logout = async (): Promise => { + const resp = await this.usersClient.logout(); + updater().setAuthed(false); + return resp.status === 200; + }; + + isAuthed = async (): Promise => { + const resp = await this.usersClient.isAuthed(); + return resp.status === 200; + }; + + initIsAuthed = async (): Promise => { + return updater() + .isAuthed() + .then((isAuthed) => { + updater().setAuthed(isAuthed); + }); + }; + + setAuthed = (isAuthed: boolean) => { + this.props.panel.authPane.authed = isAuthed; + }; + + getCaptchaID = async (): Promise => { + 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 => { + 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; +}; diff --git a/src/client/web/src/components/topbar.tsx b/src/client/web/src/components/topbar.tsx new file mode 100644 index 0000000..6b92482 --- /dev/null +++ b/src/client/web/src/components/topbar.tsx @@ -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 { + 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 ( +
+
+ + Quickshare + + + + + +
+
+ ); + } +} diff --git a/src/client/web/src/test/helpers.ts b/src/client/web/src/test/helpers.ts index 069a50b..15735f1 100644 --- a/src/client/web/src/test/helpers.ts +++ b/src/client/web/src/test/helpers.ts @@ -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 => { @@ -19,7 +19,7 @@ export const makeNumberResponse = (status: number): Promise => { export const mockUpdate = ( apply: (prevState: ICoreState) => ICoreState ): void => { - apply(mockState()); + apply(initState()); }; export const addMockUpdate = (subState: any) => {