chore(client): clean up and ci (#35)
* fix(client/web): move browser updater to single file * fix(client/web): make UploadMgr singleton * test(client/web): add unit tests for browser * fix(client/web): updater init should be in StateMgr * feat(client/browser): add selectAll button * chore(ci): disable travis although it is awsome
This commit is contained in:
parent
46f03e2e84
commit
e87a342c93
12 changed files with 532 additions and 415 deletions
|
@ -1,7 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "1.15.x"
|
|
||||||
|
|
||||||
go_import_path: github.com/ihexxa/quickshare
|
|
|
@ -15,8 +15,8 @@
|
||||||
"build:task:dev": "webpack --config webpack.task.dev.js --watch",
|
"build:task:dev": "webpack --config webpack.task.dev.js --watch",
|
||||||
"e2e": "jest -c jest.e2e.config.js",
|
"e2e": "jest -c jest.e2e.config.js",
|
||||||
"e2e:watch": "jest --watch -c jest.e2e.config.js",
|
"e2e:watch": "jest --watch -c jest.e2e.config.js",
|
||||||
"test": "jest test",
|
"test": "jest test --maxWorkers=2",
|
||||||
"test:watch": "jest test --watch",
|
"test:watch": "jest test --watch --maxWorkers=2",
|
||||||
"copy": "cp -r ../../static ../../../dockers/nginx/"
|
"copy": "cp -r ../../static ../../../dockers/nginx/"
|
||||||
},
|
},
|
||||||
"author": "hexxa",
|
"author": "hexxa",
|
||||||
|
|
7
src/client/web/src/common/env.ts
Normal file
7
src/client/web/src/common/env.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export function alertMsg(msg: string) {
|
||||||
|
if (alert != null) {
|
||||||
|
alert(msg);
|
||||||
|
} else {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,72 +1,74 @@
|
||||||
|
import * as React from "react";
|
||||||
import { List, Map } from "immutable";
|
import { List, Map } from "immutable";
|
||||||
import { mock, instance, anyString, when } from "ts-mockito";
|
import { mock, instance, anyString, anything, when, verify } from "ts-mockito";
|
||||||
|
|
||||||
import { ICoreState, initWithWorker, mockState } from "../core_state";
|
import { ICoreState, initWithWorker, mockState } from "../core_state";
|
||||||
import {
|
import {
|
||||||
makePromise,
|
makePromise,
|
||||||
makeNumberResponse,
|
makeNumberResponse,
|
||||||
mockUpdate,
|
mockUpdate,
|
||||||
|
addMockUpdate,
|
||||||
|
mockFileList,
|
||||||
} from "../../test/helpers";
|
} from "../../test/helpers";
|
||||||
import { Updater, Browser } from "../browser";
|
import { Browser } from "../browser";
|
||||||
|
import { Updater, setUpdater } from "../browser.updater";
|
||||||
import { MockUsersClient } from "../../client/users_mock";
|
import { MockUsersClient } from "../../client/users_mock";
|
||||||
import { UsersClient } from "../../client/users";
|
import { UsersClient } from "../../client/users";
|
||||||
import { FilesClient } from "../../client/files";
|
import { FilesClient } from "../../client/files";
|
||||||
import { FilesClient as MockFilesClient } from "../../client/files_mock";
|
import { FilesClient as MockFilesClient } from "../../client/files_mock";
|
||||||
import { MetadataResp, UploadInfo } from "../../client";
|
import { MetadataResp, UploadInfo } from "../../client";
|
||||||
import { MockWorker, UploadEntry } from "../../worker/interface";
|
import { MockWorker, UploadEntry } from "../../worker/interface";
|
||||||
import { UploadMgr } 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: setPwd", async () => {
|
test("Updater: addUploads: add each files to UploadMgr", async () => {
|
||||||
const tests = [
|
let coreState = mockState();
|
||||||
{
|
const UploadMgrClass = mock(UploadMgr);
|
||||||
listResp: {
|
const uploadMgr = instance(UploadMgrClass);
|
||||||
status: 200,
|
setUploadMgr(uploadMgr);
|
||||||
statusText: "",
|
|
||||||
data: {
|
|
||||||
metadatas: [
|
|
||||||
{
|
|
||||||
name: "file",
|
|
||||||
size: 1,
|
|
||||||
modTime: "1-1",
|
|
||||||
isDir: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "folder",
|
|
||||||
size: 0,
|
|
||||||
modTime: "1-1",
|
|
||||||
isDir: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filePath: "path/file",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const usersClient = new MockUsersClient("");
|
const filePaths = ["./file1", "./file2"];
|
||||||
const filesClient = new MockFilesClient("");
|
const fileList = mockFileList(filePaths);
|
||||||
for (let i = 0; i < tests.length; i++) {
|
const updater = new Updater();
|
||||||
const tc = tests[i];
|
updater.setUploadings = (infos: Map<string, UploadEntry>) => {};
|
||||||
|
updater.init(coreState.panel.browser);
|
||||||
|
|
||||||
filesClient.listMock(makePromise(tc.listResp));
|
updater.addUploads(fileList);
|
||||||
Updater.setClients(usersClient, filesClient);
|
|
||||||
|
|
||||||
const coreState = initWithWorker(mockWorker);
|
// it seems that new File will do some file path escaping, so just check call time here
|
||||||
Updater.init(coreState.panel.browser);
|
verify(UploadMgrClass.add(anything(), anything())).times(filePaths.length);
|
||||||
await Updater.setItems(List<string>(tc.filePath.split("/")));
|
// filePaths.forEach((filePath, i) => {
|
||||||
const newState = Updater.setBrowser(coreState);
|
// verify(UploadMgrClass.add(anything(), filePath)).once();
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
newState.panel.browser.items.forEach((item, i) => {
|
test("Updater: deleteUploads: call UploadMgr and api to delete", async () => {
|
||||||
expect(item.name).toEqual(tc.listResp.data.metadatas[i].name);
|
let coreState = mockState();
|
||||||
expect(item.size).toEqual(tc.listResp.data.metadatas[i].size);
|
const UploadMgrClass = mock(UploadMgr);
|
||||||
expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime);
|
const uploadMgr = instance(UploadMgrClass);
|
||||||
expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir);
|
setUploadMgr(uploadMgr);
|
||||||
});
|
|
||||||
}
|
const updater = new Updater();
|
||||||
|
const filesClientClass = mock(FilesClient);
|
||||||
|
when(filesClientClass.deleteUploading(anyString())).thenResolve({
|
||||||
|
status: 200,
|
||||||
|
statusText: "",
|
||||||
|
data: "",
|
||||||
|
});
|
||||||
|
const filesClient = instance(filesClientClass);
|
||||||
|
const usersClientClass = mock(UsersClient);
|
||||||
|
const usersClient = instance(usersClientClass);
|
||||||
|
updater.init(coreState.panel.browser);
|
||||||
|
updater.setClients(usersClient, filesClient);
|
||||||
|
|
||||||
|
const filePath = "./path/file";
|
||||||
|
updater.deleteUpload(filePath);
|
||||||
|
|
||||||
|
verify(filesClientClass.deleteUploading(filePath)).once();
|
||||||
|
verify(UploadMgrClass.delete(filePath)).once();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Updater: delete", async () => {
|
test("Updater: delete", async () => {
|
||||||
|
@ -112,21 +114,71 @@ describe("Browser", () => {
|
||||||
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();
|
||||||
|
updater.setClients(usersClient, filesClient);
|
||||||
filesClient.listMock(makePromise(tc.listResp));
|
filesClient.listMock(makePromise(tc.listResp));
|
||||||
filesClient.deleteMock(makeNumberResponse(200));
|
filesClient.deleteMock(makeNumberResponse(200));
|
||||||
Updater.setClients(usersClient, filesClient);
|
|
||||||
|
|
||||||
const coreState = initWithWorker(mockWorker);
|
const coreState = initWithWorker(mockWorker);
|
||||||
Updater.init(coreState.panel.browser);
|
updater.init(coreState.panel.browser);
|
||||||
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<boolean>(tc.selected)
|
Map(tc.selected)
|
||||||
);
|
);
|
||||||
const newState = Updater.setBrowser(coreState);
|
|
||||||
|
const newState = updater.setBrowser(coreState);
|
||||||
|
|
||||||
// TODO: check inputs of delete
|
// TODO: check inputs of delete
|
||||||
|
newState.panel.browser.items.forEach((item, i) => {
|
||||||
|
expect(item.name).toEqual(tc.listResp.data.metadatas[i].name);
|
||||||
|
expect(item.size).toEqual(tc.listResp.data.metadatas[i].size);
|
||||||
|
expect(item.modTime).toEqual(tc.listResp.data.metadatas[i].modTime);
|
||||||
|
expect(item.isDir).toEqual(tc.listResp.data.metadatas[i].isDir);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Updater: setItems", async () => {
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
listResp: {
|
||||||
|
status: 200,
|
||||||
|
statusText: "",
|
||||||
|
data: {
|
||||||
|
metadatas: [
|
||||||
|
{
|
||||||
|
name: "file",
|
||||||
|
size: 1,
|
||||||
|
modTime: "1-1",
|
||||||
|
isDir: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "folder",
|
||||||
|
size: 0,
|
||||||
|
modTime: "1-1",
|
||||||
|
isDir: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filePath: "path/file",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const usersClient = new MockUsersClient("");
|
||||||
|
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));
|
||||||
|
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);
|
||||||
|
|
||||||
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);
|
||||||
|
@ -173,22 +225,19 @@ describe("Browser", () => {
|
||||||
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();
|
||||||
|
|
||||||
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 = initWithWorker(mockWorker);
|
const coreState = initWithWorker(mockWorker);
|
||||||
Updater.init(coreState.panel.browser);
|
updater.init(coreState.panel.browser);
|
||||||
await Updater.moveHere(
|
await updater.moveHere(tc.dirPath1, tc.dirPath2, Map(tc.selected));
|
||||||
tc.dirPath1,
|
|
||||||
tc.dirPath2,
|
const newState = updater.setBrowser(coreState);
|
||||||
Map<boolean>(tc.selected)
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: check inputs of move
|
// TODO: check inputs of move
|
||||||
|
|
||||||
const newState = Updater.setBrowser(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);
|
||||||
|
@ -198,71 +247,36 @@ describe("Browser", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
xtest("Browser: deleteUploading", async () => {
|
test("Browser: deleteUpload: tell uploader to deleteUpload and refreshUploadings", async () => {
|
||||||
interface TestCase {
|
let coreState = mockState();
|
||||||
deleteFile: string;
|
addMockUpdate(coreState.panel.browser);
|
||||||
preState: ICoreState;
|
const component = new Browser(coreState.panel.browser);
|
||||||
postState: ICoreState;
|
const UpdaterClass = mock(Updater);
|
||||||
}
|
const mockUpdater = instance(UpdaterClass);
|
||||||
|
setUpdater(mockUpdater);
|
||||||
|
when(UpdaterClass.setItems(anything())).thenResolve();
|
||||||
|
when(UpdaterClass.deleteUpload(anyString())).thenResolve(true);
|
||||||
|
when(UpdaterClass.refreshUploadings()).thenResolve(true);
|
||||||
|
|
||||||
const tcs: any = [
|
const filePath = "filePath";
|
||||||
{
|
await component.deleteUpload(filePath);
|
||||||
deleteFile: "./path/file",
|
|
||||||
preState: {
|
|
||||||
browser: {
|
|
||||||
uploadings: List<UploadInfo>([
|
|
||||||
{
|
|
||||||
realFilePath: "./path/file",
|
|
||||||
size: 1,
|
|
||||||
uploaded: 0,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
update: mockUpdate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
postState: {
|
|
||||||
browser: {
|
|
||||||
uploadings: List<UploadInfo>(),
|
|
||||||
update: mockUpdate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const setState = (patch: any, state: ICoreState): ICoreState => {
|
verify(UpdaterClass.deleteUpload(filePath)).once();
|
||||||
state.panel.browser = patch.browser;
|
verify(UpdaterClass.refreshUploadings()).once();
|
||||||
return state;
|
});
|
||||||
};
|
|
||||||
const mockFilesClientClass = mock(FilesClient);
|
|
||||||
when(mockFilesClientClass.deleteUploading(anyString())).thenResolve({
|
|
||||||
status: 200,
|
|
||||||
statusText: "",
|
|
||||||
data: "",
|
|
||||||
});
|
|
||||||
// TODO: the return should dpends on test case
|
|
||||||
when(mockFilesClientClass.listUploadings()).thenResolve({
|
|
||||||
status: 200,
|
|
||||||
statusText: "",
|
|
||||||
data: { uploadInfos: Array<UploadInfo>() },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockUsersClientClass = mock(UsersClient);
|
test("Browser: stopUploading: tell updater to stopUploading", async () => {
|
||||||
|
let coreState = mockState();
|
||||||
|
addMockUpdate(coreState.panel.browser);
|
||||||
|
const component = new Browser(coreState.panel.browser);
|
||||||
|
const UpdaterClass = mock(Updater);
|
||||||
|
const mockUpdater = instance(UpdaterClass);
|
||||||
|
setUpdater(mockUpdater);
|
||||||
|
when(UpdaterClass.stopUploading(anyString())).thenReturn();
|
||||||
|
|
||||||
const mockFilesClient = instance(mockFilesClientClass);
|
const filePath = "filePath";
|
||||||
const mockUsersClient = instance(mockUsersClientClass);
|
component.stopUploading(filePath);
|
||||||
tcs.forEach((tc: TestCase) => {
|
|
||||||
const preState = setState(tc.preState, mockState());
|
|
||||||
const postState = setState(tc.postState, mockState());
|
|
||||||
// const existingFileName = preState.panel.browser.uploadings.get(0).realFilePath;
|
|
||||||
const infos:Map<string, UploadEntry> = Map();
|
|
||||||
UploadMgr._setInfos(infos);
|
|
||||||
|
|
||||||
const component = new Browser(preState.panel.browser);
|
verify(UpdaterClass.stopUploading(filePath)).once();
|
||||||
Updater.init(preState.panel.browser);
|
|
||||||
Updater.setClients(mockUsersClient, mockFilesClient);
|
|
||||||
|
|
||||||
component.deleteUploading(tc.deleteFile);
|
|
||||||
expect(Updater.props).toEqual(postState.panel.browser);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { List, Map } from "immutable";
|
||||||
import FileSize from "filesize";
|
import FileSize from "filesize";
|
||||||
|
|
||||||
import { Layouter } from "./layouter";
|
import { Layouter } from "./layouter";
|
||||||
|
import { alertMsg } from "../common/env";
|
||||||
|
import { updater } from "./browser.updater";
|
||||||
import { ICoreState } from "./core_state";
|
import { ICoreState } from "./core_state";
|
||||||
import {
|
import {
|
||||||
IUsersClient,
|
IUsersClient,
|
||||||
|
@ -11,9 +13,7 @@ import {
|
||||||
MetadataResp,
|
MetadataResp,
|
||||||
UploadInfo,
|
UploadInfo,
|
||||||
} from "../client";
|
} from "../client";
|
||||||
import { FilesClient } from "../client/files";
|
import { Up } from "../worker/upload_mgr";
|
||||||
import { UsersClient } from "../client/users";
|
|
||||||
import { UploadMgr } from "../worker/upload_mgr";
|
|
||||||
import { UploadEntry } from "../worker/interface";
|
import { UploadEntry } from "../worker/interface";
|
||||||
|
|
||||||
export const uploadCheckCycle = 1000;
|
export const uploadCheckCycle = 1000;
|
||||||
|
@ -39,143 +39,12 @@ export interface Props {
|
||||||
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemPath(dirPath: string, itemName: string): string {
|
export function getItemPath(dirPath: string, itemName: string): string {
|
||||||
return dirPath.endsWith("/")
|
return dirPath.endsWith("/")
|
||||||
? `${dirPath}${itemName}`
|
? `${dirPath}${itemName}`
|
||||||
: `${dirPath}/${itemName}`;
|
: `${dirPath}/${itemName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Updater {
|
|
||||||
static props: Props;
|
|
||||||
private static usersClient: IUsersClient;
|
|
||||||
private static filesClient: IFilesClient;
|
|
||||||
|
|
||||||
static init = (props: Props) => (Updater.props = { ...props });
|
|
||||||
static setClients(usersClient: IUsersClient, filesClient: IFilesClient) {
|
|
||||||
Updater.usersClient = usersClient;
|
|
||||||
Updater.filesClient = filesClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
static setUploadings = (infos: Map<string, UploadEntry>) => {
|
|
||||||
Updater.props.uploadings = List<UploadInfo>(
|
|
||||||
infos.valueSeq().map(
|
|
||||||
(v: UploadEntry): UploadInfo => {
|
|
||||||
return {
|
|
||||||
realFilePath: v.filePath,
|
|
||||||
size: v.size,
|
|
||||||
uploaded: v.uploaded,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
static setItems = async (dirParts: List<string>): Promise<void> => {
|
|
||||||
const dirPath = dirParts.join("/");
|
|
||||||
const listResp = await Updater.filesClient.list(dirPath);
|
|
||||||
|
|
||||||
Updater.props.dirPath = dirParts;
|
|
||||||
Updater.props.items =
|
|
||||||
listResp.status === 200
|
|
||||||
? List<MetadataResp>(listResp.data.metadatas)
|
|
||||||
: Updater.props.items;
|
|
||||||
};
|
|
||||||
|
|
||||||
static refreshUploadings = async (): Promise<boolean> => {
|
|
||||||
const luResp = await Updater.filesClient.listUploadings();
|
|
||||||
|
|
||||||
Updater.props.uploadings =
|
|
||||||
luResp.status === 200
|
|
||||||
? List<UploadInfo>(luResp.data.uploadInfos)
|
|
||||||
: Updater.props.uploadings;
|
|
||||||
return luResp.status === 200;
|
|
||||||
};
|
|
||||||
|
|
||||||
static deleteUploading = async (filePath: string): Promise<boolean> => {
|
|
||||||
UploadMgr.delete(filePath);
|
|
||||||
const resp = await Updater.filesClient.deleteUploading(filePath);
|
|
||||||
return resp.status === 200;
|
|
||||||
};
|
|
||||||
|
|
||||||
static stopUploading = (filePath: string) => {
|
|
||||||
UploadMgr.stop(filePath);
|
|
||||||
};
|
|
||||||
|
|
||||||
static mkDir = async (dirPath: string): Promise<void> => {
|
|
||||||
let resp = await Updater.filesClient.mkdir(dirPath);
|
|
||||||
if (resp.status !== 200) {
|
|
||||||
alert(`failed to make dir ${dirPath}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static 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 Updater.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 Updater.setItems(dirParts);
|
|
||||||
};
|
|
||||||
|
|
||||||
static 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 Updater.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 Updater.setItems(List<string>(dstDir.split("/")));
|
|
||||||
};
|
|
||||||
|
|
||||||
static addUploadFiles = (fileList: FileList, len: number) => {
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
const filePath = getItemPath(
|
|
||||||
Updater.props.dirPath.join("/"),
|
|
||||||
fileList[i].name
|
|
||||||
);
|
|
||||||
// do not wait for the promise
|
|
||||||
UploadMgr.add(fileList[i], filePath);
|
|
||||||
}
|
|
||||||
Updater.setUploadings(UploadMgr.list());
|
|
||||||
};
|
|
||||||
|
|
||||||
static setBrowser = (prevState: ICoreState): ICoreState => {
|
|
||||||
prevState.panel.browser = { ...prevState.panel, ...Updater.props };
|
|
||||||
return prevState;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
selectedSrc: string;
|
selectedSrc: string;
|
||||||
|
@ -190,8 +59,6 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
|
|
||||||
constructor(p: Props) {
|
constructor(p: Props) {
|
||||||
super(p);
|
super(p);
|
||||||
Updater.init(p);
|
|
||||||
Updater.setClients(new UsersClient(""), new FilesClient(""));
|
|
||||||
this.update = p.update;
|
this.update = p.update;
|
||||||
this.state = {
|
this.state = {
|
||||||
inputValue: "",
|
inputValue: "",
|
||||||
|
@ -199,6 +66,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
selectedItems: Map<string, boolean>(),
|
selectedItems: Map<string, boolean>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Up().setStatusCb(this.updateProgress);
|
||||||
this.uploadInput = undefined;
|
this.uploadInput = undefined;
|
||||||
this.assignInput = (input) => {
|
this.assignInput = (input) => {
|
||||||
this.uploadInput = ReactDOM.findDOMNode(input);
|
this.uploadInput = ReactDOM.findDOMNode(input);
|
||||||
|
@ -208,83 +76,64 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
const uploadInput = this.uploadInput as HTMLButtonElement;
|
const uploadInput = this.uploadInput as HTMLButtonElement;
|
||||||
uploadInput.click();
|
uploadInput.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
UploadMgr.setStatusCb(this.updateProgress);
|
|
||||||
Updater.setItems(p.dirPath)
|
|
||||||
.then(() => {
|
|
||||||
return Updater.refreshUploadings();
|
|
||||||
})
|
|
||||||
.then((_: boolean) => {
|
|
||||||
this.update(Updater.setBrowser);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
onInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ inputValue: ev.target.value });
|
this.setState({ inputValue: ev.target.value });
|
||||||
};
|
};
|
||||||
select = (itemName: string) => {
|
|
||||||
const selectedItems = this.state.selectedItems.has(itemName)
|
|
||||||
? this.state.selectedItems.delete(itemName)
|
|
||||||
: this.state.selectedItems.set(itemName, true);
|
|
||||||
|
|
||||||
this.setState({
|
addUploads = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
selectedSrc: this.props.dirPath.join("/"),
|
const fileList = List<File>();
|
||||||
selectedItems: selectedItems,
|
for (let i = 0; i < event.target.files.length; i++) {
|
||||||
});
|
fileList.push(event.target.files[i]);
|
||||||
|
}
|
||||||
|
updater().addUploads(fileList);
|
||||||
|
this.update(updater().setBrowser);
|
||||||
};
|
};
|
||||||
|
|
||||||
addUploadFile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
deleteUpload = (filePath: string): Promise<void> => {
|
||||||
Updater.addUploadFiles(event.target.files, event.target.files.length);
|
return updater()
|
||||||
this.update(Updater.setBrowser);
|
.deleteUpload(filePath)
|
||||||
|
.then((ok: boolean) => {
|
||||||
|
if (!ok) {
|
||||||
|
alertMsg(`Failed to delete uploading ${filePath}`);
|
||||||
|
}
|
||||||
|
return updater().refreshUploadings();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.update(updater().setBrowser);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
updateProgress = (infos: Map<string, UploadEntry>) => {
|
stopUploading = (filePath: string) => {
|
||||||
Updater.setUploadings(infos);
|
updater().stopUploading(filePath);
|
||||||
Updater.setItems(this.props.dirPath).then(() => {
|
this.update(updater().setBrowser);
|
||||||
this.update(Updater.setBrowser);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMkDir = () => {
|
onMkDir = () => {
|
||||||
if (this.state.inputValue === "") {
|
if (this.state.inputValue === "") {
|
||||||
alert("folder name can not be empty");
|
alertMsg("folder name can not be empty");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dirPath = getItemPath(
|
const dirPath = getItemPath(
|
||||||
this.props.dirPath.join("/"),
|
this.props.dirPath.join("/"),
|
||||||
this.state.inputValue
|
this.state.inputValue
|
||||||
);
|
);
|
||||||
Updater.mkDir(dirPath)
|
updater()
|
||||||
|
.mkDir(dirPath)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setState({ inputValue: "" });
|
this.setState({ inputValue: "" });
|
||||||
return Updater.setItems(this.props.dirPath);
|
return updater().setItems(this.props.dirPath);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.update(Updater.setBrowser);
|
this.update(updater().setBrowser);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteUploading = (filePath: string) => {
|
|
||||||
Updater.deleteUploading(filePath)
|
|
||||||
.then((ok: boolean) => {
|
|
||||||
if (!ok) {
|
|
||||||
alert(`Failed to delete uploading ${filePath}`);
|
|
||||||
}
|
|
||||||
return Updater.refreshUploadings();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.update(Updater.setBrowser);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
stopUploading = (filePath: string) => {
|
|
||||||
Updater.stopUploading(filePath);
|
|
||||||
this.update(Updater.setBrowser);
|
|
||||||
};
|
|
||||||
|
|
||||||
delete = () => {
|
delete = () => {
|
||||||
if (this.props.dirPath.join("/") !== this.state.selectedSrc) {
|
if (this.props.dirPath.join("/") !== this.state.selectedSrc) {
|
||||||
alert("please select file or folder to delete at first");
|
alertMsg("please select file or folder to delete at first");
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedSrc: this.props.dirPath.join("/"),
|
selectedSrc: this.props.dirPath.join("/"),
|
||||||
selectedItems: Map<string, boolean>(),
|
selectedItems: Map<string, boolean>(),
|
||||||
|
@ -292,17 +141,38 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Updater.delete(
|
updater()
|
||||||
this.props.dirPath,
|
.delete(this.props.dirPath, this.props.items, this.state.selectedItems)
|
||||||
this.props.items,
|
.then(() => {
|
||||||
this.state.selectedItems
|
this.update(updater().setBrowser);
|
||||||
).then(() => {
|
this.setState({
|
||||||
this.update(Updater.setBrowser);
|
selectedSrc: "",
|
||||||
this.setState({
|
selectedItems: Map<string, boolean>(),
|
||||||
selectedSrc: "",
|
});
|
||||||
selectedItems: Map<string, boolean>(),
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
moveHere = () => {
|
||||||
|
const oldDir = this.state.selectedSrc;
|
||||||
|
const newDir = this.props.dirPath.join("/");
|
||||||
|
if (oldDir === newDir) {
|
||||||
|
alertMsg("source directory is same as destination directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updater()
|
||||||
|
.moveHere(
|
||||||
|
this.state.selectedSrc,
|
||||||
|
this.props.dirPath.join("/"),
|
||||||
|
this.state.selectedItems
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.update(updater().setBrowser);
|
||||||
|
this.setState({
|
||||||
|
selectedSrc: "",
|
||||||
|
selectedItems: Map<string, boolean>(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
gotoChild = (childDirName: string) => {
|
gotoChild = (childDirName: string) => {
|
||||||
|
@ -314,29 +184,49 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Updater.setItems(dirPath).then(() => {
|
updater()
|
||||||
this.update(Updater.setBrowser);
|
.setItems(dirPath)
|
||||||
|
.then(() => {
|
||||||
|
this.update(updater().setBrowser);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateProgress = (infos: Map<string, UploadEntry>) => {
|
||||||
|
updater().setUploadings(infos);
|
||||||
|
updater()
|
||||||
|
.setItems(this.props.dirPath)
|
||||||
|
.then(() => {
|
||||||
|
this.update(updater().setBrowser);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
select = (itemName: string) => {
|
||||||
|
const selectedItems = this.state.selectedItems.has(itemName)
|
||||||
|
? this.state.selectedItems.delete(itemName)
|
||||||
|
: this.state.selectedItems.set(itemName, true);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedSrc: this.props.dirPath.join("/"),
|
||||||
|
selectedItems: selectedItems,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
moveHere = () => {
|
selectAll = () => {
|
||||||
const oldDir = this.state.selectedSrc;
|
let newSelected = Map<string, boolean>();
|
||||||
const newDir = this.props.dirPath.join("/");
|
const someSelected = this.state.selectedItems.size === 0 ? true : false;
|
||||||
if (oldDir === newDir) {
|
if (someSelected) {
|
||||||
alert("source directory is same as destination directory");
|
this.props.items.forEach((item) => {
|
||||||
return;
|
newSelected = newSelected.set(item.name, true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.props.items.forEach((item) => {
|
||||||
|
newSelected = newSelected.delete(item.name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Updater.moveHere(
|
this.setState({
|
||||||
this.state.selectedSrc,
|
selectedSrc: this.props.dirPath.join("/"),
|
||||||
this.props.dirPath.join("/"),
|
selectedItems: newSelected,
|
||||||
this.state.selectedItems
|
|
||||||
).then(() => {
|
|
||||||
this.update(Updater.setBrowser);
|
|
||||||
this.setState({
|
|
||||||
selectedSrc: "",
|
|
||||||
selectedItems: Map<string, boolean>(),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -397,7 +287,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
onChange={this.addUploadFile}
|
onChange={this.addUploads}
|
||||||
multiple={true}
|
multiple={true}
|
||||||
value={this.props.uploadValue}
|
value={this.props.uploadValue}
|
||||||
ref={this.assignInput}
|
ref={this.assignInput}
|
||||||
|
@ -505,7 +395,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
Stop
|
Stop
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => this.deleteUploading(uploading.realFilePath)}
|
onClick={() => this.deleteUpload(uploading.realFilePath)}
|
||||||
className="white-font"
|
className="white-font"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
|
@ -563,7 +453,15 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td className={sizeCellClass}>File Size</td>
|
<td className={sizeCellClass}>File Size</td>
|
||||||
<td className={modTimeCellClass}>Mod Time</td>
|
<td className={modTimeCellClass}>Mod Time</td>
|
||||||
<td>Edit</td>
|
<td>
|
||||||
|
<button
|
||||||
|
onClick={() => this.selectAll()}
|
||||||
|
className={`white-font`}
|
||||||
|
style={{ width: "8rem", display: "inline-block" }}
|
||||||
|
>
|
||||||
|
Select All
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{itemList}</tbody>
|
<tbody>{itemList}</tbody>
|
||||||
|
|
153
src/client/web/src/components/browser.updater.ts
Normal file
153
src/client/web/src/components/browser.updater.ts
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
prevState.panel.browser = { ...prevState.panel, ...this.props };
|
||||||
|
return prevState;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export let browserUpdater = new Updater();
|
||||||
|
export const updater = (): Updater => {
|
||||||
|
return browserUpdater;
|
||||||
|
};
|
||||||
|
export const setUpdater = (updater: Updater) => {
|
||||||
|
browserUpdater = updater;
|
||||||
|
};
|
|
@ -6,7 +6,7 @@ import { FgWorker } from "../worker/upload.fgworker";
|
||||||
import { Props as PanelProps } from "./root_frame";
|
import { Props as PanelProps } from "./root_frame";
|
||||||
import { Item } from "./browser";
|
import { Item } from "./browser";
|
||||||
import { UploadInfo } from "../client";
|
import { UploadInfo } from "../client";
|
||||||
import { UploadMgr, IWorker } from "../worker/upload_mgr";
|
import { Up, initUploadMgr, IWorker } from "../worker/upload_mgr";
|
||||||
|
|
||||||
export class BaseUpdater {
|
export class BaseUpdater {
|
||||||
public static props: any;
|
public static props: any;
|
||||||
|
@ -27,15 +27,15 @@ export interface ICoreState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initWithWorker(worker: IWorker): ICoreState {
|
export function initWithWorker(worker: IWorker): ICoreState {
|
||||||
UploadMgr.init(worker);
|
initUploadMgr(worker);
|
||||||
return initState();
|
return initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init(): ICoreState {
|
export function init(): ICoreState {
|
||||||
const scripts = Array.from(document.querySelectorAll("script"));
|
const scripts = Array.from(document.querySelectorAll("script"));
|
||||||
const worker = Worker == null ? new FgWorker() : new BgWorker();
|
const worker = Worker == null ? new FgWorker() : new BgWorker();
|
||||||
|
initUploadMgr(worker);
|
||||||
|
|
||||||
UploadMgr.init(worker);
|
|
||||||
return initState();
|
return initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ICoreState } from "./core_state";
|
||||||
import { IUsersClient } from "../client";
|
import { IUsersClient } from "../client";
|
||||||
import { UsersClient } from "../client/users";
|
import { UsersClient } from "../client/users";
|
||||||
import { Updater as PanesUpdater } from "./panes";
|
import { Updater as PanesUpdater } from "./panes";
|
||||||
import { Updater as BrowserUpdater } from "./browser";
|
import { updater as BrowserUpdater } from "./browser.updater";
|
||||||
import { Layouter } from "./layouter";
|
import { Layouter } from "./layouter";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -104,7 +104,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
this.update(PanesUpdater.updateState);
|
this.update(PanesUpdater.updateState);
|
||||||
|
|
||||||
// refresh
|
// refresh
|
||||||
return BrowserUpdater.setItems(
|
return BrowserUpdater().setItems(
|
||||||
List<string>(["."])
|
List<string>(["."])
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,10 +113,10 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return BrowserUpdater.refreshUploadings();
|
return BrowserUpdater().refreshUploadings();
|
||||||
})
|
})
|
||||||
.then((_: boolean) => {
|
.then((_: boolean) => {
|
||||||
this.update(BrowserUpdater.setBrowser);
|
this.update(BrowserUpdater().setBrowser);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { updater as BrowserUpdater } from "./browser.updater";
|
||||||
import { ICoreState, init } from "./core_state";
|
import { ICoreState, init } from "./core_state";
|
||||||
import { RootFrame } from "./root_frame";
|
import { RootFrame } from "./root_frame";
|
||||||
|
import { FilesClient } from "../client/files";
|
||||||
|
import { UsersClient } from "../client/users";
|
||||||
|
|
||||||
export interface Props {}
|
export interface Props {}
|
||||||
export interface State extends ICoreState {}
|
export interface State extends ICoreState {}
|
||||||
|
@ -10,8 +13,22 @@ export class StateMgr extends React.Component<Props, State, {}> {
|
||||||
constructor(p: Props) {
|
constructor(p: Props) {
|
||||||
super(p);
|
super(p);
|
||||||
this.state = init();
|
this.state = init();
|
||||||
|
this.initUpdaters(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initUpdaters = (state: ICoreState) => {
|
||||||
|
BrowserUpdater().init(state.panel.browser);
|
||||||
|
BrowserUpdater().setClients(new UsersClient(""), new FilesClient(""));
|
||||||
|
BrowserUpdater()
|
||||||
|
.setItems(state.panel.browser.dirPath)
|
||||||
|
.then(() => {
|
||||||
|
return BrowserUpdater().refreshUploadings();
|
||||||
|
})
|
||||||
|
.then((_: boolean) => {
|
||||||
|
this.update(BrowserUpdater().setBrowser);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
update = (apply: (prevState: ICoreState) => ICoreState): void => {
|
update = (apply: (prevState: ICoreState) => ICoreState): void => {
|
||||||
this.setState(apply(this.state));
|
this.setState(apply(this.state));
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Response } from "../client";
|
import { Response } from "../client";
|
||||||
import { ICoreState } from "../components/core_state";
|
import { ICoreState, mockState } from "../components/core_state";
|
||||||
|
import { List } from "immutable";
|
||||||
|
|
||||||
export const makePromise = (ret: any): Promise<any> => {
|
export const makePromise = (ret: any): Promise<any> => {
|
||||||
return new Promise<any>((resolve) => {
|
return new Promise<any>((resolve) => {
|
||||||
|
@ -15,4 +16,25 @@ export const makeNumberResponse = (status: number): Promise<Response> => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockUpdate = (apply: (prevState: ICoreState) => ICoreState): void => {};
|
export const mockUpdate = (
|
||||||
|
apply: (prevState: ICoreState) => ICoreState
|
||||||
|
): void => {
|
||||||
|
apply(mockState());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMockUpdate = (subState: any) => {
|
||||||
|
subState.update = mockUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function mockRandFile(filePath: string): File {
|
||||||
|
const values = new Array<string>(Math.floor(7 * Math.random()));
|
||||||
|
const content = [values.join("")];
|
||||||
|
return new File(content, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mockFileList(filePaths: Array<string>): List<File> {
|
||||||
|
const files = filePaths.map(filePath => {
|
||||||
|
return mockRandFile(filePath);
|
||||||
|
})
|
||||||
|
return List<File>(files);
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { mock, instance, when, anything } from "ts-mockito";
|
||||||
import { FilesClient } from "../../client/files_mock";
|
import { FilesClient } from "../../client/files_mock";
|
||||||
import { makePromise } from "../../test/helpers";
|
import { makePromise } from "../../test/helpers";
|
||||||
|
|
||||||
import { UploadMgr } from "../upload_mgr";
|
import { Up, initUploadMgr } from "../upload_mgr";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FileWorkerReq,
|
FileWorkerReq,
|
||||||
|
@ -115,17 +115,18 @@ describe("UploadMgr", () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const worker = new MockWorker();
|
const worker = new MockWorker();
|
||||||
UploadMgr.setCycle(100);
|
|
||||||
|
|
||||||
for (let i = 0; i < tcs.length; i++) {
|
for (let i = 0; i < tcs.length; i++) {
|
||||||
const infoMap = arraytoMap(tcs[i].inputInfos);
|
initUploadMgr(worker);
|
||||||
UploadMgr._setInfos(infoMap);
|
const up = Up();
|
||||||
|
up.setCycle(100);
|
||||||
|
|
||||||
|
const infoMap = arraytoMap(tcs[i].inputInfos);
|
||||||
|
up._setInfos(infoMap);
|
||||||
|
|
||||||
UploadMgr.init(worker);
|
|
||||||
// polling needs several rounds to finish all the tasks
|
// polling needs several rounds to finish all the tasks
|
||||||
await delay(tcs.length * UploadMgr.getCycle() + 1000);
|
await delay(tcs.length * up.getCycle() + 1000);
|
||||||
// TODO: find a better way to wait
|
// TODO: find a better way to wait
|
||||||
const gotInfos = UploadMgr.list();
|
const gotInfos = up.list();
|
||||||
|
|
||||||
const expectedInfoMap = arraytoMap(tcs[i].expectedInfos);
|
const expectedInfoMap = arraytoMap(tcs[i].expectedInfos);
|
||||||
gotInfos.keySeq().forEach((filePath) => {
|
gotInfos.keySeq().forEach((filePath) => {
|
||||||
|
@ -135,7 +136,7 @@ describe("UploadMgr", () => {
|
||||||
expect(expectedInfoMap.get(filePath)).toEqual(gotInfos.get(filePath));
|
expect(expectedInfoMap.get(filePath)).toEqual(gotInfos.get(filePath));
|
||||||
});
|
});
|
||||||
|
|
||||||
UploadMgr.destory();
|
up.destory();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,8 +10,9 @@ import {
|
||||||
errKind,
|
errKind,
|
||||||
uploadInfoKind,
|
uploadInfoKind,
|
||||||
} from "./interface";
|
} from "./interface";
|
||||||
|
import { FgWorker } from "./upload.fgworker";
|
||||||
|
|
||||||
const win = self as any;
|
const win: Window = self as any;
|
||||||
|
|
||||||
export interface IWorker {
|
export interface IWorker {
|
||||||
onmessage: (event: MessageEvent) => void;
|
onmessage: (event: MessageEvent) => void;
|
||||||
|
@ -19,51 +20,51 @@ export interface IWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UploadMgr {
|
export class UploadMgr {
|
||||||
private static infos = Map<string, UploadEntry>();
|
private infos = Map<string, UploadEntry>();
|
||||||
private static worker: IWorker;
|
private worker: IWorker;
|
||||||
private static intervalID: NodeJS.Timeout;
|
private intervalID: number;
|
||||||
private static cycle: number = 500;
|
private cycle: number = 500;
|
||||||
private static statusCb = (infos: Map<string, UploadEntry>):void => {};
|
private statusCb = (infos: Map<string, UploadEntry>): void => {};
|
||||||
|
|
||||||
static _setInfos = (infos: Map<string, UploadEntry>) => {
|
constructor(worker: IWorker) {
|
||||||
UploadMgr.infos = infos;
|
this.worker = worker;
|
||||||
};
|
|
||||||
|
|
||||||
static setCycle = (ms: number) => {
|
|
||||||
UploadMgr.cycle = ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
static getCycle = (): number => {
|
|
||||||
return UploadMgr.cycle;
|
|
||||||
};
|
|
||||||
|
|
||||||
static setStatusCb = (cb: (infos: Map<string, UploadEntry>) => void) => {
|
|
||||||
UploadMgr.statusCb = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
static init = (worker: IWorker) => {
|
|
||||||
UploadMgr.worker = worker;
|
|
||||||
// TODO: fallback to normal if Web Worker is not available
|
// TODO: fallback to normal if Web Worker is not available
|
||||||
UploadMgr.worker.onmessage = UploadMgr.respHandler;
|
this.worker.onmessage = this.respHandler;
|
||||||
|
|
||||||
const syncing = () => {
|
const syncing = () => {
|
||||||
UploadMgr.worker.postMessage({
|
this.worker.postMessage({
|
||||||
kind: syncReqKind,
|
kind: syncReqKind,
|
||||||
infos: UploadMgr.infos.valueSeq().toArray(),
|
infos: this.infos.valueSeq().toArray(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
UploadMgr.intervalID = win.setInterval(syncing, UploadMgr.cycle);
|
this.intervalID = win.setInterval(syncing, this.cycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
destory = () => {
|
||||||
|
win.clearInterval(this.intervalID);
|
||||||
};
|
};
|
||||||
|
|
||||||
static destory = () => {
|
_setInfos = (infos: Map<string, UploadEntry>) => {
|
||||||
win.clearInterval(UploadMgr.intervalID);
|
this.infos = infos;
|
||||||
};
|
};
|
||||||
|
|
||||||
static add = (file: File, filePath: string) => {
|
setCycle = (ms: number) => {
|
||||||
const entry = UploadMgr.infos.get(filePath);
|
this.cycle = ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
getCycle = (): number => {
|
||||||
|
return this.cycle;
|
||||||
|
};
|
||||||
|
|
||||||
|
setStatusCb = (cb: (infos: Map<string, UploadEntry>) => void) => {
|
||||||
|
this.statusCb = cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
add = (file: File, filePath: string) => {
|
||||||
|
const entry = this.infos.get(filePath);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
// new uploading
|
// new uploading
|
||||||
UploadMgr.infos = UploadMgr.infos.set(filePath, {
|
this.infos = this.infos.set(filePath, {
|
||||||
file: file,
|
file: file,
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
|
@ -73,36 +74,35 @@ export class UploadMgr {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// restart the uploading
|
// restart the uploading
|
||||||
UploadMgr.infos = UploadMgr.infos.set(filePath, {
|
this.infos = this.infos.set(filePath, {
|
||||||
...entry,
|
...entry,
|
||||||
runnable: true,
|
runnable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static stop = (filePath: string) => {
|
stop = (filePath: string) => {
|
||||||
const entry = UploadMgr.infos.get(filePath);
|
const entry = this.infos.get(filePath);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
UploadMgr.infos = UploadMgr.infos.set(filePath, {
|
this.infos = this.infos.set(filePath, {
|
||||||
...entry,
|
...entry,
|
||||||
runnable: false,
|
runnable: false,
|
||||||
});
|
});
|
||||||
console.log("stopped", filePath);
|
|
||||||
} else {
|
} else {
|
||||||
alert(`failed to stop uploading ${filePath}: not found`);
|
alert(`failed to stop uploading ${filePath}: not found`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static delete = (filePath: string) => {
|
delete = (filePath: string) => {
|
||||||
UploadMgr.stop(filePath);
|
this.stop(filePath);
|
||||||
UploadMgr.infos = UploadMgr.infos.delete(filePath);
|
this.infos = this.infos.delete(filePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
static list = (): Map<string, UploadEntry> => {
|
list = (): Map<string, UploadEntry> => {
|
||||||
return UploadMgr.infos;
|
return this.infos;
|
||||||
};
|
};
|
||||||
|
|
||||||
static respHandler = (event: MessageEvent) => {
|
respHandler = (event: MessageEvent) => {
|
||||||
const resp = event.data as FileWorkerResp;
|
const resp = event.data as FileWorkerResp;
|
||||||
|
|
||||||
switch (resp.kind) {
|
switch (resp.kind) {
|
||||||
|
@ -113,13 +113,13 @@ export class UploadMgr {
|
||||||
break;
|
break;
|
||||||
case uploadInfoKind:
|
case uploadInfoKind:
|
||||||
const infoResp = resp as UploadInfoResp;
|
const infoResp = resp as UploadInfoResp;
|
||||||
const entry = UploadMgr.infos.get(infoResp.filePath);
|
const entry = this.infos.get(infoResp.filePath);
|
||||||
|
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
if (infoResp.uploaded === entry.size) {
|
if (infoResp.uploaded === entry.size) {
|
||||||
UploadMgr.infos = UploadMgr.infos.delete(infoResp.filePath);
|
this.infos = this.infos.delete(infoResp.filePath);
|
||||||
} else {
|
} else {
|
||||||
UploadMgr.infos = UploadMgr.infos.set(infoResp.filePath, {
|
this.infos = this.infos.set(infoResp.filePath, {
|
||||||
...entry,
|
...entry,
|
||||||
uploaded: infoResp.uploaded,
|
uploaded: infoResp.uploaded,
|
||||||
runnable: infoResp.runnable,
|
runnable: infoResp.runnable,
|
||||||
|
@ -128,13 +128,13 @@ export class UploadMgr {
|
||||||
}
|
}
|
||||||
|
|
||||||
// call back to update the info
|
// call back to update the info
|
||||||
UploadMgr.statusCb(UploadMgr.infos);
|
this.statusCb(this.infos);
|
||||||
} else {
|
} else {
|
||||||
// TODO: refine this
|
// TODO: refine this
|
||||||
console.error(
|
console.error(
|
||||||
`respHandler: fail to found: file(${
|
`respHandler: fail to found: file(${
|
||||||
infoResp.filePath
|
infoResp.filePath
|
||||||
}) infos(${UploadMgr.infos.toObject()})`
|
}) infos(${this.infos.toObject()})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -143,3 +143,15 @@ export class UploadMgr {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export let uploadMgr = new UploadMgr(new FgWorker());
|
||||||
|
export const initUploadMgr = (worker: IWorker): UploadMgr => {
|
||||||
|
uploadMgr = new UploadMgr(worker);
|
||||||
|
return uploadMgr;
|
||||||
|
};
|
||||||
|
export const Up = (): UploadMgr => {
|
||||||
|
return uploadMgr;
|
||||||
|
};
|
||||||
|
export const setUploadMgr = (up: UploadMgr) => {
|
||||||
|
uploadMgr = up;
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue