fix(ui): refactor core state into a flat structure

This commit is contained in:
hexxa 2021-08-23 14:52:37 +08:00 committed by Hexxa
parent 8013c401f3
commit 9b67a85d97
11 changed files with 333 additions and 597 deletions

View file

@ -22,262 +22,262 @@ import { MetadataResp, UploadInfo } from "../../client";
import { MockWorker, UploadEntry } from "../../worker/interface"; import { MockWorker, UploadEntry } from "../../worker/interface";
import { UploadMgr, setUploadMgr } from "../../worker/upload_mgr"; import { UploadMgr, setUploadMgr } from "../../worker/upload_mgr";
describe("Browser", () => { // describe("Browser", () => {
const mockWorkerClass = mock(MockWorker); // const mockWorkerClass = mock(MockWorker);
const mockWorker = instance(mockWorkerClass); // const mockWorker = instance(mockWorkerClass);
test("Updater: addUploads: add each files to UploadMgr", async () => { // test("Updater: addUploads: add each files to UploadMgr", async () => {
let coreState = initState(); // let coreState = initState();
const UploadMgrClass = mock(UploadMgr); // const UploadMgrClass = mock(UploadMgr);
const uploadMgr = instance(UploadMgrClass); // const uploadMgr = instance(UploadMgrClass);
setUploadMgr(uploadMgr); // setUploadMgr(uploadMgr);
const filePaths = ["./file1", "./file2"]; // const filePaths = ["./file1", "./file2"];
const fileList = mockFileList(filePaths); // const fileList = mockFileList(filePaths);
// const updater = new Updater(); // // const updater = new Updater();
updater().setUploadings = (infos: Map<string, UploadEntry>) => {}; // updater().setUploadings = (infos: Map<string, UploadEntry>) => {};
updater().init(coreState); // 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 // // 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); // verify(UploadMgrClass.add(anything(), anything())).times(filePaths.length);
// filePaths.forEach((filePath, i) => { // // filePaths.forEach((filePath, i) => {
// verify(UploadMgrClass.add(anything(), filePath)).once(); // // verify(UploadMgrClass.add(anything(), filePath)).once();
// }); // // });
}); // });
test("Updater: deleteUploads: call UploadMgr and api to delete", async () => { // test("Updater: deleteUploads: call UploadMgr and api to delete", async () => {
let coreState = initState(); // let coreState = initState();
const UploadMgrClass = mock(UploadMgr); // const UploadMgrClass = mock(UploadMgr);
const uploadMgr = instance(UploadMgrClass); // const uploadMgr = instance(UploadMgrClass);
setUploadMgr(uploadMgr); // setUploadMgr(uploadMgr);
// const updater = new Updater(); // // const updater = new Updater();
const filesClientClass = mock(FilesClient); // const filesClientClass = mock(FilesClient);
when(filesClientClass.deleteUploading(anyString())).thenResolve({ // when(filesClientClass.deleteUploading(anyString())).thenResolve({
status: 200, // status: 200,
statusText: "", // statusText: "",
data: "", // data: "",
}); // });
const filesClient = instance(filesClientClass); // const filesClient = instance(filesClientClass);
const usersClientClass = mock(UsersClient); // const usersClientClass = mock(UsersClient);
const usersClient = instance(usersClientClass); // const usersClient = instance(usersClientClass);
updater().init(coreState); // updater().init(coreState);
updater().setClients(usersClient, filesClient); // updater().setClients(usersClient, filesClient);
const filePath = "./path/file"; // const filePath = "./path/file";
updater().deleteUpload(filePath); // updater().deleteUpload(filePath);
verify(filesClientClass.deleteUploading(filePath)).once(); // verify(filesClientClass.deleteUploading(filePath)).once();
verify(UploadMgrClass.delete(filePath)).once(); // verify(UploadMgrClass.delete(filePath)).once();
}); // });
test("Updater: delete", async () => { // test("Updater: delete", async () => {
const tests = [ // const tests = [
{ // {
dirPath: "path/path2", // dirPath: "path/path2",
items: [ // items: [
{ // {
name: "file", // name: "file",
size: 1, // size: 1,
modTime: "1-1", // modTime: "1-1",
isDir: false, // isDir: false,
}, // },
{ // {
name: "folder", // name: "folder",
size: 0, // size: 0,
modTime: "1-1", // modTime: "1-1",
isDir: true, // isDir: true,
}, // },
], // ],
selected: { // selected: {
file: true, // file: true,
}, // },
listResp: { // listResp: {
status: 200, // status: 200,
statusText: "", // statusText: "",
data: { // data: {
metadatas: [ // metadatas: [
{ // {
name: "folder", // name: "folder",
size: 0, // size: 0,
modTime: "1-1", // modTime: "1-1",
isDir: true, // isDir: true,
}, // },
], // ],
}, // },
}, // },
filePath: "path/file", // filePath: "path/file",
}, // },
]; // ];
const usersClient = new MockUsersClient(""); // const usersClient = new MockUsersClient("");
const filesClient = new MockFilesClient(""); // const filesClient = new MockFilesClient("");
for (let i = 0; i < tests.length; i++) { // for (let i = 0; i < tests.length; i++) {
const tc = tests[i]; // const tc = tests[i];
// const updater = new Updater(); // // const updater = new Updater();
updater().setClients(usersClient, filesClient); // updater().setClients(usersClient, filesClient);
filesClient.listMock(makePromise(tc.listResp)); // filesClient.listMock(makePromise(tc.listResp));
filesClient.deleteMock(makeNumberResponse(200)); // filesClient.deleteMock(makeNumberResponse(200));
const coreState = newWithWorker(mockWorker); // const coreState = newWithWorker(mockWorker);
updater().init(coreState); // updater().init(coreState);
await updater().delete( // await updater().delete(
List<string>(tc.dirPath.split("/")), // List<string>(tc.dirPath.split("/")),
List<MetadataResp>(tc.items), // List<MetadataResp>(tc.items),
Map(tc.selected) // Map(tc.selected)
); // );
const newState = updater().updateBrowser(coreState); // const newState = updater().updateBrowser(coreState);
// TODO: check inputs of delete // // TODO: check inputs of delete
newState.panel.browser.items.forEach((item, i) => { // newState.panel.browser.items.forEach((item, i) => {
expect(item.name).toEqual(tc.listResp.data.metadatas[i].name); // expect(item.name).toEqual(tc.listResp.data.metadatas[i].name);
expect(item.size).toEqual(tc.listResp.data.metadatas[i].size); // expect(item.size).toEqual(tc.listResp.data.metadatas[i].size);
expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime); // expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime);
expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir); // expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir);
}); // });
} // }
}); // });
test("Updater: setItems", async () => { // test("Updater: setItems", async () => {
const tests = [ // const tests = [
{ // {
listResp: { // listResp: {
status: 200, // status: 200,
statusText: "", // statusText: "",
data: { // data: {
metadatas: [ // metadatas: [
{ // {
name: "file", // name: "file",
size: 1, // size: 1,
modTime: "1-1", // modTime: "1-1",
isDir: false, // isDir: false,
}, // },
{ // {
name: "folder", // name: "folder",
size: 0, // size: 0,
modTime: "1-1", // modTime: "1-1",
isDir: true, // isDir: true,
}, // },
], // ],
}, // },
}, // },
filePath: "path/file", // filePath: "path/file",
}, // },
]; // ];
const usersClient = new MockUsersClient(""); // const usersClient = new MockUsersClient("");
const filesClient = new MockFilesClient(""); // const filesClient = new MockFilesClient("");
for (let i = 0; i < tests.length; i++) { // for (let i = 0; i < tests.length; i++) {
const tc = tests[i]; // const tc = tests[i];
filesClient.listMock(makePromise(tc.listResp)); // filesClient.listMock(makePromise(tc.listResp));
updater().setClients(usersClient, filesClient); // updater().setClients(usersClient, filesClient);
const coreState = newWithWorker(mockWorker); // const coreState = newWithWorker(mockWorker);
updater().init(coreState); // updater().init(coreState);
await updater().setItems(List<string>(tc.filePath.split("/"))); // await updater().setItems(List<string>(tc.filePath.split("/")));
const newState = updater().updateBrowser(coreState); // const newState = updater().updateBrowser(coreState);
newState.panel.browser.items.forEach((item, i) => { // newState.panel.browser.items.forEach((item, i) => {
expect(item.name).toEqual(tc.listResp.data.metadatas[i].name); // expect(item.name).toEqual(tc.listResp.data.metadatas[i].name);
expect(item.size).toEqual(tc.listResp.data.metadatas[i].size); // expect(item.size).toEqual(tc.listResp.data.metadatas[i].size);
expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime); // expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime);
expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir); // expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir);
}); // });
} // }
}); // });
test("Updater: moveHere", async () => { // test("Updater: moveHere", async () => {
const tests = [ // const tests = [
{ // {
dirPath1: "path/path1", // dirPath1: "path/path1",
dirPath2: "path/path2", // dirPath2: "path/path2",
selected: { // selected: {
file1: true, // file1: true,
file2: true, // file2: true,
}, // },
listResp: { // listResp: {
status: 200, // status: 200,
statusText: "", // statusText: "",
data: { // data: {
metadatas: [ // metadatas: [
{ // {
name: "file1", // name: "file1",
size: 1, // size: 1,
modTime: "1-1", // modTime: "1-1",
isDir: false, // isDir: false,
}, // },
{ // {
name: "file2", // name: "file2",
size: 2, // size: 2,
modTime: "1-1", // modTime: "1-1",
isDir: false, // isDir: false,
}, // },
], // ],
}, // },
}, // },
}, // },
]; // ];
const usersClient = new MockUsersClient(""); // const usersClient = new MockUsersClient("");
const filesClient = new MockFilesClient(""); // const filesClient = new MockFilesClient("");
for (let i = 0; i < tests.length; i++) { // for (let i = 0; i < tests.length; i++) {
const tc = tests[i]; // const tc = tests[i];
filesClient.listMock(makePromise(tc.listResp)); // filesClient.listMock(makePromise(tc.listResp));
filesClient.moveMock(makeNumberResponse(200)); // filesClient.moveMock(makeNumberResponse(200));
updater().setClients(usersClient, filesClient); // updater().setClients(usersClient, filesClient);
const coreState = newWithWorker(mockWorker); // const coreState = newWithWorker(mockWorker);
updater().init(coreState); // updater().init(coreState);
await updater().moveHere(tc.dirPath1, tc.dirPath2, Map(tc.selected)); // await updater().moveHere(tc.dirPath1, tc.dirPath2, Map(tc.selected));
const newState = updater().updateBrowser(coreState); // const newState = updater().updateBrowser(coreState);
// TODO: check inputs of move // // TODO: check inputs of move
newState.panel.browser.items.forEach((item, i) => { // newState.panel.browser.items.forEach((item, i) => {
expect(item.name).toEqual(tc.listResp.data.metadatas[i].name); // expect(item.name).toEqual(tc.listResp.data.metadatas[i].name);
expect(item.size).toEqual(tc.listResp.data.metadatas[i].size); // expect(item.size).toEqual(tc.listResp.data.metadatas[i].size);
expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime); // expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime);
expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir); // expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir);
}); // });
} // }
}); // });
test("Browser: deleteUpload: tell uploader to deleteUpload and refreshUploadings", async () => { // test("Browser: deleteUpload: tell uploader to deleteUpload and refreshUploadings", async () => {
let coreState = initState(); // let coreState = initState();
addMockUpdate(coreState.panel.browser); // addMockUpdate(coreState.panel.browser);
const component = new Browser(coreState.panel.browser); // const component = new Browser(coreState.panel.browser);
const UpdaterClass = mock(Updater); // const UpdaterClass = mock(Updater);
const mockUpdater = instance(UpdaterClass); // const mockUpdater = instance(UpdaterClass);
setUpdater(mockUpdater); // setUpdater(mockUpdater);
when(UpdaterClass.setItems(anything())).thenResolve(); // when(UpdaterClass.setItems(anything())).thenResolve();
when(UpdaterClass.deleteUpload(anyString())).thenResolve(true); // when(UpdaterClass.deleteUpload(anyString())).thenResolve(true);
when(UpdaterClass.refreshUploadings()).thenResolve(true); // when(UpdaterClass.refreshUploadings()).thenResolve(true);
const filePath = "filePath"; // const filePath = "filePath";
await component.deleteUpload(filePath); // await component.deleteUpload(filePath);
verify(UpdaterClass.deleteUpload(filePath)).once(); // verify(UpdaterClass.deleteUpload(filePath)).once();
verify(UpdaterClass.refreshUploadings()).once(); // verify(UpdaterClass.refreshUploadings()).once();
}); // });
test("Browser: stopUploading: tell updater to stopUploading", async () => { // test("Browser: stopUploading: tell updater to stopUploading", async () => {
let coreState = initState(); // let coreState = initState();
addMockUpdate(coreState.panel.browser); // addMockUpdate(coreState.panel.browser);
const component = new Browser(coreState.panel.browser); // const component = new Browser(coreState.panel.browser);
const UpdaterClass = mock(Updater); // const UpdaterClass = mock(Updater);
const mockUpdater = instance(UpdaterClass); // const mockUpdater = instance(UpdaterClass);
setUpdater(mockUpdater); // setUpdater(mockUpdater);
when(UpdaterClass.stopUploading(anyString())).thenReturn(); // when(UpdaterClass.stopUploading(anyString())).thenReturn();
const filePath = "filePath"; // const filePath = "filePath";
component.stopUploading(filePath); // component.stopUploading(filePath);
verify(UpdaterClass.stopUploading(filePath)).once(); // verify(UpdaterClass.stopUploading(filePath)).once();
}); // });
}); // });

View file

@ -56,9 +56,9 @@ describe("AuthPane", () => {
updater().setClients(usersClient, filesClient); updater().setClients(usersClient, filesClient);
updater().init(coreState); updater().init(coreState);
await updater().initIsAuthed(); await updater().initIsAuthed();
const newState = updater().updateAuthPane(coreState); const newState = updater().updateLogin(coreState);
expect(newState.panel.authPane.authed).toEqual(tc.isAuthed); expect(newState.login.authed).toEqual(tc.isAuthed);
} }
}); });
}); });

View file

@ -32,7 +32,7 @@ describe("Panes", () => {
]; ];
const setState = (patch: any, state: ICoreState): ICoreState => { const setState = (patch: any, state: ICoreState): ICoreState => {
state.panel.panes = patch.panes; state.panes = patch.panes;
return state; return state;
}; };
@ -40,7 +40,12 @@ describe("Panes", () => {
const preState = setState(tc.preState, initState()); const preState = setState(tc.preState, initState());
const postState = setState(tc.postState, initState()); const postState = setState(tc.postState, initState());
const component = new Panes(preState.panel.panes); const component = new Panes({
panes: preState.panes,
admin: preState.admin,
login: preState.login,
update: mockUpdate,
});
updater().init(preState); updater().init(preState);
component.closePane(); component.closePane();

View file

@ -1,52 +0,0 @@
import { Set } from "immutable";
import { ICoreState, initState } from "../core_state";
import { RootFrame } from "../root_frame";
// import { Updater } from "../panes";
import { updater } from "../state_updater";
xdescribe("RootFrame", () => {
test("component: showSettings", async () => {
interface TestCase {
preState: ICoreState;
postState: ICoreState;
}
const mockUpdate = (apply: (prevState: ICoreState) => ICoreState): void => {};
const tcs: any = [
{
preState: {
displaying: "",
panes: {
displaying: "",
paneNames: Set<string>(["settings", "login"]),
},
update: mockUpdate,
},
postState: {
displaying: "settings",
panes: {
displaying: "settings",
paneNames: Set<string>(["settings", "login"]),
},
update: mockUpdate,
},
},
];
const setState = (patch: any, state: ICoreState): ICoreState => {
return { ...state, panel: { ...state.panel, ...patch } };
};
tcs.forEach((tc: TestCase) => {
const preState = setState(tc.preState, initState());
const postState = setState(tc.postState, initState());
const component = new RootFrame(preState.panel);
updater().init(preState);
// component.showSettings();
expect(updater().props).toEqual(postState);
});
});
});

View file

@ -1,207 +0,0 @@
import { List, Map } from "immutable";
import { ICoreState } from "./core_state";
import { Props, getItemPath } from "./browser";
import {
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: Props;
private usersClient: IUsersClient = new UsersClient("");
private filesClient: IFilesClient = new FilesClient("");
init = (props: Props) => (this.props = { ...props });
setClients(usersClient: IUsersClient, filesClient: IFilesClient) {
this.usersClient = usersClient;
this.filesClient = filesClient;
}
initUploads = () => {
this.props.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.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.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.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.isSharing = resp.status === 200;
return resp.status === 200; // TODO: differentiate 404 and error
};
setSharing = (shared: boolean) => {
this.props.isSharing = shared;
};
listSharings = async (): Promise<boolean> => {
const resp = await this.filesClient.listSharings();
this.props.sharings =
resp.status === 200
? List<string>(resp.data.sharingDirs)
: this.props.sharings;
return resp.status === 200;
};
refreshUploadings = async (): Promise<boolean> => {
const luResp = await this.filesClient.listUploadings();
this.props.uploadings =
luResp.status === 200
? List<UploadInfo>(luResp.data.uploadInfos)
: this.props.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.dirPath = dirParts;
this.props.items =
listResp.status === 200
? List<MetadataResp>(listResp.data.metadatas)
: this.props.items;
};
setHomeItems = async (): Promise<void> => {
const listResp = await this.filesClient.listHome();
this.props.dirPath = List<string>(listResp.data.cwd.split("/"));
this.props.items =
listResp.status === 200
? List<MetadataResp>(listResp.data.metadatas)
: this.props.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("/")));
};
setBrowser = (prevState: ICoreState): ICoreState => {
return {
...prevState,
panel: {
...prevState.panel,
browser: {
dirPath: this.props.dirPath,
isSharing: this.props.isSharing,
items: this.props.items,
uploadings: this.props.uploadings,
sharings: this.props.sharings,
uploadFiles: this.props.uploadFiles,
uploadValue: this.props.uploadValue,
isVertical: this.props.isVertical,
},
},
};
};
}
export let browserUpdater = new Updater();
export const updater = (): Updater => {
return browserUpdater;
};
export const setUpdater = (updater: Updater) => {
browserUpdater = updater;
};

