[ARVADOS-WORKBENCH2] created: 1.2.0-126-g540750a
Git user
git at public.curoverse.com
Wed Aug 22 17:12:55 EDT 2018
at 540750a7749cb71ea0a8fde4b7a3689eeaa1c3dd (commit)
commit 540750a7749cb71ea0a8fde4b7a3689eeaa1c3dd
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date: Wed Aug 22 23:12:36 2018 +0200
Extract major components from workbench
Feature #14102
Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
diff --git a/src/store/collection-panel/collection-panel-action.ts b/src/store/collection-panel/collection-panel-action.ts
index 06d4d27..d8ad6d0 100644
--- a/src/store/collection-panel/collection-panel-action.ts
+++ b/src/store/collection-panel/collection-panel-action.ts
@@ -2,16 +2,17 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { unionize, ofType, UnionOf } from "unionize";
import { Dispatch } from "redux";
import { loadCollectionFiles } from "./collection-panel-files/collection-panel-files-actions";
-import { CollectionResource } from "~/models/collection";
+import { CollectionResource } from '~/models/collection';
import { collectionPanelFilesAction } from "./collection-panel-files/collection-panel-files-actions";
import { createTree } from "~/models/tree";
import { RootState } from "../store";
import { ServiceRepository } from "~/services/services";
import { TagResource, TagProperty } from "~/models/tag";
import { snackbarActions } from "../snackbar/snackbar-actions";
+import { resourcesActions } from "~/store/resources/resources-actions";
+import { unionize, ofType, UnionOf } from '~/common/unionize';
export const collectionPanelActions = unionize({
LOAD_COLLECTION: ofType<{ uuid: string }>(),
@@ -22,22 +23,20 @@ export const collectionPanelActions = unionize({
CREATE_COLLECTION_TAG_SUCCESS: ofType<{ tag: TagResource }>(),
DELETE_COLLECTION_TAG: ofType<{ uuid: string }>(),
DELETE_COLLECTION_TAG_SUCCESS: ofType<{ uuid: string }>()
-}, { tag: 'type', value: 'payload' });
+});
export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
export const COLLECTION_TAG_FORM_NAME = 'collectionTagForm';
export const loadCollection = (uuid: string) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid }));
dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() }));
- return services.collectionService
- .get(uuid)
- .then(item => {
- dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item }));
- dispatch<any>(loadCollectionFiles(uuid));
- });
+ const collection = await services.collectionService.get(uuid);
+ dispatch(resourcesActions.SET_RESOURCES([collection]));
+ dispatch<any>(loadCollectionFiles(collection.uuid));
+ dispatch<any>(loadCollectionTags(collection.uuid));
};
export const loadCollectionTags = (uuid: string) =>
@@ -50,7 +49,6 @@ export const loadCollectionTags = (uuid: string) =>
});
};
-
export const createCollectionTag = (data: TagProperty) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(collectionPanelActions.CREATE_COLLECTION_TAG({ data }));
diff --git a/src/store/collections/updater/collection-updater-action.ts b/src/store/collections/updater/collection-updater-action.ts
index 2f520d4..1ca1a83 100644
--- a/src/store/collections/updater/collection-updater-action.ts
+++ b/src/store/collections/updater/collection-updater-action.ts
@@ -2,27 +2,22 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { default as unionize, ofType, UnionOf } from "unionize";
import { Dispatch } from "redux";
-
+import { unionize, ofType, UnionOf } from '~/common/unionize';
import { RootState } from "../../store";
import { ServiceRepository } from "~/services/services";
import { CollectionResource } from '~/models/collection';
import { initialize } from 'redux-form';
import { collectionPanelActions } from "../../collection-panel/collection-panel-action";
import { ContextMenuResource } from "../../context-menu/context-menu-reducer";
-import { updateDetails } from "~/store/details-panel/details-panel-action";
+import { resourcesActions } from "~/store/resources/resources-actions";
export const collectionUpdaterActions = unionize({
OPEN_COLLECTION_UPDATER: ofType<{ uuid: string }>(),
CLOSE_COLLECTION_UPDATER: ofType<{}>(),
UPDATE_COLLECTION_SUCCESS: ofType<{}>(),
-}, {
- tag: 'type',
- value: 'payload'
});
-
export const COLLECTION_FORM_NAME = 'collectionEditDialog';
export const openUpdater = (item: ContextMenuResource) =>
@@ -39,11 +34,10 @@ export const updateCollection = (collection: Partial<CollectionResource>) =>
return services.collectionService
.update(uuid, collection)
.then(collection => {
- dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection as CollectionResource }));
- dispatch(collectionUpdaterActions.UPDATE_COLLECTION_SUCCESS());
- dispatch<any>(updateDetails(collection));
- }
- );
+ dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection as CollectionResource }));
+ dispatch(collectionUpdaterActions.UPDATE_COLLECTION_SUCCESS());
+ dispatch(resourcesActions.SET_RESOURCES([collection]));
+ });
};
export type CollectionUpdaterAction = UnionOf<typeof collectionUpdaterActions>;
diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts
index 8e5eb1e..b517503 100644
--- a/src/store/context-menu/context-menu-actions.ts
+++ b/src/store/context-menu/context-menu-actions.ts
@@ -2,15 +2,25 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { default as unionize, ofType, UnionOf } from "unionize";
+import { unionize, ofType, UnionOf } from '~/common/unionize';
import { ContextMenuPosition, ContextMenuResource } from "./context-menu-reducer";
+import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+import { Dispatch } from 'redux';
export const contextMenuActions = unionize({
OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
CLOSE_CONTEXT_MENU: ofType<{}>()
-}, {
- tag: 'type',
- value: 'payload'
- });
+});
export type ContextMenuAction = UnionOf<typeof contextMenuActions>;
+
+export const openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: { name: string; uuid: string; description?: string; kind: ContextMenuKind; }) =>
+ (dispatch: Dispatch) => {
+ event.preventDefault();
+ dispatch(
+ contextMenuActions.OPEN_CONTEXT_MENU({
+ position: { x: event.clientX, y: event.clientY },
+ resource
+ })
+ );
+ };
\ No newline at end of file
diff --git a/src/store/details-panel/details-panel-action.ts b/src/store/details-panel/details-panel-action.ts
index b8021fb..2724a3e 100644
--- a/src/store/details-panel/details-panel-action.ts
+++ b/src/store/details-panel/details-panel-action.ts
@@ -2,48 +2,17 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { unionize, ofType, UnionOf } from "unionize";
-import { Dispatch } from "redux";
-import { Resource, ResourceKind } from "~/models/resource";
-import { RootState } from "../store";
-import { ServiceRepository } from "~/services/services";
+import { unionize, ofType, UnionOf } from '~/common/unionize';
export const detailsPanelActions = unionize({
TOGGLE_DETAILS_PANEL: ofType<{}>(),
- LOAD_DETAILS: ofType<{ uuid: string, kind: ResourceKind }>(),
- LOAD_DETAILS_SUCCESS: ofType<{ item: Resource }>(),
- UPDATE_DETAILS: ofType<{ item: Resource }>()
-}, { tag: 'type', value: 'payload' });
+ LOAD_DETAILS_PANEL: ofType<string>()
+});
export type DetailsPanelAction = UnionOf<typeof detailsPanelActions>;
-export const loadDetails = (uuid: string, kind: ResourceKind) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch(detailsPanelActions.LOAD_DETAILS({ uuid, kind }));
- const item = await getService(services, kind).get(uuid);
- dispatch(detailsPanelActions.LOAD_DETAILS_SUCCESS({ item }));
- };
-
-export const updateDetails = (item: Resource) =>
- async (dispatch: Dispatch, getState: () => RootState) => {
- const currentItem = getState().detailsPanel.item;
- if (currentItem && (currentItem.uuid === item.uuid)) {
- dispatch(detailsPanelActions.UPDATE_DETAILS({ item }));
- dispatch(detailsPanelActions.LOAD_DETAILS_SUCCESS({ item }));
- }
- };
-
-
-const getService = (services: ServiceRepository, kind: ResourceKind) => {
- switch (kind) {
- case ResourceKind.PROJECT:
- return services.projectService;
- case ResourceKind.COLLECTION:
- return services.collectionService;
- default:
- return services.projectService;
- }
-};
+export const loadDetailsPanel = (uuid: string) => detailsPanelActions.LOAD_DETAILS_PANEL(uuid);
+
diff --git a/src/store/details-panel/details-panel-reducer.ts b/src/store/details-panel/details-panel-reducer.ts
index f22add3..091b2fa 100644
--- a/src/store/details-panel/details-panel-reducer.ts
+++ b/src/store/details-panel/details-panel-reducer.ts
@@ -3,21 +3,20 @@
// SPDX-License-Identifier: AGPL-3.0
import { detailsPanelActions, DetailsPanelAction } from "./details-panel-action";
-import { Resource } from "~/models/resource";
export interface DetailsPanelState {
- item: Resource | null;
+ resourceUuid: string;
isOpened: boolean;
}
const initialState = {
- item: null,
+ resourceUuid: '',
isOpened: false
};
export const detailsPanelReducer = (state: DetailsPanelState = initialState, action: DetailsPanelAction) =>
detailsPanelActions.match(action, {
default: () => state,
- LOAD_DETAILS_SUCCESS: ({ item }) => ({ ...state, item }),
+ LOAD_DETAILS_PANEL: resourceUuid => ({ ...state, resourceUuid }),
TOGGLE_DETAILS_PANEL: () => ({ ...state, isOpened: !state.isOpened })
});
diff --git a/src/store/favorite-panel/favorite-panel-action.ts b/src/store/favorite-panel/favorite-panel-action.ts
index aa1ec8d..067d5ce 100644
--- a/src/store/favorite-panel/favorite-panel-action.ts
+++ b/src/store/favorite-panel/favorite-panel-action.ts
@@ -6,3 +6,5 @@ import { bindDataExplorerActions } from "../data-explorer/data-explorer-action";
export const FAVORITE_PANEL_ID = "favoritePanel";
export const favoritePanelActions = bindDataExplorerActions(FAVORITE_PANEL_ID);
+
+export const loadFavoritePanel = () => favoritePanelActions.REQUEST_ITEMS();
diff --git a/src/store/favorite-panel/favorite-panel-middleware-service.ts b/src/store/favorite-panel/favorite-panel-middleware-service.ts
index 1c2f062..e4be32d 100644
--- a/src/store/favorite-panel/favorite-panel-middleware-service.ts
+++ b/src/store/favorite-panel/favorite-panel-middleware-service.ts
@@ -6,7 +6,6 @@ import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-mi
import { FavoritePanelColumnNames, FavoritePanelFilter } from "~/views/favorite-panel/favorite-panel";
import { RootState } from "../store";
import { DataColumns } from "~/components/data-table/data-table";
-import { FavoritePanelItem, resourceToDataItem } from "~/views/favorite-panel/favorite-panel-item";
import { ServiceRepository } from "~/services/services";
import { SortDirection } from "~/components/data-table/data-column";
import { FilterBuilder } from "~/common/api/filter-builder";
@@ -16,6 +15,7 @@ import { Dispatch, MiddlewareAPI } from "redux";
import { OrderBuilder, OrderDirection } from "~/common/api/order-builder";
import { LinkResource } from "~/models/link";
import { GroupContentsResource, GroupContentsResourcePrefix } from "~/services/groups-service/groups-service";
+import { resourcesActions } from "~/store/resources/resources-actions";
export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareService {
constructor(private services: ServiceRepository, id: string) {
@@ -24,7 +24,7 @@ export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareServic
requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
const dataExplorer = api.getState().dataExplorer[this.getId()];
- const columns = dataExplorer.columns as DataColumns<FavoritePanelItem, FavoritePanelFilter>;
+ const columns = dataExplorer.columns as DataColumns<string, FavoritePanelFilter>;
const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
const typeFilters = this.getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
@@ -55,8 +55,9 @@ export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareServic
.getFilters()
})
.then(response => {
+ api.dispatch(resourcesActions.SET_RESOURCES(response.items));
api.dispatch(favoritePanelActions.SET_ITEMS({
- items: response.items.map(resourceToDataItem),
+ items: response.items.map(resource => resource.uuid),
itemsAvailable: response.itemsAvailable,
page: Math.floor(response.offset / response.limit),
rowsPerPage: response.limit
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index 50ec93d..d440f19 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -8,7 +8,7 @@ import { push } from "react-router-redux";
import { TreeItemStatus } from "~/components/tree/tree";
import { findTreeItem } from "../project/project-reducer";
import { RootState } from "../store";
-import { ResourceKind } from "~/models/resource";
+import { ResourceKind, Resource } from '~/models/resource';
import { projectPanelActions } from "../project-panel/project-panel-action";
import { getCollectionUrl } from "~/models/collection";
import { getProjectUrl, ProjectResource } from "~/models/project";
@@ -17,6 +17,12 @@ import { ServiceRepository } from "~/services/services";
import { sidePanelActions } from "../side-panel/side-panel-action";
import { SidePanelId } from "../side-panel/side-panel-reducer";
import { getUuidObjectType, ObjectTypes } from "~/models/object-types";
+import { getResource } from '~/store/resources/resources';
+import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { loadCollection } from '~/store/collection-panel/collection-panel-action';
+import { GroupContentsResource } from "~/services/groups-service/groups-service";
+import { snackbarActions } from '../snackbar/snackbar-actions';
+import { resourceLabel } from "~/common/labels";
export const getResourceUrl = (resourceKind: ResourceKind, resourceUuid: string): string => {
switch (resourceKind) {
@@ -98,3 +104,47 @@ const loadBranch = async (uuids: string[], dispatch: Dispatch): Promise<any> =>
return loadBranch(rest, dispatch);
}
};
+
+export const navigateToResource = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState) => {
+ const resource = getResource(uuid)(getState().resources);
+ resource
+ ? dispatch<any>(getResourceNavigationAction(resource))
+ : dispatch<any>(resourceIsNotLoaded(uuid));
+ };
+
+const getResourceNavigationAction = (resource: Resource) => {
+ switch (resource.kind) {
+ case ResourceKind.COLLECTION:
+ return navigateToCollection(resource);
+ case ResourceKind.PROJECT:
+ return navigateToProject(resource);
+ default:
+ return cannotNavigateToResource(resource);
+ }
+};
+
+export const navigateToProject = ({ uuid }: Resource) =>
+ (dispatch: Dispatch) => {
+ dispatch<any>(setProjectItem(uuid, ItemMode.BOTH));
+ dispatch(loadDetailsPanel(uuid));
+ };
+
+export const navigateToCollection = ({ uuid }: Resource) =>
+ (dispatch: Dispatch) => {
+ dispatch<any>(loadCollection(uuid));
+ dispatch(push(getCollectionUrl(uuid)));
+ };
+
+export const cannotNavigateToResource = ({ kind, uuid }: Resource) =>
+ snackbarActions.OPEN_SNACKBAR({
+ message: `${resourceLabel(kind)} identified by ${uuid} cannot be opened.`,
+ hideDuration: 3000
+ });
+
+
+export const resourceIsNotLoaded = (uuid: string) =>
+ snackbarActions.OPEN_SNACKBAR({
+ message: `Resource identified by ${uuid} is not loaded.`,
+ hideDuration: 3000
+ });
diff --git a/src/store/project-panel/project-panel-middleware-service.ts b/src/store/project-panel/project-panel-middleware-service.ts
index 663add3..0196ed4 100644
--- a/src/store/project-panel/project-panel-middleware-service.ts
+++ b/src/store/project-panel/project-panel-middleware-service.ts
@@ -7,7 +7,6 @@ import { ProjectPanelColumnNames, ProjectPanelFilter } from "~/views/project-pan
import { RootState } from "../store";
import { DataColumns } from "~/components/data-table/data-table";
import { ServiceRepository } from "~/services/services";
-import { ProjectPanelItem, resourceToDataItem } from "~/views/project-panel/project-panel-item";
import { SortDirection } from "~/components/data-table/data-column";
import { OrderBuilder, OrderDirection } from "~/common/api/order-builder";
import { FilterBuilder } from "~/common/api/filter-builder";
@@ -16,6 +15,7 @@ import { checkPresenceInFavorites } from "../favorites/favorites-actions";
import { projectPanelActions } from "./project-panel-action";
import { Dispatch, MiddlewareAPI } from "redux";
import { ProjectResource } from "~/models/project";
+import { resourcesActions } from "~/store/resources/resources-actions";
export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
constructor(private services: ServiceRepository, id: string) {
@@ -25,7 +25,7 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService
requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
const state = api.getState();
const dataExplorer = state.dataExplorer[this.getId()];
- const columns = dataExplorer.columns as DataColumns<ProjectPanelItem, ProjectPanelFilter>;
+ const columns = dataExplorer.columns as DataColumns<string, ProjectPanelFilter>;
const typeFilters = this.getColumnFilters(columns, ProjectPanelColumnNames.TYPE);
const statusFilters = this.getColumnFilters(columns, ProjectPanelColumnNames.STATUS);
const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
@@ -58,8 +58,9 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService
.getFilters()
})
.then(response => {
+ api.dispatch(resourcesActions.SET_RESOURCES(response.items));
api.dispatch(projectPanelActions.SET_ITEMS({
- items: response.items.map(resourceToDataItem),
+ items: response.items.map(resource => resource.uuid),
itemsAvailable: response.itemsAvailable,
page: Math.floor(response.offset / response.limit),
rowsPerPage: response.limit
diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts
index 2017658..da58ed2 100644
--- a/src/store/project/project-action.ts
+++ b/src/store/project/project-action.ts
@@ -1,8 +1,8 @@
// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0
-import { default as unionize, ofType, UnionOf } from "unionize";
+import { unionize, ofType, UnionOf } from '~/common/unionize';
import { ProjectResource } from "~/models/project";
import { Dispatch } from "redux";
import { FilterBuilder } from "~/common/api/filter-builder";
@@ -10,14 +10,15 @@ import { RootState } from "../store";
import { checkPresenceInFavorites } from "../favorites/favorites-actions";
import { ServiceRepository } from "~/services/services";
import { projectPanelActions } from "~/store/project-panel/project-panel-action";
-import { updateDetails } from "~/store/details-panel/details-panel-action";
+import { resourcesActions } from "~/store/resources/resources-actions";
+import { reset } from 'redux-form';
export const projectActions = unionize({
OPEN_PROJECT_CREATOR: ofType<{ ownerUuid: string }>(),
CLOSE_PROJECT_CREATOR: ofType<{}>(),
CREATE_PROJECT: ofType<Partial<ProjectResource>>(),
CREATE_PROJECT_SUCCESS: ofType<ProjectResource>(),
- OPEN_PROJECT_UPDATER: ofType<{ uuid: string}>(),
+ OPEN_PROJECT_UPDATER: ofType<{ uuid: string }>(),
CLOSE_PROJECT_UPDATER: ofType<{}>(),
UPDATE_PROJECT_SUCCESS: ofType<ProjectResource>(),
REMOVE_PROJECT: ofType<string>(),
@@ -26,14 +27,11 @@ export const projectActions = unionize({
TOGGLE_PROJECT_TREE_ITEM_OPEN: ofType<string>(),
TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ofType<string>(),
RESET_PROJECT_TREE_ACTIVITY: ofType<string>()
-}, {
- tag: 'type',
- value: 'payload'
});
export const PROJECT_FORM_NAME = 'projectEditDialog';
-export const getProjectList = (parentUuid: string = '') =>
+export const getProjectList = (parentUuid: string = '') =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(projectActions.PROJECTS_REQUEST(parentUuid));
return services.projectService.list({
@@ -66,8 +64,16 @@ export const updateProject = (project: Partial<ProjectResource>) =>
dispatch(projectActions.UPDATE_PROJECT_SUCCESS(project));
dispatch(projectPanelActions.REQUEST_ITEMS());
dispatch<any>(getProjectList(project.ownerUuid));
- dispatch<any>(updateDetails(project));
+ dispatch(resourcesActions.SET_RESOURCES([project]));
});
};
+export const PROJECT_CREATE_DIALOG = "projectCreateDialog";
+
+export const openProjectCreator = (ownerUuid: string) =>
+ (dispatch: Dispatch) => {
+ dispatch(reset(PROJECT_CREATE_DIALOG));
+ dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid }));
+ };
+
export type ProjectAction = UnionOf<typeof projectActions>;
diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts
index 01e6004..7f83fb2 100644
--- a/src/views-components/context-menu/action-sets/project-action-set.ts
+++ b/src/views-components/context-menu/action-sets/project-action-set.ts
@@ -2,15 +2,13 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { reset, initialize } from "redux-form";
-
+import { initialize } from "redux-form";
import { ContextMenuActionSet } from "../context-menu-action-set";
-import { projectActions, PROJECT_FORM_NAME } from "~/store/project/project-action";
+import { projectActions, PROJECT_FORM_NAME, openProjectCreator } from '~/store/project/project-action';
import { NewProjectIcon, RenameIcon, CopyIcon, MoveToIcon } from "~/components/icon/icon";
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "~/store/favorites/favorites-actions";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
-import { PROJECT_CREATE_DIALOG } from "../../dialog-create/dialog-project-create";
import { openMoveProjectDialog } from '~/store/move-project-dialog/move-project-dialog';
import { openProjectCopyDialog } from "~/views-components/project-copy-dialog/project-copy-dialog";
@@ -18,10 +16,7 @@ export const projectActionSet: ContextMenuActionSet = [[
{
icon: NewProjectIcon,
name: "New project",
- execute: (dispatch, resource) => {
- dispatch(reset(PROJECT_CREATE_DIALOG));
- dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
- }
+ execute: (dispatch, resource) => dispatch<any>(openProjectCreator(resource.uuid))
},
{
icon: RenameIcon,
diff --git a/src/views-components/context-menu/action-sets/root-project-action-set.ts b/src/views-components/context-menu/action-sets/root-project-action-set.ts
index de3b954..eb4a9a3 100644
--- a/src/views-components/context-menu/action-sets/root-project-action-set.ts
+++ b/src/views-components/context-menu/action-sets/root-project-action-set.ts
@@ -5,9 +5,8 @@
import { reset } from "redux-form";
import { ContextMenuActionSet } from "../context-menu-action-set";
-import { projectActions } from "~/store/project/project-action";
+import { openProjectCreator } from "~/store/project/project-action";
import { collectionCreateActions } from "~/store/collections/creator/collection-creator-action";
-import { PROJECT_CREATE_DIALOG } from "../../dialog-create/dialog-project-create";
import { COLLECTION_CREATE_DIALOG } from "../../dialog-create/dialog-collection-create";
import { NewProjectIcon, CollectionIcon } from "~/components/icon/icon";
@@ -15,10 +14,7 @@ export const rootProjectActionSet: ContextMenuActionSet = [[
{
icon: NewProjectIcon,
name: "New project",
- execute: (dispatch, resource) => {
- dispatch(reset(PROJECT_CREATE_DIALOG));
- dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
- }
+ execute: (dispatch, resource) => dispatch<any>(openProjectCreator(resource.uuid))
},
{
icon: CollectionIcon,
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index 1b07642..abf1839 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -9,9 +9,14 @@ import { ResourceKind } from '~/models/resource';
import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '~/components/icon/icon';
import { formatDate, formatFileSize } from '~/common/formatters';
import { resourceLabel } from '~/common/labels';
+import { connect } from 'react-redux';
+import { RootState } from '~/store/store';
+import { getResource } from '../../store/resources/resources';
+import { GroupContentsResource } from '~/services/groups-service/groups-service';
+import { ProcessResource } from '~/models/process';
-export const renderName = (item: {name: string; uuid: string, kind: string}) =>
+export const renderName = (item: { name: string; uuid: string, kind: string }) =>
<Grid container alignItems="center" wrap="nowrap" spacing={16}>
<Grid item>
{renderIcon(item)}
@@ -28,8 +33,13 @@ export const renderName = (item: {name: string; uuid: string, kind: string}) =>
</Grid>
</Grid>;
+export const ResourceName = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined;
+ return resource || { name: '', uuid: '', kind: '' };
+ })(renderName);
-export const renderIcon = (item: {kind: string}) => {
+export const renderIcon = (item: { kind: string }) => {
switch (item.kind) {
case ResourceKind.PROJECT:
return <ProjectIcon />;
@@ -46,22 +56,52 @@ export const renderDate = (date: string) => {
return <Typography noWrap>{formatDate(date)}</Typography>;
};
+export const ResourceLastModifiedDate = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined;
+ return { date: resource ? resource.modifiedAt : '' };
+ })((props: { date: string }) => renderDate(props.date));
+
export const renderFileSize = (fileSize?: number) =>
<Typography noWrap>
{formatFileSize(fileSize)}
</Typography>;
+export const ResourceFileSize = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined;
+ return {};
+ })((props: { fileSize?: number }) => renderFileSize(props.fileSize));
+
export const renderOwner = (owner: string) =>
<Typography noWrap color="primary" >
{owner}
</Typography>;
+export const ResourceOwner = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined;
+ return { owner: resource ? resource.ownerUuid : '' };
+ })((props: { owner: string }) => renderOwner(props.owner));
+
export const renderType = (type: string) =>
<Typography noWrap>
{resourceLabel(type)}
</Typography>;
-export const renderStatus = (item: {status?: string}) =>
+export const ResourceType = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource(props.uuid)(state.resources) as GroupContentsResource | undefined;
+ return { type: resource ? resource.kind : '' };
+ })((props: { type: string }) => renderType(props.type));
+
+export const renderStatus = (item: { status?: string }) =>
<Typography noWrap align="center" >
{item.status || "-"}
</Typography>;
+
+export const ProcessStatus = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource(props.uuid)(state.resources) as ProcessResource | undefined;
+ return { status: resource ? resource.state : '-' };
+ })((props: { status: string }) => renderType(props.status));
diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx
index 21d57ae..7aae786 100644
--- a/src/views-components/details-panel/details-panel.tsx
+++ b/src/views-components/details-panel/details-panel.tsx
@@ -20,6 +20,7 @@ import { ProcessDetails } from "./process-details";
import { EmptyDetails } from "./empty-details";
import { DetailsData } from "./details-data";
import { DetailsResource } from "~/models/details";
+import { getResource } from '../../store/resources/resources';
type CssRules = 'drawerPaper' | 'container' | 'opened' | 'headerContainer' | 'headerIcon' | 'headerTitle' | 'tabContainer';
@@ -70,10 +71,13 @@ const getItem = (resource: DetailsResource): DetailsData => {
}
};
-const mapStateToProps = ({ detailsPanel }: RootState) => ({
- isOpened: detailsPanel.isOpened,
- item: getItem(detailsPanel.item as DetailsResource)
-});
+const mapStateToProps = ({ detailsPanel, resources }: RootState) => {
+ const resource = getResource(detailsPanel.resourceUuid)(resources) as DetailsResource;
+ return {
+ isOpened: detailsPanel.isOpened,
+ item: getItem(resource)
+ };
+};
const mapDispatchToProps = (dispatch: Dispatch) => ({
onCloseDrawer: () => {
@@ -110,7 +114,7 @@ export const DetailsPanel = withStyles(styles)(
const { tabsValue } = this.state;
return (
<Typography component="div"
- className={classnames([classes.container, { [classes.opened]: isOpened }])}>
+ className={classnames([classes.container, { [classes.opened]: isOpened }])}>
<Drawer variant="permanent" anchor="right" classes={{ paper: classes.drawerPaper }}>
<Typography component="div" className={classes.headerContainer}>
<Grid container alignItems='center' justify='space-around'>
@@ -124,14 +128,14 @@ export const DetailsPanel = withStyles(styles)(
</Grid>
<Grid item>
<IconButton color="inherit" onClick={onCloseDrawer}>
- {<CloseIcon/>}
+ {<CloseIcon />}
</IconButton>
</Grid>
</Grid>
</Typography>
<Tabs value={tabsValue} onChange={this.handleChange}>
- <Tab disableRipple label="Details"/>
- <Tab disableRipple label="Activity" disabled/>
+ <Tab disableRipple label="Details" />
+ <Tab disableRipple label="Activity" disabled />
</Tabs>
{tabsValue === 0 && this.renderTabContainer(
<Grid container direction="column">
@@ -139,7 +143,7 @@ export const DetailsPanel = withStyles(styles)(
</Grid>
)}
{tabsValue === 1 && this.renderTabContainer(
- <Grid container direction="column"/>
+ <Grid container direction="column" />
)}
</Drawer>
</Typography>
diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-project-create.tsx
index e77114b..d32582e 100644
--- a/src/views-components/dialog-create/dialog-project-create.tsx
+++ b/src/views-components/dialog-create/dialog-project-create.tsx
@@ -10,6 +10,7 @@ import { Dialog, DialogActions, DialogContent, DialogTitle } from '@material-ui/
import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core';
import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '~/validators/validators';
+import { PROJECT_CREATE_DIALOG } from '~/store/project/project-action';
type CssRules = "button" | "lastButton" | "formContainer" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions";
@@ -52,8 +53,6 @@ interface DialogProjectProps {
pristine: boolean;
}
-export const PROJECT_CREATE_DIALOG = "projectCreateDialog";
-
export const DialogProjectCreate = compose(
reduxForm({ form: PROJECT_CREATE_DIALOG }),
withStyles(styles))(
diff --git a/src/views-components/navigation-panel/navigation-panel.tsx b/src/views-components/navigation-panel/navigation-panel.tsx
new file mode 100644
index 0000000..283e9be
--- /dev/null
+++ b/src/views-components/navigation-panel/navigation-panel.tsx
@@ -0,0 +1,111 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import Drawer from '@material-ui/core/Drawer';
+import { connect } from "react-redux";
+import { ProjectTree } from '~/views-components/project-tree/project-tree';
+import { SidePanel, SidePanelItem } from '~/components/side-panel/side-panel';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { RootState } from '~/store/store';
+import { TreeItem } from '~/components/tree/tree';
+import { ProjectResource } from '~/models/project';
+import { sidePanelActions } from '../../store/side-panel/side-panel-action';
+import { Dispatch } from 'redux';
+import { projectActions } from '~/store/project/project-action';
+import { navigateToResource } from '../../store/navigation/navigation-action';
+import { openContextMenu } from '~/store/context-menu/context-menu-actions';
+import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+
+
+const DRAWER_WITDH = 240;
+
+type CssRules = 'drawerPaper' | 'toolbar';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ drawerPaper: {
+ position: 'relative',
+ width: DRAWER_WITDH,
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ toolbar: theme.mixins.toolbar
+});
+
+interface NavigationPanelDataProps {
+ projects: Array<TreeItem<ProjectResource>>;
+ sidePanelItems: SidePanelItem[];
+}
+
+interface NavigationPanelActionProps {
+ toggleSidePanelOpen: (panelItemId: string) => void;
+ toggleSidePanelActive: (panelItemId: string) => void;
+ toggleProjectOpen: (projectUuid: string) => void;
+ toggleProjectActive: (projectUuid: string) => void;
+ openRootContextMenu: (event: React.MouseEvent<any>) => void;
+ openProjectContextMenu: (event: React.MouseEvent<any>, item: TreeItem<ProjectResource>) => void;
+}
+
+type NavigationPanelProps = NavigationPanelDataProps & NavigationPanelActionProps & WithStyles<CssRules>;
+
+const mapStateToProps = (state: RootState): NavigationPanelDataProps => ({
+ projects: state.projects.items,
+ sidePanelItems: state.sidePanel
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): NavigationPanelActionProps => ({
+ toggleSidePanelOpen: panelItemId => {
+ dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(panelItemId));
+ },
+ toggleSidePanelActive: panelItemId => {
+ dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(panelItemId));
+
+ // const panelItem = this.props.sidePanelItems.find(it => it.id === itemId);
+ // if (panelItem && panelItem.activeAction) {
+ // panelItem.activeAction(this.props.dispatch, this.props.authService.getUuid());
+ // }
+ },
+ toggleProjectOpen: projectUuid => {
+ dispatch<any>(navigateToResource(projectUuid));
+ },
+ toggleProjectActive: projectUuid => {
+ dispatch<any>(navigateToResource(projectUuid));
+ },
+ openRootContextMenu: event => {
+ dispatch<any>(openContextMenu(event, {
+ uuid: "",
+ name: "",
+ kind: ContextMenuKind.ROOT_PROJECT
+ }));
+ },
+ openProjectContextMenu: (event, item) => {
+ dispatch<any>(openContextMenu(event, {
+ uuid: item.data.uuid,
+ name: item.data.name,
+ kind: ContextMenuKind.PROJECT
+ }));
+ }
+});
+
+export const NavigationPanel = withStyles(styles)(
+ connect(mapStateToProps, mapDispatchToProps)(
+ ({ classes, sidePanelItems, projects, ...actions }: NavigationPanelProps) => <Drawer
+ variant="permanent"
+ classes={{ paper: classes.drawerPaper }}>
+ <div className={classes.toolbar} />
+ <SidePanel
+ toggleOpen={actions.toggleSidePanelOpen}
+ toggleActive={actions.toggleSidePanelOpen}
+ sidePanelItems={sidePanelItems}
+ onContextMenu={actions.openRootContextMenu}>
+ <ProjectTree
+ projects={projects}
+ toggleOpen={actions.toggleProjectOpen}
+ onContextMenu={actions.openProjectContextMenu}
+ toggleActive={actions.toggleProjectActive} />
+ </SidePanel>
+ </Drawer>
+ )
+);
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index 559d4a9..7621d95 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -20,6 +20,10 @@ import { TagResource } from '~/models/tag';
import { CollectionTagForm } from './collection-tag-form';
import { deleteCollectionTag } from '~/store/collection-panel/collection-panel-action';
import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+import { getResource } from '~/store/resources/resources';
+import { loadCollection } from '../../store/collection-panel/collection-panel-action';
+import { contextMenuActions } from '~/store/context-menu/context-menu-actions';
+import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon' | 'label' | 'value';
@@ -55,81 +59,96 @@ interface CollectionPanelDataProps {
tags: TagResource[];
}
-interface CollectionPanelActionProps {
- onItemRouteChange: (collectionId: string) => void;
- onContextMenu: (event: React.MouseEvent<HTMLElement>, item: CollectionResource) => void;
-}
-
-type CollectionPanelProps = CollectionPanelDataProps & CollectionPanelActionProps & DispatchProp
- & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
+type CollectionPanelProps = CollectionPanelDataProps & DispatchProp
+ & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
export const CollectionPanel = withStyles(styles)(
- connect((state: RootState) => ({
- item: state.collectionPanel.item,
- tags: state.collectionPanel.tags
- }))(
+ connect((state: RootState, props: RouteComponentProps<{ id: string }>) => {
+ const collection = getResource(props.match.params.id)(state.resources);
+ return {
+ item: collection,
+ tags: state.collectionPanel.tags
+ };
+ })(
class extends React.Component<CollectionPanelProps> {
render() {
- const { classes, item, tags, onContextMenu } = this.props;
+ const { classes, item, tags } = this.props;
return <div>
- <Card className={classes.card}>
- <CardHeader
- avatar={ <CollectionIcon className={classes.iconHeader} /> }
- action={
- <IconButton
- aria-label="More options"
- onClick={event => onContextMenu(event, item)}>
- <MoreOptionsIcon />
- </IconButton>
- }
- title={item && item.name }
- subheader={item && item.description} />
- <CardContent>
- <Grid container direction="column">
- <Grid item xs={6}>
- <DetailsAttribute classLabel={classes.label} classValue={classes.value}
- label='Collection UUID'
- value={item && item.uuid}>
- <Tooltip title="Copy uuid">
- <CopyToClipboard text={item && item.uuid} onCopy={() => this.onCopy() }>
- <CopyIcon className={classes.copyIcon} />
- </CopyToClipboard>
- </Tooltip>
- </DetailsAttribute>
- <DetailsAttribute classLabel={classes.label} classValue={classes.value}
- label='Number of files' value='14' />
- <DetailsAttribute classLabel={classes.label} classValue={classes.value}
- label='Content size' value='54 MB' />
- <DetailsAttribute classLabel={classes.label} classValue={classes.value}
- label='Owner' value={item && item.ownerUuid} />
- </Grid>
+ <Card className={classes.card}>
+ <CardHeader
+ avatar={<CollectionIcon className={classes.iconHeader} />}
+ action={
+ <IconButton
+ aria-label="More options"
+ onClick={this.handleContextMenu}>
+ <MoreOptionsIcon />
+ </IconButton>
+ }
+ title={item && item.name}
+ subheader={item && item.description} />
+ <CardContent>
+ <Grid container direction="column">
+ <Grid item xs={6}>
+ <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+ label='Collection UUID'
+ value={item && item.uuid}>
+ <Tooltip title="Copy uuid">
+ <CopyToClipboard text={item && item.uuid} onCopy={() => this.onCopy()}>
+ <CopyIcon className={classes.copyIcon} />
+ </CopyToClipboard>
+ </Tooltip>
+ </DetailsAttribute>
+ <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+ label='Number of files' value='14' />
+ <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+ label='Content size' value='54 MB' />
+ <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+ label='Owner' value={item && item.ownerUuid} />
</Grid>
- </CardContent>
- </Card>
+ </Grid>
+ </CardContent>
+ </Card>
- <Card className={classes.card}>
- <CardHeader title="Properties" />
- <CardContent>
- <Grid container direction="column">
- <Grid item xs={12}><CollectionTagForm /></Grid>
- <Grid item xs={12}>
- {
- tags.map(tag => {
- return <Chip key={tag.etag} className={classes.tag}
- onDelete={this.handleDelete(tag.uuid)}
- label={renderTagLabel(tag)} />;
- })
- }
- </Grid>
+ <Card className={classes.card}>
+ <CardHeader title="Properties" />
+ <CardContent>
+ <Grid container direction="column">
+ <Grid item xs={12}><CollectionTagForm /></Grid>
+ <Grid item xs={12}>
+ {
+ tags.map(tag => {
+ return <Chip key={tag.etag} className={classes.tag}
+ onDelete={this.handleDelete(tag.uuid)}
+ label={renderTagLabel(tag)} />;
+ })
+ }
</Grid>
- </CardContent>
- </Card>
- <div className={classes.card}>
- <CollectionPanelFiles/>
- </div>
- </div>;
+ </Grid>
+ </CardContent>
+ </Card>
+ <div className={classes.card}>
+ <CollectionPanelFiles />
+ </div>
+ </div>;
+ }
+
+ handleContextMenu = (event: React.MouseEvent<any>) => {
+ event.preventDefault();
+ const { uuid, name, description } = this.props.item;
+ const resource = {
+ uuid,
+ name,
+ description,
+ kind: ContextMenuKind.COLLECTION
+ };
+ this.props.dispatch(
+ contextMenuActions.OPEN_CONTEXT_MENU({
+ position: { x: event.clientX, y: event.clientY },
+ resource
+ })
+ );
}
handleDelete = (uuid: string) => () => {
@@ -143,9 +162,10 @@ export const CollectionPanel = withStyles(styles)(
}));
}
- componentWillReceiveProps({ match, item, onItemRouteChange }: CollectionPanelProps) {
- if (!item || match.params.id !== item.uuid) {
- onItemRouteChange(match.params.id);
+ componentDidMount() {
+ const { match, item } = this.props;
+ if (!item && match.params.id) {
+ this.props.dispatch<any>(loadCollection(match.params.id));
}
}
diff --git a/src/views/favorite-panel/favorite-panel-item.ts b/src/views/favorite-panel/favorite-panel-item.ts
deleted file mode 100644
index 842b6d6..0000000
--- a/src/views/favorite-panel/favorite-panel-item.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { GroupContentsResource } from "~/services/groups-service/groups-service";
-import { ResourceKind } from "~/models/resource";
-
-export interface FavoritePanelItem {
- uuid: string;
- name: string;
- kind: string;
- url: string;
- owner: string;
- lastModified: string;
- fileSize?: number;
- status?: string;
-}
-
-export function resourceToDataItem(r: GroupContentsResource): FavoritePanelItem {
- return {
- uuid: r.uuid,
- name: r.name,
- kind: r.kind,
- url: "",
- owner: r.ownerUuid,
- lastModified: r.modifiedAt,
- status: r.kind === ResourceKind.PROCESS ? r.state : undefined
- };
-}
diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx
index 125ea27..dfe107a 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/favorite-panel/favorite-panel.tsx
@@ -3,7 +3,6 @@
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { FavoritePanelItem } from './favorite-panel-item';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
import { DispatchProp, connect } from 'react-redux';
@@ -16,9 +15,14 @@ import { SortDirection } from '~/components/data-table/data-column';
import { ResourceKind } from '~/models/resource';
import { resourceLabel } from '~/common/labels';
import { ArvadosTheme } from '~/common/custom-theme';
-import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
-import { FAVORITE_PANEL_ID } from "~/store/favorite-panel/favorite-panel-action";
+import { FAVORITE_PANEL_ID, loadFavoritePanel } from "~/store/favorite-panel/favorite-panel-action";
+import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner, ResourceName } from '~/views-components/data-explorer/renderers';
import { FavoriteIcon } from '~/components/icon/icon';
+import { Dispatch } from 'redux';
+import { contextMenuActions } from '~/store/context-menu/context-menu-actions';
+import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+import { loadDetailsPanel } from '../../store/details-panel/details-panel-action';
+import { navigateToResource } from '~/store/navigation/navigation-action';
type CssRules = "toolbar" | "button";
@@ -45,14 +49,14 @@ export interface FavoritePanelFilter extends DataTableFilterItem {
type: ResourceKind | ContainerRequestState;
}
-export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
+export const columns: DataColumns<string, FavoritePanelFilter> = [
{
name: FavoritePanelColumnNames.NAME,
selected: true,
configurable: true,
sortDirection: SortDirection.ASC,
filters: [],
- render: renderName,
+ render: uuid => <ResourceName uuid={uuid} />,
width: "450px"
},
{
@@ -77,7 +81,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
type: ContainerRequestState.UNCOMMITTED
}
],
- render: renderStatus,
+ render: uuid => <ProcessStatus uuid={uuid} />,
width: "75px"
},
{
@@ -102,7 +106,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
type: ResourceKind.PROJECT
}
],
- render: item => renderType(item.kind),
+ render: uuid => <ResourceType uuid={uuid} />,
width: "125px"
},
{
@@ -111,7 +115,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
configurable: true,
sortDirection: SortDirection.NONE,
filters: [],
- render: item => renderOwner(item.owner),
+ render: uuid => <ResourceOwner uuid={uuid} />,
width: "200px"
},
{
@@ -120,7 +124,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
configurable: true,
sortDirection: SortDirection.NONE,
filters: [],
- render: item => renderFileSize(item.fileSize),
+ render: uuid => <ResourceFileSize uuid={uuid} />,
width: "50px"
},
{
@@ -129,7 +133,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
configurable: true,
sortDirection: SortDirection.NONE,
filters: [],
- render: item => renderDate(item.lastModified),
+ render: uuid => <ResourceLastModifiedDate uuid={uuid} />,
width: "150px"
}
];
@@ -139,18 +143,40 @@ interface FavoritePanelDataProps {
}
interface FavoritePanelActionProps {
- onItemClick: (item: FavoritePanelItem) => void;
- onContextMenu: (event: React.MouseEvent<HTMLElement>, item: FavoritePanelItem) => void;
+ onItemClick: (item: string) => void;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string) => void;
onDialogOpen: (ownerUuid: string) => void;
- onItemDoubleClick: (item: FavoritePanelItem) => void;
- onItemRouteChange: (itemId: string) => void;
+ onItemDoubleClick: (item: string) => void;
+ onMount: () => void;
}
+const mapDispatchToProps = (dispatch: Dispatch): FavoritePanelActionProps => ({
+ onContextMenu: (event, resourceUuid) => {
+ event.preventDefault();
+ dispatch(
+ contextMenuActions.OPEN_CONTEXT_MENU({
+ position: { x: event.clientX, y: event.clientY },
+ resource: { name: '', uuid: resourceUuid, kind: ContextMenuKind.RESOURCE }
+ })
+ );
+ },
+ onDialogOpen: (ownerUuid: string) => { return; },
+ onItemClick: (resourceUuid: string) => {
+ dispatch<any>(loadDetailsPanel(resourceUuid));
+ },
+ onItemDoubleClick: uuid => {
+ dispatch<any>(navigateToResource(uuid));
+ },
+ onMount: () => {
+ dispatch(loadFavoritePanel());
+ },
+});
+
type FavoritePanelProps = FavoritePanelDataProps & FavoritePanelActionProps & DispatchProp
- & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
+ & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
export const FavoritePanel = withStyles(styles)(
- connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
+ connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }), mapDispatchToProps)(
class extends React.Component<FavoritePanelProps> {
render() {
return <DataExplorer
@@ -159,16 +185,12 @@ export const FavoritePanel = withStyles(styles)(
onRowClick={this.props.onItemClick}
onRowDoubleClick={this.props.onItemDoubleClick}
onContextMenu={this.props.onContextMenu}
- extractKey={(item: FavoritePanelItem) => item.uuid}
defaultIcon={FavoriteIcon}
- defaultMessages={['Your favorites list is empty.']}/>
- ;
+ defaultMessages={['Your favorites list is empty.']} />;
}
- componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: FavoritePanelProps) {
- if (match.params.id !== currentItemId) {
- onItemRouteChange(match.params.id);
- }
+ componentDidMount() {
+ this.props.onMount();
}
}
)
diff --git a/src/views/project-panel/project-panel-item.ts b/src/views/project-panel/project-panel-item.ts
deleted file mode 100644
index f031859..0000000
--- a/src/views/project-panel/project-panel-item.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { GroupContentsResource } from "~/services/groups-service/groups-service";
-import { ResourceKind } from "~/models/resource";
-
-export interface ProjectPanelItem {
- uuid: string;
- name: string;
- description?: string;
- kind: string;
- url: string;
- owner: string;
- lastModified: string;
- fileSize?: number;
- status?: string;
-}
-
-export function resourceToDataItem(r: GroupContentsResource): ProjectPanelItem {
- return {
- uuid: r.uuid,
- name: r.name,
- description: r.description,
- kind: r.kind,
- url: "",
- owner: r.ownerUuid,
- lastModified: r.modifiedAt,
- status: r.kind === ResourceKind.PROCESS ? r.state : undefined
- };
-}
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 0f958d2..a2ae4cf 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -3,7 +3,6 @@
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { ProjectPanelItem } from './project-panel-item';
import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
import { DispatchProp, connect } from 'react-redux';
@@ -16,9 +15,21 @@ import { SortDirection } from '~/components/data-table/data-column';
import { ResourceKind } from '~/models/resource';
import { resourceLabel } from '~/common/labels';
import { ArvadosTheme } from '~/common/custom-theme';
-import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
-import { restoreBranch } from '~/store/navigation/navigation-action';
+import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
+import { restoreBranch, setProjectItem, ItemMode } from '~/store/navigation/navigation-action';
import { ProjectIcon } from '~/components/icon/icon';
+import { ResourceName } from '~/views-components/data-explorer/renderers';
+import { ResourcesState, getResource } from '~/store/resources/resources';
+import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+import { contextMenuActions } from '~/store/context-menu/context-menu-actions';
+import { CollectionResource } from '~/models/collection';
+import { ProjectResource } from '~/models/project';
+import { openProjectCreator } from '~/store/project/project-action';
+import { reset } from 'redux-form';
+import { COLLECTION_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-collection-create';
+import { collectionCreateActions } from '~/store/collections/creator/collection-creator-action';
+import { navigateToResource } from '~/store/navigation/navigation-action';
type CssRules = 'root' | "toolbar" | "button";
@@ -50,14 +61,14 @@ export interface ProjectPanelFilter extends DataTableFilterItem {
type: ResourceKind | ContainerRequestState;
}
-export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
+export const columns: DataColumns<string, ProjectPanelFilter> = [
{
name: ProjectPanelColumnNames.NAME,
selected: true,
configurable: true,
sortDirection: SortDirection.ASC,
filters: [],
- render: renderName,
+ render: uuid => <ResourceName uuid={uuid} />,
width: "450px"
},
{
@@ -82,7 +93,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
type: ContainerRequestState.UNCOMMITTED
}
],
- render: renderStatus,
+ render: uuid => <ProcessStatus uuid={uuid} />,
width: "75px"
},
{
@@ -107,7 +118,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
type: ResourceKind.PROJECT
}
],
- render: item => renderType(item.kind),
+ render: uuid => <ResourceType uuid={uuid} />,
width: "125px"
},
{
@@ -116,7 +127,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
configurable: true,
sortDirection: SortDirection.NONE,
filters: [],
- render: item => renderOwner(item.owner),
+ render: uuid => <ResourceOwner uuid={uuid} />,
width: "200px"
},
{
@@ -125,7 +136,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
configurable: true,
sortDirection: SortDirection.NONE,
filters: [],
- render: item => renderFileSize(item.fileSize),
+ render: uuid => <ResourceFileSize uuid={uuid} />,
width: "50px"
},
{
@@ -134,7 +145,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
configurable: true,
sortDirection: SortDirection.NONE,
filters: [],
- render: item => renderDate(item.lastModified),
+ render: uuid => <ResourceLastModifiedDate uuid={uuid} />,
width: "150px"
}
];
@@ -143,22 +154,14 @@ export const PROJECT_PANEL_ID = "projectPanel";
interface ProjectPanelDataProps {
currentItemId: string;
+ resources: ResourcesState;
}
-interface ProjectPanelActionProps {
- onItemClick: (item: ProjectPanelItem) => void;
- onContextMenu: (event: React.MouseEvent<HTMLElement>, item: ProjectPanelItem) => void;
- onProjectCreationDialogOpen: (ownerUuid: string) => void;
- onCollectionCreationDialogOpen: (ownerUuid: string) => void;
- onItemDoubleClick: (item: ProjectPanelItem) => void;
- onItemRouteChange: (itemId: string) => void;
-}
-
-type ProjectPanelProps = ProjectPanelDataProps & ProjectPanelActionProps & DispatchProp
+type ProjectPanelProps = ProjectPanelDataProps & DispatchProp
& WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
export const ProjectPanel = withStyles(styles)(
- connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
+ connect((state: RootState) => ({ currentItemId: state.projects.currentItemId, resources: state.resources }))(
class extends React.Component<ProjectPanelProps> {
render() {
const { classes } = this.props;
@@ -177,32 +180,64 @@ export const ProjectPanel = withStyles(styles)(
<DataExplorer
id={PROJECT_PANEL_ID}
columns={columns}
- onRowClick={this.props.onItemClick}
- onRowDoubleClick={this.props.onItemDoubleClick}
- onContextMenu={this.props.onContextMenu}
- extractKey={(item: ProjectPanelItem) => item.uuid}
+ onRowClick={this.handleRowClick}
+ onRowDoubleClick={this.handleRowDoubleClick}
+ onContextMenu={this.handleContextMenu}
defaultIcon={ProjectIcon}
defaultMessages={['Your project is empty.', 'Please create a project or create a collection and upload a data.']} />
</div>;
}
handleNewProjectClick = () => {
- this.props.onProjectCreationDialogOpen(this.props.currentItemId);
+ this.props.dispatch<any>(openProjectCreator(this.props.currentItemId));
}
handleNewCollectionClick = () => {
- this.props.onCollectionCreationDialogOpen(this.props.currentItemId);
+ this.props.dispatch(reset(COLLECTION_CREATE_DIALOG));
+ this.props.dispatch(collectionCreateActions.OPEN_COLLECTION_CREATOR({ ownerUuid: this.props.currentItemId }));
}
- componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: ProjectPanelProps) {
- if (match.params.id !== currentItemId) {
- onItemRouteChange(match.params.id);
+ handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
+ event.preventDefault();
+ const resource = getResource(resourceUuid)(this.props.resources) as CollectionResource | ProjectResource | undefined;
+ if (resource) {
+ let kind: ContextMenuKind;
+
+ if (resource.kind === ResourceKind.PROJECT) {
+ kind = ContextMenuKind.PROJECT;
+ } else if (resource.kind === ResourceKind.COLLECTION) {
+ kind = ContextMenuKind.COLLECTION_RESOURCE;
+ } else {
+ kind = ContextMenuKind.RESOURCE;
+ }
+ if (kind !== ContextMenuKind.RESOURCE) {
+ this.props.dispatch(
+ contextMenuActions.OPEN_CONTEXT_MENU({
+ position: { x: event.clientX, y: event.clientY },
+ resource: {
+ uuid: resource.uuid,
+ name: resource.name || '',
+ description: resource.description,
+ kind,
+ }
+ })
+ );
+ }
}
}
- componentDidMount() {
+ handleRowDoubleClick = (uuid: string) => {
+ this.props.dispatch<any>(navigateToResource(uuid));
+ }
+
+ handleRowClick = (uuid: string) => {
+ this.props.dispatch(loadDetailsPanel(uuid));
+ }
+
+ async componentDidMount() {
if (this.props.match.params.id && this.props.currentItemId === '') {
- this.props.dispatch<any>(restoreBranch(this.props.match.params.id));
+ await this.props.dispatch<any>(restoreBranch(this.props.match.params.id));
+ this.props.dispatch<any>(setProjectItem(this.props.match.params.id, ItemMode.BOTH));
}
}
}
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index ed11eb1..2dda4d2 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -4,42 +4,30 @@
import * as React from 'react';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-import Drawer from '@material-ui/core/Drawer';
import { connect, DispatchProp } from "react-redux";
-import { Route, RouteComponentProps, Switch, Redirect } from "react-router";
+import { Route, Switch, Redirect } from "react-router";
import { login, logout } from "~/store/auth/auth-action";
import { User } from "~/models/user";
import { RootState } from "~/store/store";
import { MainAppBar, MainAppBarActionProps, MainAppBarMenuItem } from '~/views-components/main-app-bar/main-app-bar';
import { Breadcrumb } from '~/components/breadcrumbs/breadcrumbs';
import { push } from 'react-router-redux';
-import { reset } from 'redux-form';
-import { ProjectTree } from '~/views-components/project-tree/project-tree';
import { TreeItem } from "~/components/tree/tree";
import { getTreePath } from '~/store/project/project-reducer';
-import { sidePanelActions } from '~/store/side-panel/side-panel-action';
-import { SidePanel, SidePanelItem } from '~/components/side-panel/side-panel';
import { ItemMode, setProjectItem } from "~/store/navigation/navigation-action";
-import { projectActions } from "~/store/project/project-action";
-import { collectionCreateActions } from '~/store/collections/creator/collection-creator-action';
import { ProjectPanel } from "~/views/project-panel/project-panel";
import { DetailsPanel } from '~/views-components/details-panel/details-panel';
import { ArvadosTheme } from '~/common/custom-theme';
import { CreateProjectDialog } from "~/views-components/create-project-dialog/create-project-dialog";
-
-import { detailsPanelActions, loadDetails } from "~/store/details-panel/details-panel-action";
-import { contextMenuActions } from "~/store/context-menu/context-menu-actions";
+import { detailsPanelActions, loadDetailsPanel } from "~/store/details-panel/details-panel-action";
+import { openContextMenu } from '~/store/context-menu/context-menu-actions';
import { ProjectResource } from '~/models/project';
-import { ResourceKind } from '~/models/resource';
import { ContextMenu, ContextMenuKind } from "~/views-components/context-menu/context-menu";
import { FavoritePanel } from "../favorite-panel/favorite-panel";
import { CurrentTokenDialog } from '~/views-components/current-token-dialog/current-token-dialog';
import { Snackbar } from '~/views-components/snackbar/snackbar';
-import { favoritePanelActions } from '~/store/favorite-panel/favorite-panel-action';
import { CreateCollectionDialog } from '~/views-components/create-collection-dialog/create-collection-dialog';
import { CollectionPanel } from '../collection-panel/collection-panel';
-import { loadCollection, loadCollectionTags } from '~/store/collection-panel/collection-panel-action';
-import { getCollectionUrl } from '~/models/collection';
import { UpdateCollectionDialog } from '~/views-components/update-collection-dialog/update-collection-dialog.';
import { UpdateProjectDialog } from '~/views-components/update-project-dialog/update-project-dialog';
import { AuthService } from "~/services/auth-service/auth-service";
@@ -47,18 +35,16 @@ import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-f
import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog';
import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog/multiple-files-remove-dialog';
import { DialogCollectionCreateWithSelectedFile } from '~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
-import { COLLECTION_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-collection-create';
-import { PROJECT_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-project-create';
import { UploadCollectionFilesDialog } from '~/views-components/upload-collection-files-dialog/upload-collection-files-dialog';
import { ProjectCopyDialog } from '~/views-components/project-copy-dialog/project-copy-dialog';
import { CollectionPartialCopyDialog } from '../../views-components/collection-partial-copy-dialog/collection-partial-copy-dialog';
import { MoveProjectDialog } from '~/views-components/move-project-dialog/move-project-dialog';
import { MoveCollectionDialog } from '~/views-components/move-collection-dialog/move-collection-dialog';
+import { NavigationPanel } from '~/views-components/navigation-panel/navigation-panel';
-const DRAWER_WITDH = 240;
const APP_BAR_HEIGHT = 100;
-type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'contentWrapper' | 'toolbar';
+type CssRules = 'root' | 'appBar' | 'content' | 'contentWrapper';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
@@ -75,12 +61,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
position: "absolute",
width: "100%"
},
- drawerPaper: {
- position: 'relative',
- width: DRAWER_WITDH,
- display: 'flex',
- flexDirection: 'column',
- },
contentWrapper: {
backgroundColor: theme.palette.background.default,
display: "flex",
@@ -94,7 +74,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
flexGrow: 1,
position: 'relative'
},
- toolbar: theme.mixins.toolbar
});
interface WorkbenchDataProps {
@@ -102,7 +81,6 @@ interface WorkbenchDataProps {
currentProjectId: string;
user?: User;
currentToken?: string;
- sidePanelItems: SidePanelItem[];
}
interface WorkbenchGeneralProps {
@@ -141,12 +119,10 @@ export const Workbench = withStyles(styles)(
currentProjectId: state.projects.currentItemId,
user: state.auth.user,
currentToken: state.auth.apiToken,
- sidePanelItems: state.sidePanel
})
)(
class extends React.Component<WorkbenchProps, WorkbenchState> {
state = {
- isCreationDialogOpen: false,
isCurrentTokenDialogOpen: false,
anchorEl: null,
searchText: "",
@@ -201,43 +177,14 @@ export const Workbench = withStyles(styles)(
buildInfo={this.props.buildInfo}
{...this.mainAppBarActions} />
</div>
- {user &&
- <Drawer
- variant="permanent"
- classes={{
- paper: classes.drawerPaper,
- }}>
- <div className={classes.toolbar} />
- <SidePanel
- toggleOpen={this.toggleSidePanelOpen}
- toggleActive={this.toggleSidePanelActive}
- sidePanelItems={this.props.sidePanelItems}
- onContextMenu={(event) => this.openContextMenu(event, {
- uuid: this.props.authService.getUuid() || "",
- name: "",
- kind: ContextMenuKind.ROOT_PROJECT
- })}>
- <ProjectTree
- projects={this.props.projects}
- toggleOpen={itemId => this.props.dispatch(setProjectItem(itemId, ItemMode.OPEN))}
- onContextMenu={(event, item) => this.openContextMenu(event, {
- uuid: item.data.uuid,
- name: item.data.name,
- kind: ContextMenuKind.PROJECT
- })}
- toggleActive={itemId => {
- this.props.dispatch(setProjectItem(itemId, ItemMode.ACTIVE));
- this.props.dispatch(loadDetails(itemId, ResourceKind.PROJECT));
- }} />
- </SidePanel>
- </Drawer>}
+ {user && <NavigationPanel />}
<main className={classes.contentWrapper}>
<div className={classes.content}>
<Switch>
<Route path='/' exact render={() => <Redirect to={`/projects/${this.props.authService.getUuid()}`} />} />
- <Route path="/projects/:id" render={this.renderProjectPanel} />
- <Route path="/favorites" render={this.renderFavoritePanel} />
- <Route path="/collections/:id" render={this.renderCollectionPanel} />
+ <Route path="/projects/:id" component={ProjectPanel} />
+ <Route path="/favorites" component={FavoritePanel} />
+ <Route path="/collections/:id" component={CollectionPanel} />
</Switch>
</div>
{user && <DetailsPanel />}
@@ -265,90 +212,10 @@ export const Workbench = withStyles(styles)(
);
}
- renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
- onItemRouteChange={(collectionId) => {
- this.props.dispatch<any>(loadCollection(collectionId));
- this.props.dispatch<any>(loadCollectionTags(collectionId));
- }}
- onContextMenu={(event, item) => {
- this.openContextMenu(event, {
- uuid: item.uuid,
- name: item.name,
- description: item.description,
- kind: ContextMenuKind.COLLECTION
- });
- }}
- {...props} />
-
- renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
- onItemRouteChange={itemId => this.props.dispatch(setProjectItem(itemId, ItemMode.ACTIVE))}
- onContextMenu={(event, item) => {
- let kind: ContextMenuKind;
-
- if (item.kind === ResourceKind.PROJECT) {
- kind = ContextMenuKind.PROJECT;
- } else if (item.kind === ResourceKind.COLLECTION) {
- kind = ContextMenuKind.COLLECTION_RESOURCE;
- } else {
- kind = ContextMenuKind.RESOURCE;
- }
-
- this.openContextMenu(event, {
- uuid: item.uuid,
- name: item.name,
- description: item.description,
- kind
- });
- }}
- onProjectCreationDialogOpen={this.handleProjectCreationDialogOpen}
- onCollectionCreationDialogOpen={this.handleCollectionCreationDialogOpen}
- onItemClick={item => {
- this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
- }}
- onItemDoubleClick={item => {
- switch (item.kind) {
- case ResourceKind.COLLECTION:
- this.props.dispatch(loadCollection(item.uuid));
- this.props.dispatch(push(getCollectionUrl(item.uuid)));
- default:
- this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
- this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
- }
-
- }}
- {...props} />
-
- renderFavoritePanel = (props: RouteComponentProps<{ id: string }>) => <FavoritePanel
- onItemRouteChange={() => this.props.dispatch(favoritePanelActions.REQUEST_ITEMS())}
- onContextMenu={(event, item) => {
- const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.RESOURCE;
- this.openContextMenu(event, {
- uuid: item.uuid,
- name: item.name,
- kind,
- });
- }}
- onDialogOpen={this.handleProjectCreationDialogOpen}
- onItemClick={item => {
- this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
- }}
- onItemDoubleClick={item => {
- switch (item.kind) {
- case ResourceKind.COLLECTION:
- this.props.dispatch(loadCollection(item.uuid));
- this.props.dispatch(push(getCollectionUrl(item.uuid)));
- default:
- this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));
- this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
- }
-
- }}
- {...props} />
-
mainAppBarActions: MainAppBarActionProps = {
onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
this.props.dispatch(setProjectItem(itemId, ItemMode.BOTH));
- this.props.dispatch(loadDetails(itemId, ResourceKind.PROJECT));
+ this.props.dispatch(loadDetailsPanel(itemId));
},
onSearch: searchText => {
this.setState({ searchText });
@@ -359,47 +226,14 @@ export const Workbench = withStyles(styles)(
this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
},
onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: NavBreadcrumb) => {
- this.openContextMenu(event, {
+ this.props.dispatch<any>(openContextMenu(event, {
uuid: breadcrumb.itemId,
name: breadcrumb.label,
kind: ContextMenuKind.PROJECT
- });
+ }));
}
};
- toggleSidePanelOpen = (itemId: string) => {
- this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
- }
-
- toggleSidePanelActive = (itemId: string) => {
- this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
-
- const panelItem = this.props.sidePanelItems.find(it => it.id === itemId);
- if (panelItem && panelItem.activeAction) {
- panelItem.activeAction(this.props.dispatch, this.props.authService.getUuid());
- }
- }
-
- handleProjectCreationDialogOpen = (itemUuid: string) => {
- this.props.dispatch(reset(PROJECT_CREATE_DIALOG));
- this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid }));
- }
-
- handleCollectionCreationDialogOpen = (itemUuid: string) => {
- this.props.dispatch(reset(COLLECTION_CREATE_DIALOG));
- this.props.dispatch(collectionCreateActions.OPEN_COLLECTION_CREATOR({ ownerUuid: itemUuid }));
- }
-
- openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: { name: string; uuid: string; description?: string; kind: ContextMenuKind; }) => {
- event.preventDefault();
- this.props.dispatch(
- contextMenuActions.OPEN_CONTEXT_MENU({
- position: { x: event.clientX, y: event.clientY },
- resource
- })
- );
- }
-
toggleCurrentTokenModal = () => {
this.setState({ isCurrentTokenDialogOpen: !this.state.isCurrentTokenDialogOpen });
}
commit 2df6bac4eea43c9079641cd05262b29cbd285905
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date: Wed Aug 22 23:06:06 2018 +0200
Create common state for resources
Feature #14102
Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
diff --git a/src/store/resources/resources-actions.ts b/src/store/resources/resources-actions.ts
new file mode 100644
index 0000000..36f9936
--- /dev/null
+++ b/src/store/resources/resources-actions.ts
@@ -0,0 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { unionize, ofType, UnionOf } from '~/common/unionize';
+import { Resource } from '~/models/resource';
+
+export const resourcesActions = unionize({
+ SET_RESOURCES: ofType<Resource[]>(),
+ DELETE_RESOURCES: ofType<string[]>()
+});
+
+export type ResourcesAction = UnionOf<typeof resourcesActions>;
\ No newline at end of file
diff --git a/src/store/resources/resources-reducer.ts b/src/store/resources/resources-reducer.ts
new file mode 100644
index 0000000..22108e0
--- /dev/null
+++ b/src/store/resources/resources-reducer.ts
@@ -0,0 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ResourcesState, setResource, deleteResource } from './resources';
+import { ResourcesAction, resourcesActions } from './resources-actions';
+
+export const resourcesReducer = (state: ResourcesState = {}, action: ResourcesAction) =>
+ resourcesActions.match(action, {
+ SET_RESOURCES: resources => resources.reduce((state, resource) => setResource(resource.uuid, resource)(state), state),
+ DELETE_RESOURCES: ids => ids.reduce((state, id) => deleteResource(id)(state), state),
+ default: () => state,
+ });
\ No newline at end of file
diff --git a/src/store/resources/resources.ts b/src/store/resources/resources.ts
new file mode 100644
index 0000000..7f21332
--- /dev/null
+++ b/src/store/resources/resources.ts
@@ -0,0 +1,37 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Resource } from "~/models/resource";
+import { ResourceKind } from '../../models/resource';
+
+export type ResourcesState = { [key: string]: Resource };
+
+export const getResource = <T extends Resource>(id: string) =>
+ (state: ResourcesState): Resource | undefined =>
+ state[id];
+
+export const setResource = <T extends Resource>(id: string, data: T) =>
+ (state: ResourcesState) => ({
+ ...state,
+ [id]: data
+ });
+
+export const deleteResource = (id: string) =>
+ (state: ResourcesState) => {
+ const newState = {...state};
+ delete newState[id];
+ return newState;
+ };
+
+export const filterResources = (filter: (resource: Resource) => boolean) =>
+ (state: ResourcesState) =>
+ Object
+ .keys(state)
+ .map(id => getResource(id)(state))
+ .filter(filter);
+
+export const filterResourcesByKind = (kind: ResourceKind) =>
+ (state: ResourcesState) =>
+ filterResources(resource => resource.kind === kind)(state);
+
diff --git a/src/store/store.ts b/src/store/store.ts
index a4bf9d6..eb36fa4 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -29,6 +29,8 @@ import { CollectionsState, collectionsReducer } from './collections/collections-
import { ServiceRepository } from "~/services/services";
import { treePickerReducer } from './tree-picker/tree-picker-reducer';
import { TreePicker } from './tree-picker/tree-picker';
+import { ResourcesState } from '~/store/resources/resources';
+import { resourcesReducer } from '~/store/resources/resources-reducer';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
@@ -50,6 +52,7 @@ export interface RootState {
collectionPanelFiles: CollectionPanelFilesState;
dialog: DialogState;
treePicker: TreePicker;
+ resources: ResourcesState;
}
export type RootStore = Store<RootState, Action> & { dispatch: Dispatch<any> };
@@ -71,6 +74,7 @@ export function configureStore(history: History, services: ServiceRepository): R
collectionPanelFiles: collectionPanelFilesReducer,
dialog: dialogReducer,
treePicker: treePickerReducer,
+ resources: resourcesReducer,
});
const projectPanelMiddleware = dataExplorerMiddleware(
commit 6ce4e6e255691116f2c8e229d45df571dffa6b9a
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date: Wed Aug 22 23:04:24 2018 +0200
Create unionize wrapper
Feature #14102
Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
diff --git a/src/common/unionize.ts b/src/common/unionize.ts
new file mode 100644
index 0000000..b684431
--- /dev/null
+++ b/src/common/unionize.ts
@@ -0,0 +1,14 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export * from 'unionize';
+import { unionize as originalUnionize, SingleValueRec } from 'unionize';
+
+export function unionize<Record extends SingleValueRec>(record: Record) {
+ return originalUnionize(record, {
+ tag: 'type',
+ value: 'payload'
+ });
+}
+
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list