"react-transition-group": "2.4.0",
"redux": "4.0.0",
"redux-thunk": "2.3.0",
- "unionize": "2.1.2"
+ "unionize": "2.1.2",
+ "uuid": "3.3.2"
},
"scripts": {
"start": "react-scripts-ts start",
"@types/react-router-redux": "5.0.15",
"@types/redux-devtools": "3.0.44",
"@types/redux-form": "7.4.5",
+ "@types/uuid": "3.4.4",
"axios-mock-adapter": "1.15.0",
"enzyme": "3.4.4",
"enzyme-adapter-react-16": "1.2.0",
import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions';
import { processResourceActionSet } from './views-components/context-menu/action-sets/process-resource-action-set';
import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
fetchConfig()
.then(({ config, apiHost }) => {
const history = createBrowserHistory();
- const services = createServices(config);
+ const services = createServices(config, (id, working) => {
+ store.dispatch(progressIndicatorActions.TOGGLE({ id, working }));
+ });
const store = configureStore(history, services);
store.subscribe(initListener(history, store, services, config));
store.dispatch(initAuth());
store.dispatch(setCurrentTokenDialogApiHost(apiHost));
- store.dispatch(progressIndicatorActions.START_SUBMIT({ id: ProgressIndicatorData.SIDE_PANEL_PROGRESS }));
const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props} />;
const WorkbenchComponent = (props: any) => <Workbench authService={services.authService} buildInfo={buildInfo} {...props} />;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export type ProgressFn = (id: string, working: boolean) => void;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { OrderBuilder } from "./order-builder";
+import { joinUrls } from "~/services/api/url-builder";
+
+describe("UrlBuilder", () => {
+ it("should join urls properly 1", () => {
+ expect(joinUrls('http://localhost:3000', '/main')).toEqual('http://localhost:3000/main');
+ });
+ it("should join urls properly 2", () => {
+ expect(joinUrls('http://localhost:3000/', '/main')).toEqual('http://localhost:3000/main');
+ });
+ it("should join urls properly 3", () => {
+ expect(joinUrls('http://localhost:3000//', '/main')).toEqual('http://localhost:3000/main');
+ });
+ it("should join urls properly 4", () => {
+ expect(joinUrls('http://localhost:3000', '//main')).toEqual('http://localhost:3000/main');
+ });
+ it("should join urls properly 5", () => {
+ expect(joinUrls('http://localhost:3000///', 'main')).toEqual('http://localhost:3000/main');
+ });
+ it("should join urls properly 6", () => {
+ expect(joinUrls('http://localhost:3000///', '//main')).toEqual('http://localhost:3000/main');
+ });
+ it("should join urls properly 7", () => {
+ expect(joinUrls(undefined, '//main')).toEqual('/main');
+ });
+ it("should join urls properly 8", () => {
+ expect(joinUrls(undefined, 'main')).toEqual('/main');
+ });
+ it("should join urls properly 9", () => {
+ expect(joinUrls('http://localhost:3000///', undefined)).toEqual('http://localhost:3000');
+ });
+});
return this.url + this.query;
}
}
+
+export function joinUrls(url0?: string, url1?: string) {
+ let u0 = "";
+ if (url0) {
+ let idx0 = url0.length - 1;
+ while (url0[idx0] === '/') { --idx0; }
+ u0 = url0.substr(0, idx0 + 1);
+ }
+ let u1 = "";
+ if (url1) {
+ let idx1 = 0;
+ while (url1[idx1] === '/') { ++idx1; }
+ u1 = url1.substr(idx1);
+ }
+ let url = u0;
+ if (u1.length > 0) {
+ url += '/';
+ }
+ url += u1;
+ return url;
+}
import { User } from "~/models/user";
import { AxiosInstance } from "axios";
+import { ProgressFn } from "~/services/api/api-progress";
+import * as uuid from "uuid/v4";
export const API_TOKEN_KEY = 'apiToken';
export const USER_EMAIL_KEY = 'userEmail';
constructor(
protected apiClient: AxiosInstance,
- protected baseUrl: string) { }
+ protected baseUrl: string,
+ protected progressFn: ProgressFn) { }
public saveApiToken(token: string) {
localStorage.setItem(API_TOKEN_KEY, token);
}
public getUserDetails = (): Promise<User> => {
+ const reqId = uuid();
+ this.progressFn(reqId, true);
return this.apiClient
.get<UserDetailsResponse>('/users/current')
- .then(resp => ({
- email: resp.data.email,
- firstName: resp.data.first_name,
- lastName: resp.data.last_name,
- uuid: resp.data.uuid,
- ownerUuid: resp.data.owner_uuid
- }));
+ .then(resp => {
+ this.progressFn(reqId, false);
+ return {
+ email: resp.data.email,
+ firstName: resp.data.first_name,
+ lastName: resp.data.last_name,
+ uuid: resp.data.uuid,
+ ownerUuid: resp.data.owner_uuid
+ };
+ })
+ .catch(e => {
+ this.progressFn(reqId, false);
+ throw e;
+ });
}
public getRootUuid() {
import { parseFilesResponse } from "./collection-service-files-response";
import { fileToArrayBuffer } from "~/common/file";
import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
export class CollectionService extends TrashableResourceService<CollectionResource> {
- constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService) {
- super(serverApi, "collections");
+ constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService, progressFn: ProgressFn) {
+ super(serverApi, "collections", progressFn);
}
async files(uuid: string) {
import axios, { AxiosInstance } from "axios";
import MockAdapter from "axios-mock-adapter";
import { Resource } from "src/models/resource";
+import { ProgressFn } from "~/services/api/api-progress";
-export const mockResourceService = <R extends Resource, C extends CommonResourceService<R>>(Service: new (client: AxiosInstance) => C) => {
+export const mockResourceService = <R extends Resource, C extends CommonResourceService<R>>(
+ Service: new (client: AxiosInstance, progressFn: ProgressFn) => C) => {
const axiosInstance = axios.create();
const axiosMock = new MockAdapter(axiosInstance);
- const service = new Service(axiosInstance);
+ const service = new Service(axiosInstance, (id, working) => {});
Object.keys(service).map(key => service[key] = jest.fn());
return service;
};
describe("CommonResourceService", () => {
const axiosInstance = axios.create();
const axiosMock = new MockAdapter(axiosInstance);
+ const progressFn = (id: string, working: boolean) => {};
beforeEach(() => {
axiosMock.reset();
.onPost("/resource/")
.reply(200, { owner_uuid: "ownerUuidValue" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
const resource = await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
expect(resource).toEqual({ ownerUuid: "ownerUuidValue" });
});
it("#create maps request params to snake case", async () => {
axiosInstance.post = jest.fn(() => Promise.resolve({data: {}}));
- const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
expect(axiosInstance.post).toHaveBeenCalledWith("/resource/", {owner_uuid: "ownerUuidValue"});
});
.onDelete("/resource/uuid")
.reply(200, { deleted_at: "now" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
const resource = await commonResourceService.delete("uuid");
expect(resource).toEqual({ deletedAt: "now" });
});
.onGet("/resource/uuid")
.reply(200, { modified_at: "now" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
const resource = await commonResourceService.get("uuid");
expect(resource).toEqual({ modifiedAt: "now" });
});
items_available: 20
});
- const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
const resource = await commonResourceService.list({ limit: 10, offset: 1 });
expect(resource).toEqual({
kind: "kind",
import * as _ from "lodash";
import { AxiosInstance, AxiosPromise } from "axios";
import { Resource } from "src/models/resource";
+import * as uuid from "uuid/v4";
+import { ProgressFn } from "~/services/api/api-progress";
export interface ListArguments {
limit?: number;
}
}
- static defaultResponse<R>(promise: AxiosPromise<R>): Promise<R> {
+ static defaultResponse<R>(promise: AxiosPromise<R>, progressFn: ProgressFn): Promise<R> {
+ const reqId = uuid();
+ progressFn(reqId, true);
return promise
+ .then(data => {
+ progressFn(reqId, false);
+ return data;
+ })
.then(CommonResourceService.mapResponseKeys)
- .catch(({ response }) => Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response)));
+ .catch(({ response }) => {
+ progressFn(reqId, false);
+ Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response));
+ });
}
protected serverApi: AxiosInstance;
protected resourceType: string;
+ protected progressFn: ProgressFn;
- constructor(serverApi: AxiosInstance, resourceType: string) {
+ constructor(serverApi: AxiosInstance, resourceType: string, onProgress: ProgressFn) {
this.serverApi = serverApi;
this.resourceType = '/' + resourceType + '/';
+ this.progressFn = onProgress;
}
create(data?: Partial<T> | any) {
return CommonResourceService.defaultResponse(
this.serverApi
- .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
+ .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
+ this.progressFn
+ );
}
delete(uuid: string): Promise<T> {
return CommonResourceService.defaultResponse(
this.serverApi
- .delete(this.resourceType + uuid));
+ .delete(this.resourceType + uuid),
+ this.progressFn
+ );
}
get(uuid: string) {
return CommonResourceService.defaultResponse(
this.serverApi
- .get<T>(this.resourceType + uuid));
+ .get<T>(this.resourceType + uuid),
+ this.progressFn
+ );
}
list(args: ListArguments = {}): Promise<ListResults<T>> {
this.serverApi
.get(this.resourceType, {
params: CommonResourceService.mapKeys(_.snakeCase)(params)
- }));
+ }),
+ this.progressFn
+ );
}
update(uuid: string, data: Partial<T>) {
return CommonResourceService.defaultResponse(
this.serverApi
- .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
-
+ .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
+ this.progressFn
+ );
}
}
import { AxiosInstance } from "axios";
import { TrashableResource } from "src/models/resource";
import { CommonResourceService } from "~/services/common-service/common-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
export class TrashableResourceService<T extends TrashableResource> extends CommonResourceService<T> {
- constructor(serverApi: AxiosInstance, resourceType: string) {
- super(serverApi, resourceType);
+ constructor(serverApi: AxiosInstance, resourceType: string, progressFn: ProgressFn) {
+ super(serverApi, resourceType, progressFn);
}
trash(uuid: string): Promise<T> {
- return this.serverApi
- .post(this.resourceType + `${uuid}/trash`)
- .then(CommonResourceService.mapResponseKeys);
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .post(this.resourceType + `${uuid}/trash`),
+ this.progressFn
+ );
}
untrash(uuid: string): Promise<T> {
const params = {
ensure_unique_name: true
};
- return this.serverApi
- .post(this.resourceType + `${uuid}/untrash`, {
- params: CommonResourceService.mapKeys(_.snakeCase)(params)
- })
- .then(CommonResourceService.mapResponseKeys);
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .post(this.resourceType + `${uuid}/untrash`, {
+ params: CommonResourceService.mapKeys(_.snakeCase)(params)
+ }),
+ this.progressFn
+ );
}
}
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { AxiosInstance } from "axios";
-import { ContainerRequestResource } from '../../models/container-request';
+import { ContainerRequestResource } from '~/models/container-request';
+import { ProgressFn } from "~/services/api/api-progress";
export class ContainerRequestService extends CommonResourceService<ContainerRequestResource> {
- constructor(serverApi: AxiosInstance) {
- super(serverApi, "container_requests");
+ constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+ super(serverApi, "container_requests", progressFn);
}
}
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { AxiosInstance } from "axios";
-import { ContainerResource } from '../../models/container';
+import { ContainerResource } from '~/models/container';
+import { ProgressFn } from "~/services/api/api-progress";
export class ContainerService extends CommonResourceService<ContainerResource> {
- constructor(serverApi: AxiosInstance) {
- super(serverApi, "containers");
+ constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+ super(serverApi, "containers", progressFn);
}
}
export class FavoriteService {
constructor(
private linkService: LinkService,
- private groupsService: GroupsService
+ private groupsService: GroupsService,
) {}
create(data: { userUuid: string; resource: { uuid: string; name: string } }) {
items_available: 20
});
- const groupsService = new GroupsService(axios);
+ const groupsService = new GroupsService(axios, (id, working) => {});
const resource = await groupsService.contents("1", { limit: 10, offset: 1 });
expect(resource).toEqual({
kind: "kind",
import { ProcessResource } from "~/models/process";
import { TrashableResource } from "~/models/resource";
import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
export interface ContentsArguments {
limit?: number;
export class GroupsService<T extends TrashableResource = TrashableResource> extends TrashableResourceService<T> {
- constructor(serverApi: AxiosInstance) {
- super(serverApi, "groups");
+ constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+ super(serverApi, "groups", progressFn);
}
contents(uuid: string, args: ContentsArguments = {}): Promise<ListResults<GroupContentsResource>> {
filters: filters ? `[${filters}]` : undefined,
order: order ? order : undefined
};
- return this.serverApi
- .get(this.resourceType + `${uuid}/contents`, {
- params: CommonResourceService.mapKeys(_.snakeCase)(params)
- })
- .then(CommonResourceService.mapResponseKeys);
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .get(this.resourceType + `${uuid}/contents`, {
+ params: CommonResourceService.mapKeys(_.snakeCase)(params)
+ }),
+ this.progressFn
+ );
}
}
import { CommonResourceService } from "~/services/common-service/common-resource-service";\r
import { AxiosInstance } from "axios";\r
import { KeepResource } from "~/models/keep";\r
+import { ProgressFn } from "~/services/api/api-progress";\r
\r
export class KeepService extends CommonResourceService<KeepResource> {\r
- constructor(serverApi: AxiosInstance) {\r
- super(serverApi, "keep_services");\r
+ constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {\r
+ super(serverApi, "keep_services", progressFn);\r
}\r
}\r
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { LinkResource } from "~/models/link";
import { AxiosInstance } from "axios";
+import { ProgressFn } from "~/services/api/api-progress";
export class LinkService extends CommonResourceService<LinkResource> {
- constructor(serverApi: AxiosInstance) {
- super(serverApi, "links");
+ constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+ super(serverApi, "links", progressFn);
}
}
import { AxiosInstance } from "axios";
import { LogResource } from '~/models/log';
import { CommonResourceService } from "~/services/common-service/common-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
export class LogService extends CommonResourceService<LogResource> {
- constructor(serverApi: AxiosInstance) {
- super(serverApi, "logs");
+ constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+ super(serverApi, "logs", progressFn);
}
}
it(`#create has groupClass set to "project"`, async () => {
axiosInstance.post = jest.fn(() => Promise.resolve({ data: {} }));
- const projectService = new ProjectService(axiosInstance);
+ const projectService = new ProjectService(axiosInstance, (id, working) => {});
const resource = await projectService.create({ name: "nameValue" });
expect(axiosInstance.post).toHaveBeenCalledWith("/groups/", {
name: "nameValue",
it("#list has groupClass filter set by default", async () => {
axiosInstance.get = jest.fn(() => Promise.resolve({ data: {} }));
- const projectService = new ProjectService(axiosInstance);
+ const projectService = new ProjectService(axiosInstance, (id, working) => {});
const resource = await projectService.list();
expect(axiosInstance.get).toHaveBeenCalledWith("/groups/", {
params: {
import { TagService } from "./tag-service/tag-service";
import { CollectionFilesService } from "./collection-files-service/collection-files-service";
import { KeepService } from "./keep-service/keep-service";
-import { WebDAV } from "../common/webdav";
-import { Config } from "../common/config";
+import { WebDAV } from "~/common/webdav";
+import { Config } from "~/common/config";
import { UserService } from './user-service/user-service';
import { AncestorService } from "~/services/ancestors-service/ancestors-service";
import { ResourceKind } from "~/models/resource";
export type ServiceRepository = ReturnType<typeof createServices>;
-export const createServices = (config: Config) => {
+export const createServices = (config: Config, progressFn: (id: string, working: boolean) => void) => {
const apiClient = Axios.create();
apiClient.defaults.baseURL = config.baseUrl;
const webdavClient = new WebDAV();
webdavClient.defaults.baseURL = config.keepWebServiceUrl;
- const containerRequestService = new ContainerRequestService(apiClient);
- const containerService = new ContainerService(apiClient);
- const groupsService = new GroupsService(apiClient);
- const keepService = new KeepService(apiClient);
- const linkService = new LinkService(apiClient);
- const logService = new LogService(apiClient);
- const projectService = new ProjectService(apiClient);
- const userService = new UserService(apiClient);
-
+ const containerRequestService = new ContainerRequestService(apiClient, progressFn);
+ const containerService = new ContainerService(apiClient, progressFn);
+ const groupsService = new GroupsService(apiClient, progressFn);
+ const keepService = new KeepService(apiClient, progressFn);
+ const linkService = new LinkService(apiClient, progressFn);
+ const logService = new LogService(apiClient, progressFn);
+ const projectService = new ProjectService(apiClient, progressFn);
+ const userService = new UserService(apiClient, progressFn);
+
const ancestorsService = new AncestorService(groupsService, userService);
- const authService = new AuthService(apiClient, config.rootUrl);
- const collectionService = new CollectionService(apiClient, webdavClient, authService);
+ const authService = new AuthService(apiClient, config.rootUrl, progressFn);
+ const collectionService = new CollectionService(apiClient, webdavClient, authService, progressFn);
const collectionFilesService = new CollectionFilesService(collectionService);
const favoriteService = new FavoriteService(linkService, groupsService);
const tagService = new TagService(linkService);
default:
return undefined;
}
-};
\ No newline at end of file
+};
import { AxiosInstance } from "axios";
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { UserResource } from "~/models/user";
+import { ProgressFn } from "~/services/api/api-progress";
export class UserService extends CommonResourceService<UserResource> {
- constructor(serverApi: AxiosInstance) {
- super(serverApi, "users");
+ constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+ super(serverApi, "users", progressFn);
}
}
describe('auth-actions', () => {
let reducer: (state: AuthState | undefined, action: AuthAction) => any;
let store: RootStore;
+ const progressFn = (id: string, working: boolean) => {};
beforeEach(() => {
- store = configureStore(createBrowserHistory(), createServices(mockConfig({})));
+ store = configureStore(createBrowserHistory(), createServices(mockConfig({}), progressFn));
localStorage.clear();
- reducer = authReducer(createServices(mockConfig({})));
+ reducer = authReducer(createServices(mockConfig({}), progressFn));
});
it('should initialise state with user and api token from local storage', () => {
describe('auth-reducer', () => {
let reducer: (state: AuthState | undefined, action: AuthAction) => any;
+ const progressFn = (id: string, working: boolean) => {};
beforeAll(() => {
localStorage.clear();
- reducer = authReducer(createServices(mockConfig({})));
+ reducer = authReducer(createServices(mockConfig({}), progressFn));
});
it('should correctly initialise state', () => {
import { unionize, ofType, UnionOf } from "~/common/unionize";
export const progressIndicatorActions = unionize({
- START_SUBMIT: ofType<{ id: string }>(),
- STOP_SUBMIT: ofType<{ id: string }>()
+ START: ofType<string>(),
+ STOP: ofType<string>(),
+ TOGGLE: ofType<{ id: string, working: boolean }>()
});
-export type ProgressIndicatorAction = UnionOf<typeof progressIndicatorActions>;
\ No newline at end of file
+export type ProgressIndicatorAction = UnionOf<typeof progressIndicatorActions>;
// SPDX-License-Identifier: AGPL-3.0
import { ProgressIndicatorAction, progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
-import { Dispatch } from 'redux';
-import { RootState } from '~/store/store';
-import { ServiceRepository } from '~/services/services';
export interface ProgressIndicatorState {
- 'sidePanelProgress': { started: boolean };
- 'contentProgress': { started: boolean };
- // 'workbenchProgress': { started: boolean };
+ [key: string]: {
+ working: boolean
+ };
}
const initialState: ProgressIndicatorState = {
- 'sidePanelProgress': { started: false },
- 'contentProgress': { started: false },
- // 'workbenchProgress': { started: false }
};
-export enum ProgressIndicatorData {
- SIDE_PANEL_PROGRESS = 'sidePanelProgress',
- CONTENT_PROGRESS = 'contentProgress',
- // WORKBENCH_PROGRESS = 'workbenchProgress',
-}
-
export const progressIndicatorReducer = (state: ProgressIndicatorState = initialState, action: ProgressIndicatorAction) => {
return progressIndicatorActions.match(action, {
- START_SUBMIT: ({ id }) => ({ ...state, [id]: { started: true } }),
- STOP_SUBMIT: ({ id }) => ({
- ...state,
- [id]: state[id] ? { ...state[id], started: false } : { started: false }
- }),
+ START: id => ({ ...state, [id]: { working: true } }),
+ STOP: id => ({ ...state, [id]: { working: false } }),
+ TOGGLE: ({ id, working }) => ({ ...state, [id]: { working }}),
default: () => state,
});
};
-// export const getProgress = () =>
-// (dispatch: Dispatch, getState: () => RootState) => {
-// const progress = getState().progressIndicator;
-// if (progress.sidePanelProgress.started || progress.contentProgress.started) {
-// dispatch(progressIndicatorActions.START_SUBMIT({ id: ProgressIndicatorData.WORKBENCH_PROGRESS }));
-// } else {
-// dispatch(progressIndicatorActions.STOP_SUBMIT({ id: ProgressIndicatorData.WORKBENCH_PROGRESS }));
-// }
-// };
+export function isSystemWorking(state: ProgressIndicatorState): boolean {
+ return Object.keys(state).reduce((working, k) => working ? true : state[k].working, false);
+}
-// Copyright (C) The Arvados Authors. All rights reserved.
+// // Copyright (C) The Arvados Authors. All rights reserved.
+// //
+// // SPDX-License-Identifier: AGPL-3.0
//
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { connect } from 'react-redux';
-import { RootState } from '~/store/store';
-
-export type WithProgressStateProps = {
- started: boolean;
-};
-
-export const withProgress = (id: string) =>
- (component: React.ComponentType<WithProgressStateProps>) =>
- connect(mapStateToProps(id))(component);
-
-export const mapStateToProps = (id: string) => (state: RootState): WithProgressStateProps => {
- const progress = state.progressIndicator[id];
- return progress;
-};
\ No newline at end of file
+// import * as React from 'react';
+// import { connect } from 'react-redux';
+// import { RootState } from '~/store/store';
+//
+// export type WithProgressStateProps = {
+// started: boolean;
+// };
+//
+// export const withProgress = (id: string) =>
+// (component: React.ComponentType<WithProgressStateProps>) =>
+// connect(mapStateToProps(id))(component);
+//
+// export const mapStateToProps = (id: string) => (state: RootState): WithProgressStateProps => {
+// const progress = state.progressIndicator[id];
+// return progress;
+// };
import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree';
import { ProjectResource } from '~/models/project';
import { progressIndicatorActions } from '../progress-indicator/progress-indicator-actions';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
export enum SidePanelTreeCategory {
PROJECTS = 'Projects',
if (!isSidePanelTreeCategory(nodeId)) {
await dispatch<any>(activateSidePanelTreeProject(nodeId));
}
- dispatch(progressIndicatorActions.STOP_SUBMIT({ id: ProgressIndicatorData.SIDE_PANEL_PROGRESS }));
};
export const activateSidePanelTreeProject = (nodeId: string) =>
import { AccountMenu } from "~/views-components/main-app-bar/account-menu";
import { AnonymousMenu } from "~/views-components/main-app-bar/anonymous-menu";
import { HelpMenu } from './help-menu';
+import { ReactNode } from "react";
type CssRules = 'toolbar' | 'link';
searchDebounce?: number;
user?: User;
buildInfo?: string;
+ children?: ReactNode;
}
export interface MainAppBarActionProps {
export const MainAppBar = withStyles(styles)(
(props: MainAppBarProps) => {
- return <AppBar position="static">
+ return <AppBar position="absolute">
<Toolbar className={props.classes.toolbar}>
<Grid container justify="space-between">
<Grid container item xs={3} direction="column" justify="center">
</Grid>
</Grid>
</Toolbar>
+ {props.children}
</AppBar>;
}
);
-// Copyright (C) The Arvados Authors. All rights reserved.
+// // Copyright (C) The Arvados Authors. All rights reserved.
+// //
+// // SPDX-License-Identifier: AGPL-3.0
//
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { CircularProgress } from '@material-ui/core';
-import { withProgress } from '~/store/progress-indicator/with-progress';
-import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
-
-export const ContentProgress = withProgress(ProgressIndicatorData.CONTENT_PROGRESS)((props: WithProgressStateProps) =>
- props.started ? <CircularProgress /> : null
-);
+// import * as React from 'react';
+// import { CircularProgress } from '@material-ui/core';
+// import { withProgress } from '~/store/progress-indicator/with-progress';
+// import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
+// import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
+//
+// export const ContentProgress = withProgress(ProgressIndicatorData.CONTENT_PROGRESS)((props: WithProgressStateProps) =>
+// props.started ? <CircularProgress /> : null
+// );
-// Copyright (C) The Arvados Authors. All rights reserved.
+// // Copyright (C) The Arvados Authors. All rights reserved.
+// //
+// // SPDX-License-Identifier: AGPL-3.0
//
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { CircularProgress } from '@material-ui/core';
-import { withProgress } from '~/store/progress-indicator/with-progress';
-import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
-
-export const SidePanelProgress = withProgress(ProgressIndicatorData.SIDE_PANEL_PROGRESS)((props: WithProgressStateProps) =>
- props.started ? <span style={{ display: 'flex', justifyContent: 'center', marginTop: "40px" }}><CircularProgress /></span> : null
-);
+// import * as React from 'react';
+// import { CircularProgress } from '@material-ui/core';
+// import { withProgress } from '~/store/progress-indicator/with-progress';
+// import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
+// import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
+//
+// export const SidePanelProgress = withProgress(ProgressIndicatorData.SIDE_PANEL_PROGRESS)((props: WithProgressStateProps) =>
+// props.started ? <span style={{ display: 'flex', justifyContent: 'center', marginTop: "40px" }}><CircularProgress /></span> : null
+// );
import { Grid } from '@material-ui/core';
import { SidePanelButton } from '~/views-components/side-panel-button/side-panel-button';
import { RootState } from '~/store/store';
-import { SidePanelProgress } from '~/views-components/progress/side-panel-progress';
const DRAWER_WITDH = 240;
});
const mapStateToProps = (state: RootState) => ({
- sidePanelProgress: state.progressIndicator.sidePanelProgress.started
});
export const SidePanel = compose(
)(({ classes, ...props }: WithStyles<CssRules> & SidePanelTreeProps) =>
<Grid item xs>
<SidePanelButton />
- {props.sidePanelProgress ? <SidePanelProgress /> : <SidePanelTree {...props} />}
- </Grid>);
\ No newline at end of file
+ <SidePanelTree {...props} />
+ </Grid>);
import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
import { Grid, LinearProgress } from '@material-ui/core';
import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
+import { isSystemWorking } from "~/store/progress-indicator/progress-indicator-reducer";
type CssRules = 'root' | 'asidePanel' | 'contentWrapper' | 'content' | 'appBar';
root: {
overflow: 'hidden',
width: '100vw',
- height: '100vh'
+ height: '100vh',
+ paddingTop: theme.spacing.unit * 8
},
asidePanel: {
maxWidth: '240px',
interface WorkbenchDataProps {
user?: User;
currentToken?: string;
- loadingSidePanel: boolean;
- loadingContent: boolean;
+ working: boolean;
}
interface WorkbenchGeneralProps {
(state: RootState) => ({
user: state.auth.user,
currentToken: state.auth.apiToken,
- loadingSidePanel: state.progressIndicator.sidePanelProgress.started,
- loadingContent: state.progressIndicator.contentProgress.started
+ working: isSystemWorking(state.progressIndicator)
})
)(
class extends React.Component<WorkbenchProps, WorkbenchState> {
render() {
const { classes } = this.props;
return <>
+ <MainAppBar
+ searchText={this.state.searchText}
+ user={this.props.user}
+ onSearch={this.onSearch}
+ buildInfo={this.props.buildInfo}>
+ {this.props.working ? <LinearProgress color="secondary" /> : null}
+ </MainAppBar>
<Grid container direction="column" className={classes.root}>
- <Grid className={classes.appBar}>
- <MainAppBar
- searchText={this.state.searchText}
- user={this.props.user}
- onSearch={this.onSearch}
- buildInfo={this.props.buildInfo} />
- </Grid>
- {this.props.loadingContent || this.props.loadingSidePanel ? <LinearProgress color="secondary" /> : null}
{this.props.user &&
<Grid container item xs alignItems="stretch" wrap="nowrap">
<Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
"no-shadowed-variable": false,
"semicolon": true,
"array-type": false,
- "interface-over-type-literal": false
+ "interface-over-type-literal": false,
+ "no-empty": false
},
"linterOptions": {
"exclude": [
"@types/react" "*"
redux "^3.6.0 || ^4.0.0"
+"@types/uuid@3.4.4":
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5"
+ dependencies:
+ "@types/node" "*"
+
abab@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+uuid@3.3.2, uuid@^3.1.0:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
+
uuid@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
-uuid@^3.1.0:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
-
validate-npm-package-license@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338"