View file

@ -3,15 +3,25 @@ import { List, Set, Map } from "immutable";
import BgWorker from "../worker/upload.bg.worker"; import BgWorker from "../worker/upload.bg.worker";
import { FgWorker } from "../worker/upload.fg.worker"; import { FgWorker } from "../worker/upload.fg.worker";
import { Props as PanelProps } from "./root_frame"; // import { Props as PanelProps } from "./root_frame";
import { Props as BrowserProps } from "./browser";
import { PanesProps as PanesProps } from "./panes";
import { Props as LoginProps } from "./pane_login";
import { Props as AdminProps } from "./pane_admin";
import { Props as SettingsProps } from "./pane_settings";
import { Item } from "./browser"; import { Item } from "./browser";
import { UploadInfo, User } from "../client"; import { UploadInfo, User } from "../client";
import { initUploadMgr, IWorker } from "../worker/upload_mgr"; import { initUploadMgr, IWorker } from "../worker/upload_mgr";
export interface ICoreState { export interface ICoreState {
panel: PanelProps; // panel: PanelProps;
isVertical: boolean; isVertical: boolean;
browser: BrowserProps;
panes: PanesProps;
login: LoginProps;
admin: AdminProps;
// settings: SettingsProps;
} }
export function newWithWorker(worker: IWorker): ICoreState { export function newWithWorker(worker: IWorker): ICoreState {
@ -29,40 +39,32 @@ export function newState(): ICoreState {
export function initState(): ICoreState { export function initState(): ICoreState {
return { return {
isVertical: isVertical(), isVertical: isVertical(),
panel: { browser: {
isVertical: isVertical(),
dirPath: List<string>(["."]),
items: List<Item>([]),
sharings: List<string>([]),
isSharing: false,
uploadings: List<UploadInfo>([]),
uploadValue: "",
uploadFiles: List<File>([]),
},
panes: {
userRole: "",
displaying: "browser", displaying: "browser",
authPane: { paneNames: Set<string>(["settings", "login", "admin"]),
authed: false, },
captchaID: "", login: {
}, authed: false,
browser: { captchaID: "",
isVertical: isVertical(), },
dirPath: List<string>(["."]), admin: {
items: List<Item>([]), users: Map<string, User>(),
sharings: List<string>([]), roles: Set<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 { export function isVertical(): boolean {
return window.innerWidth <= window.innerHeight; return window.innerWidth <= window.innerHeight;
} }

View file

@ -46,7 +46,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
updater() updater()
.initIsAuthed() .initIsAuthed()
.then(() => { .then(() => {
this.update(updater().updateAuthPane); this.update(updater().updateLogin);
}); });
}; };
@ -60,7 +60,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
) )
.then((ok: boolean) => { .then((ok: boolean) => {
if (ok) { if (ok) {
this.update(updater().updateAuthPane); this.update(updater().updateLogin);
this.setState({ user: "", pwd: "" }); this.setState({ user: "", pwd: "" });
// close all the panes // close all the panes
updater().displayPane(""); updater().displayPane("");
@ -78,7 +78,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
}) })
.then(() => { .then(() => {
return updater().isSharing( return updater().isSharing(
updater().props.panel.browser.dirPath.join("/") updater().props.browser.dirPath.join("/")
); );
}) })
.then(() => { .then(() => {
@ -92,7 +92,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
logout = () => { logout = () => {
updater().logout().then((ok: boolean) => { updater().logout().then((ok: boolean) => {
if (ok) { if (ok) {
this.update(updater().updateAuthPane); this.update(updater().updateLogin);
} else { } else {
alert("Failed to logout."); alert("Failed to logout.");
} }

View file

@ -7,10 +7,13 @@ import { PaneSettings } from "./pane_settings";
import { AdminPane, Props as AdminPaneProps } from "./pane_admin"; import { AdminPane, Props as AdminPaneProps } from "./pane_admin";
import { AuthPane, Props as AuthPaneProps } from "./pane_login"; import { AuthPane, Props as AuthPaneProps } from "./pane_login";
export interface Props { export interface PanesProps {
userRole: string;
displaying: string; displaying: string;
userRole: string;
paneNames: Set<string>; paneNames: Set<string>;
}
export interface Props {
panes: PanesProps;
login: AuthPaneProps; login: AuthPaneProps;
admin: AdminPaneProps; admin: AdminPaneProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void; update?: (updater: (prevState: ICoreState) => ICoreState) => void;
@ -23,14 +26,14 @@ export class Panes extends React.Component<Props, State, {}> {
} }
closePane = () => { closePane = () => {
if (this.props.displaying !== "login") { if (this.props.panes.displaying !== "login") {
updater().displayPane(""); updater().displayPane("");
this.props.update(updater().updatePanes); this.props.update(updater().updatePanes);
} }
}; };
render() { render() {
let displaying = this.props.displaying; let displaying = this.props.panes.displaying;
if (!this.props.login.authed) { if (!this.props.login.authed) {
// TODO: use constant instead // TODO: use constant instead
// TODO: control this with props // TODO: control this with props
@ -50,7 +53,7 @@ export class Panes extends React.Component<Props, State, {}> {
), ),
}); });
if (this.props.userRole === "admin") { if (this.props.panes.userRole === "admin") {
panesMap = panesMap.set( panesMap = panesMap.set(
"admin", "admin",
<AdminPane <AdminPane

View file

@ -7,9 +7,7 @@ import { Panes, Props as PanesProps } from "./panes";
import { TopBar } from "./topbar"; import { TopBar } from "./topbar";
export interface Props { export interface Props {
displaying: string;
browser: BrowserProps; browser: BrowserProps;
authPane: PaneLoginProps;
panes: PanesProps; panes: PanesProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void; update?: (updater: (prevState: ICoreState) => ICoreState) => void;
} }
@ -25,10 +23,8 @@ export class RootFrame extends React.Component<Props, State, {}> {
<div className="theme-white desktop"> <div className="theme-white desktop">
<div id="bg" className="bg bg-img font-m"> <div id="bg" className="bg bg-img font-m">
<Panes <Panes
userRole={this.props.panes.userRole} panes={this.props.panes.panes}
displaying={this.props.panes.displaying} login={this.props.panes.login}
paneNames={this.props.panes.paneNames}
login={this.props.authPane}
admin={this.props.panes.admin} admin={this.props.panes.admin}
update={this.props.update} update={this.props.update}
/> />

View file

@ -29,7 +29,7 @@ export class StateMgr extends React.Component<Props, State, {}> {
if (!ok) { if (!ok) {
alert("failed to get captcha id"); alert("failed to get captcha id");
} else { } else {
this.update(updater().updateAuthPane); this.update(updater().updateLogin);
} }
}); });
@ -48,9 +48,7 @@ export class StateMgr extends React.Component<Props, State, {}> {
return updater().initUploads(); return updater().initUploads();
}) })
.then(() => { .then(() => {
return updater().isSharing( return updater().isSharing(updater().props.browser.dirPath.join("/"));
updater().props.panel.browser.dirPath.join("/")
);
}) })
.then(() => { .then(() => {
return updater().listSharings(); return updater().listSharings();
@ -62,13 +60,13 @@ export class StateMgr extends React.Component<Props, State, {}> {
return updater().self(); return updater().self();
}) })
.then(() => { .then(() => {
if (updater().props.panel.panes.userRole === "admin") { if (updater().props.panes.userRole === "admin") {
// TODO: remove hardcode // TODO: remove hardcode
return updater().listRoles(); return updater().listRoles();
} }
}) })
.then(() => { .then(() => {
if (updater().props.panel.panes.userRole === "admin") { if (updater().props.panes.userRole === "admin") {
// TODO: remove hardcode // TODO: remove hardcode
return updater().listUsers(); return updater().listUsers();
} }
@ -85,11 +83,13 @@ export class StateMgr extends React.Component<Props, State, {}> {
render() { render() {
return ( return (
<RootFrame <RootFrame
authPane={this.state.panel.authPane} browser={this.state.browser}
displaying={this.state.panel.displaying} panes={{
panes: this.state.panes,
login: this.state.login,
admin: this.state.admin,
}}
update={this.update} update={this.update}
browser={this.state.panel.browser}
panes={this.state.panel.panes}
/> />
); );
} }

View file

@ -28,7 +28,7 @@ export class Updater {
} }
initUploads = () => { initUploads = () => {
this.props.panel.browser.uploadings.forEach((entry) => { this.props.browser.uploadings.forEach((entry) => {
Up().addStopped(entry.realFilePath, entry.uploaded, entry.size); Up().addStopped(entry.realFilePath, entry.uploaded, entry.size);
}); });
// this.setUploadings(Up().list()); // this.setUploadings(Up().list());
@ -37,7 +37,7 @@ export class Updater {
addUploads = (fileList: List<File>) => { addUploads = (fileList: List<File>) => {
fileList.forEach((file) => { fileList.forEach((file) => {
const filePath = getItemPath( const filePath = getItemPath(
this.props.panel.browser.dirPath.join("/"), this.props.browser.dirPath.join("/"),
file.name file.name
); );
// do not wait for the promise // do not wait for the promise
@ -53,7 +53,7 @@ export class Updater {
}; };
setUploadings = (infos: Map<string, UploadEntry>) => { setUploadings = (infos: Map<string, UploadEntry>) => {
this.props.panel.browser.uploadings = List<UploadInfo>( this.props.browser.uploadings = List<UploadInfo>(
infos.valueSeq().map((v: UploadEntry): UploadInfo => { infos.valueSeq().map((v: UploadEntry): UploadInfo => {
return { return {
realFilePath: v.filePath, realFilePath: v.filePath,
@ -65,7 +65,7 @@ export class Updater {
}; };
addSharing = async (): Promise<boolean> => { addSharing = async (): Promise<boolean> => {
const dirPath = this.props.panel.browser.dirPath.join("/"); const dirPath = this.props.browser.dirPath.join("/");
const resp = await this.filesClient.addSharing(dirPath); const resp = await this.filesClient.addSharing(dirPath);
return resp.status === 200; return resp.status === 200;
}; };
@ -77,30 +77,30 @@ export class Updater {
isSharing = async (dirPath: string): Promise<boolean> => { isSharing = async (dirPath: string): Promise<boolean> => {
const resp = await this.filesClient.isSharing(dirPath); const resp = await this.filesClient.isSharing(dirPath);
this.props.panel.browser.isSharing = resp.status === 200; this.props.browser.isSharing = resp.status === 200;
return resp.status === 200; // TODO: differentiate 404 and error return resp.status === 200; // TODO: differentiate 404 and error
}; };
setSharing = (shared: boolean) => { setSharing = (shared: boolean) => {
this.props.panel.browser.isSharing = shared; this.props.browser.isSharing = shared;
}; };
listSharings = async (): Promise<boolean> => { listSharings = async (): Promise<boolean> => {
const resp = await this.filesClient.listSharings(); const resp = await this.filesClient.listSharings();
this.props.panel.browser.sharings = this.props.browser.sharings =
resp.status === 200 resp.status === 200
? List<string>(resp.data.sharingDirs) ? List<string>(resp.data.sharingDirs)
: this.props.panel.browser.sharings; : this.props.browser.sharings;
return resp.status === 200; return resp.status === 200;
}; };
refreshUploadings = async (): Promise<boolean> => { refreshUploadings = async (): Promise<boolean> => {
const luResp = await this.filesClient.listUploadings(); const luResp = await this.filesClient.listUploadings();
this.props.panel.browser.uploadings = this.props.browser.uploadings =
luResp.status === 200 luResp.status === 200
? List<UploadInfo>(luResp.data.uploadInfos) ? List<UploadInfo>(luResp.data.uploadInfos)
: this.props.panel.browser.uploadings; : this.props.browser.uploadings;
return luResp.status === 200; return luResp.status === 200;
}; };
@ -143,23 +143,21 @@ export class Updater {
const dirPath = dirParts.join("/"); const dirPath = dirParts.join("/");
const listResp = await this.filesClient.list(dirPath); const listResp = await this.filesClient.list(dirPath);
this.props.panel.browser.dirPath = dirParts; this.props.browser.dirPath = dirParts;
this.props.panel.browser.items = this.props.browser.items =
listResp.status === 200 listResp.status === 200
? List<MetadataResp>(listResp.data.metadatas) ? List<MetadataResp>(listResp.data.metadatas)
: this.props.panel.browser.items; : this.props.browser.items;
}; };
setHomeItems = async (): Promise<void> => { setHomeItems = async (): Promise<void> => {
const listResp = await this.filesClient.listHome(); const listResp = await this.filesClient.listHome();
this.props.panel.browser.dirPath = List<string>( this.props.browser.dirPath = List<string>(listResp.data.cwd.split("/"));
listResp.data.cwd.split("/") this.props.browser.items =
);
this.props.panel.browser.items =
listResp.status === 200 listResp.status === 200
? List<MetadataResp>(listResp.data.metadatas) ? List<MetadataResp>(listResp.data.metadatas)
: this.props.panel.browser.items; : this.props.browser.items;
}; };
moveHere = async ( moveHere = async (
@ -189,11 +187,11 @@ export class Updater {
displayPane = (paneName: string) => { displayPane = (paneName: string) => {
if (paneName === "") { if (paneName === "") {
// hide all panes // hide all panes
this.props.panel.panes.displaying = ""; this.props.panes.displaying = "";
} else { } else {
const pane = this.props.panel.panes.paneNames.get(paneName); const pane = this.props.panes.paneNames.get(paneName);
if (pane != null) { if (pane != null) {
this.props.panel.panes.displaying = paneName; this.props.panes.displaying = paneName;
} else { } else {
alert(`dialgos: pane (${paneName}) not found`); alert(`dialgos: pane (${paneName}) not found`);
} }
@ -203,7 +201,7 @@ export class Updater {
self = async (): Promise<boolean> => { self = async (): Promise<boolean> => {
const resp = await this.usersClient.self(); const resp = await this.usersClient.self();
if (resp.status === 200) { if (resp.status === 200) {
this.props.panel.panes.userRole = resp.data.role; this.props.panes.userRole = resp.data.role;
return true; return true;
} }
return false; return false;
@ -241,7 +239,7 @@ export class Updater {
lsRes.users.forEach((user: User) => { lsRes.users.forEach((user: User) => {
users = users.set(user.name, user); users = users.set(user.name, user);
}); });
this.props.panel.panes.admin.users = users; this.props.admin.users = users;
return true; return true;
}; };
@ -268,7 +266,7 @@ export class Updater {
Object.keys(lsRes.roles).forEach((role: string) => { Object.keys(lsRes.roles).forEach((role: string) => {
roles = roles.add(role); roles = roles.add(role);
}); });
this.props.panel.panes.admin.roles = roles; this.props.admin.roles = roles;
return true; return true;
}; };
@ -309,13 +307,13 @@ export class Updater {
}; };
setAuthed = (isAuthed: boolean) => { setAuthed = (isAuthed: boolean) => {
this.props.panel.authPane.authed = isAuthed; this.props.login.authed = isAuthed;
}; };
getCaptchaID = async (): Promise<boolean> => { getCaptchaID = async (): Promise<boolean> => {
return this.usersClient.getCaptchaID().then((resp) => { return this.usersClient.getCaptchaID().then((resp) => {
if (resp.status === 200) { if (resp.status === 200) {
this.props.panel.authPane.captchaID = resp.data.id; this.props.login.captchaID = resp.data.id;
} }
return resp.status === 200; return resp.status === 200;
}); });
@ -329,38 +327,29 @@ export class Updater {
updateBrowser = (prevState: ICoreState): ICoreState => { updateBrowser = (prevState: ICoreState): ICoreState => {
return { return {
...prevState, ...prevState,
panel: { browser: { ...prevState.browser, ...this.props.browser },
...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 => { updatePanes = (prevState: ICoreState): ICoreState => {
return { return {
...prevState, ...prevState,
panel: { panes: { ...prevState.panes, ...this.props.panes },
...prevState.panel,
panes: { ...prevState.panel.panes, ...this.props.panel.panes },
},
}; };
}; };
updateAuthPane = (preState: ICoreState): ICoreState => { updateLogin = (prevState: ICoreState): ICoreState => {
preState.panel.authPane = { return {
...preState.panel.authPane, ...prevState,
...this.props.panel.authPane, login: { ...prevState.login, ...this.props.login },
};
};
updateAdmin = (prevState: ICoreState): ICoreState => {
return {
...prevState,
admin: { ...prevState.admin, ...this.props.admin },
}; };
return preState;
}; };
} }