[ARVADOS-WORKBENCH2] updated: 1.2.0-40-gf1065fb
Git user
git at public.curoverse.com
Wed Aug 29 16:57:40 EDT 2018
Summary of changes:
package.json | 4 +-
src/components/icon/icon.tsx | 2 +
.../collection-service/collection-service.ts | 19 ++-
src/services/groups-service/groups-service.ts | 24 +++-
src/services/services.ts | 3 -
src/services/trash-service/trash-service.test.ts | 17 ---
src/services/trash-service/trash-service.ts | 12 --
src/store/collections/collection-trash-actions.ts | 39 ++++++
src/store/context-menu/context-menu-reducer.ts | 2 +
src/store/navigation/navigation-action.ts | 8 +-
src/store/project/project-action.ts | 40 +++++-
src/store/project/project-reducer.test.ts | 4 +-
src/store/project/project-reducer.ts | 154 +++++++++------------
src/store/side-panel/side-panel-reducer.ts | 2 +-
.../trash-panel/trash-panel-middleware-service.ts | 4 +-
.../action-sets/collection-action-set.ts | 8 ++
.../action-sets/collection-resource-action-set.ts | 8 ++
.../context-menu/action-sets/project-action-set.ts | 9 +-
.../{favorite-action.tsx => trash-action.tsx} | 21 ++-
src/views/collection-panel/collection-panel.tsx | 2 -
src/views/favorite-panel/favorite-panel-item.ts | 4 +-
src/views/project-panel/project-panel-item.ts | 4 +-
src/views/trash-panel/trash-panel-item.ts | 2 +
src/views/trash-panel/trash-panel.tsx | 3 +-
src/views/workbench/workbench.tsx | 30 ++--
25 files changed, 257 insertions(+), 168 deletions(-)
delete mode 100644 src/services/trash-service/trash-service.test.ts
delete mode 100644 src/services/trash-service/trash-service.ts
create mode 100644 src/store/collections/collection-trash-actions.ts
copy src/views-components/context-menu/actions/{favorite-action.tsx => trash-action.tsx} (50%)
via f1065fb7260dd562ea2e826e9cbc508d7932ecca (commit)
from efe0283919eb18e60ad876eaf6edef03c6cf04b3 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
commit f1065fb7260dd562ea2e826e9cbc508d7932ecca
Author: Daniel Kos <daniel.kos at contractors.roche.com>
Date: Tue Aug 21 21:46:18 2018 +0200
Add trash view and trahsing/untrashing project/collection
Feature #13828
Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos at contractors.roche.com>
diff --git a/package.json b/package.json
index e2b6c4e..0264998 100644
--- a/package.json
+++ b/package.json
@@ -26,8 +26,8 @@
"unionize": "2.1.2"
},
"scripts": {
- "start": "react-scripts-ts start",
- "build": "react-scripts-ts build",
+ "start": "REACT_APP_BUILD_NUMBER=$BUILD_NUMBER REACT_APP_GIT_COMMIT=$GIT_COMMIT react-scripts-ts start",
+ "build": "REACT_APP_BUILD_NUMBER=$BUILD_NUMBER REACT_APP_GIT_COMMIT=$GIT_COMMIT react-scripts-ts build",
"test": "react-scripts-ts test --env=jsdom",
"eject": "react-scripts-ts eject",
"lint": "tslint src/** -t verbose"
diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 0f0442a..86a1a68 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -32,6 +32,7 @@ import Person from '@material-ui/icons/Person';
import PersonAdd from '@material-ui/icons/PersonAdd';
import PlayArrow from '@material-ui/icons/PlayArrow';
import RateReview from '@material-ui/icons/RateReview';
+import RestoreFromTrash from '@material-ui/icons/RestoreFromTrash';
import Search from '@material-ui/icons/Search';
import SettingsApplications from '@material-ui/icons/SettingsApplications';
import Star from '@material-ui/icons/Star';
@@ -67,6 +68,7 @@ export const RecentIcon: IconType = (props) => <AccessTime {...props} />;
export const RemoveIcon: IconType = (props) => <Delete {...props} />;
export const RemoveFavoriteIcon: IconType = (props) => <Star {...props} />;
export const RenameIcon: IconType = (props) => <Edit {...props} />;
+export const RestoreFromTrashIcon: IconType = (props) => <RestoreFromTrash {...props} />;
export const ReRunProcessIcon: IconType = (props) => <Cached {...props} />;
export const SearchIcon: IconType = (props) => <Search {...props} />;
export const ShareIcon: IconType = (props) => <PersonAdd {...props} />;
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
index 9feec69..ad493b5 100644
--- a/src/services/collection-service/collection-service.ts
+++ b/src/services/collection-service/collection-service.ts
@@ -76,7 +76,6 @@ export class CollectionService extends CommonResourceService<CollectionResource>
});
}
-
private readFile(file: File): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>(resolve => {
const reader = new FileReader();
@@ -163,4 +162,22 @@ export class CollectionService extends CommonResourceService<CollectionResource>
}
});
}
+
+ trash(uuid: string): Promise<CollectionResource> {
+ return this.serverApi
+ .post(this.resourceType + `${uuid}/trash`)
+ .then(CommonResourceService.mapResponseKeys);
+ }
+
+ untrash(uuid: string): Promise<CollectionResource> {
+ const params = {
+ ensure_unique_name: true
+ };
+ return this.serverApi
+ .post(this.resourceType + `${uuid}/untrash`, {
+ params: CommonResourceService.mapKeys(_.snakeCase)(params)
+ })
+ .then(CommonResourceService.mapResponseKeys);
+ }
+
}
diff --git a/src/services/groups-service/groups-service.ts b/src/services/groups-service/groups-service.ts
index 39cc74a..4756aa3 100644
--- a/src/services/groups-service/groups-service.ts
+++ b/src/services/groups-service/groups-service.ts
@@ -5,10 +5,10 @@
import * as _ from "lodash";
import { CommonResourceService, ListResults } from "~/common/api/common-resource-service";
import { AxiosInstance } from "axios";
-import { GroupResource } from "~/models/group";
import { CollectionResource } from "~/models/collection";
import { ProjectResource } from "~/models/project";
import { ProcessResource } from "~/models/process";
+import { TrashResource } from "~/models/resource";
export interface ContentsArguments {
limit?: number;
@@ -24,7 +24,7 @@ export type GroupContentsResource =
ProjectResource |
ProcessResource;
-export class GroupsService<T extends GroupResource = GroupResource> extends CommonResourceService<T> {
+export class GroupsService<T extends TrashResource = TrashResource> extends CommonResourceService<T> {
constructor(serverApi: AxiosInstance) {
super(serverApi, "groups");
@@ -38,11 +38,29 @@ export class GroupsService<T extends GroupResource = GroupResource> extends Comm
order: order ? order : undefined
};
return this.serverApi
- .get(this.resourceType + `${uuid}/contents/`, {
+ .get(this.resourceType + `${uuid}/contents`, {
params: CommonResourceService.mapKeys(_.snakeCase)(params)
})
.then(CommonResourceService.mapResponseKeys);
}
+
+ trash(uuid: string): Promise<T> {
+ return this.serverApi
+ .post(this.resourceType + `${uuid}/trash`)
+ .then(CommonResourceService.mapResponseKeys);
+ }
+
+ 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);
+ }
+
}
export enum GroupContentsResourcePrefix {
diff --git a/src/services/services.ts b/src/services/services.ts
index 9199755..f63194d 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -14,7 +14,6 @@ import { CollectionFilesService } from "./collection-files-service/collection-fi
import { KeepService } from "./keep-service/keep-service";
import { WebDAV } from "~/common/webdav";
import { Config } from "~/common/config";
-import { TrashService } from "~/services/trash-service/trash-service";
export type ServiceRepository = ReturnType<typeof createServices>;
@@ -31,7 +30,6 @@ export const createServices = (config: Config) => {
const projectService = new ProjectService(apiClient);
const linkService = new LinkService(apiClient);
const favoriteService = new FavoriteService(linkService, groupsService);
- const trashService = new TrashService(apiClient);
const collectionService = new CollectionService(apiClient, keepService, webdavClient, authService);
const tagService = new TagService(linkService);
const collectionFilesService = new CollectionFilesService(collectionService);
@@ -45,7 +43,6 @@ export const createServices = (config: Config) => {
projectService,
linkService,
favoriteService,
- trashService,
collectionService,
tagService,
collectionFilesService
diff --git a/src/services/trash-service/trash-service.test.ts b/src/services/trash-service/trash-service.test.ts
deleted file mode 100644
index f22d066..0000000
--- a/src/services/trash-service/trash-service.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { GroupsService } from "../groups-service/groups-service";
-import { TrashService } from "./trash-service";
-import { mockResourceService } from "~/common/api/common-resource-service.test";
-
-describe("TrashService", () => {
-
- let groupService: GroupsService;
-
- beforeEach(() => {
- groupService = mockResourceService(GroupsService);
- });
-
-});
diff --git a/src/services/trash-service/trash-service.ts b/src/services/trash-service/trash-service.ts
deleted file mode 100644
index fc02d2f..0000000
--- a/src/services/trash-service/trash-service.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { GroupsService } from "../groups-service/groups-service";
-import { AxiosInstance } from "axios";
-
-export class TrashService extends GroupsService {
- constructor(serverApi: AxiosInstance) {
- super(serverApi);
- }
-}
diff --git a/src/store/collections/collection-trash-actions.ts b/src/store/collections/collection-trash-actions.ts
new file mode 100644
index 0000000..c6d4ee0
--- /dev/null
+++ b/src/store/collections/collection-trash-actions.ts
@@ -0,0 +1,39 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from "redux";
+import { RootState } from "~/store/store";
+import { ServiceRepository } from "~/services/services";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
+import { sidePanelActions } from "~/store/side-panel/side-panel-action";
+import { SidePanelId } from "~/store/side-panel/side-panel-reducer";
+import { trashPanelActions } from "~/store/trash-panel/trash-panel-action";
+import { getProjectList, projectActions } from "~/store/project/project-action";
+
+export const toggleCollectionTrashed = (resource: { uuid: string; name: string, isTrashed?: boolean, ownerUuid?: string }) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Working..." }));
+ if (resource.isTrashed) {
+ return services.collectionService.untrash(resource.uuid).then(() => {
+ dispatch<any>(getProjectList(resource.ownerUuid)).then(() => {
+ dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(SidePanelId.PROJECTS));
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN({ itemId: resource.ownerUuid!!, open: true, recursive: true }));
+ });
+ dispatch(trashPanelActions.REQUEST_ITEMS());
+ dispatch(snackbarActions.CLOSE_SNACKBAR());
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Restored from trash",
+ hideDuration: 2000
+ }));
+ });
+ } else {
+ return services.collectionService.trash(resource.uuid).then(() => {
+ dispatch(snackbarActions.CLOSE_SNACKBAR());
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Added to trash",
+ hideDuration: 2000
+ }));
+ });
+ }
+ };
diff --git a/src/store/context-menu/context-menu-reducer.ts b/src/store/context-menu/context-menu-reducer.ts
index ac14c35..8026c1d 100644
--- a/src/store/context-menu/context-menu-reducer.ts
+++ b/src/store/context-menu/context-menu-reducer.ts
@@ -20,6 +20,8 @@ export interface ContextMenuResource {
kind: string;
name: string;
description?: string;
+ isTrashed?: boolean;
+ ownerUuid?: string;
}
const initialState = {
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index 50ec93d..ad70d9d 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -45,7 +45,7 @@ export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
if (router.location && !router.location.pathname.includes(resourceUrl)) {
dispatch(push(resourceUrl));
}
- dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(treeItem.data.uuid));
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE({ itemId: treeItem.data.uuid }));
}
const promise = treeItem.status === TreeItemStatus.LOADED
@@ -55,7 +55,7 @@ export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
promise
.then(() => dispatch<any>(() => {
if (itemMode === ItemMode.OPEN || itemMode === ItemMode.BOTH) {
- dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(treeItem.data.uuid));
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN({ itemId: treeItem.data.uuid }));
}
dispatch(projectPanelActions.RESET_PAGINATION());
dispatch(projectPanelActions.REQUEST_ITEMS());
@@ -63,7 +63,7 @@ export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
} else {
const uuid = services.authService.getUuid();
if (itemId === uuid) {
- dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(uuid));
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE({ itemId: uuid }));
dispatch(projectPanelActions.RESET_PAGINATION());
dispatch(projectPanelActions.REQUEST_ITEMS());
}
@@ -77,7 +77,7 @@ export const restoreBranch = (itemId: string) =>
await loadBranch(uuids, dispatch);
dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(SidePanelId.PROJECTS));
uuids.forEach(uuid => {
- dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(uuid));
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN({ itemId: uuid }));
});
};
diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts
index 2017658..bb5da19 100644
--- a/src/store/project/project-action.ts
+++ b/src/store/project/project-action.ts
@@ -11,6 +11,10 @@ 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 { snackbarActions } from "~/store/snackbar/snackbar-actions";
+import { trashPanelActions } from "~/store/trash-panel/trash-panel-action";
+import { sidePanelActions } from "~/store/side-panel/side-panel-action";
+import { SidePanelId } from "~/store/side-panel/side-panel-reducer";
export const projectActions = unionize({
OPEN_PROJECT_CREATOR: ofType<{ ownerUuid: string }>(),
@@ -23,8 +27,8 @@ export const projectActions = unionize({
REMOVE_PROJECT: ofType<string>(),
PROJECTS_REQUEST: ofType<string>(),
PROJECTS_SUCCESS: ofType<{ projects: ProjectResource[], parentItemId?: string }>(),
- TOGGLE_PROJECT_TREE_ITEM_OPEN: ofType<string>(),
- TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ofType<string>(),
+ TOGGLE_PROJECT_TREE_ITEM_OPEN: ofType<{ itemId: string, open?: boolean, recursive?: boolean }>(),
+ TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ofType<{ itemId: string, active?: boolean, recursive?: boolean }>(),
RESET_PROJECT_TREE_ACTIVITY: ofType<string>()
}, {
tag: 'type',
@@ -33,7 +37,7 @@ export const projectActions = unionize({
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({
@@ -70,4 +74,34 @@ export const updateProject = (project: Partial<ProjectResource>) =>
});
};
+export const toggleProjectTrashed = (resource: { uuid: string; name: string, isTrashed?: boolean, ownerUuid?: string }) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Working..." }));
+ if (resource.isTrashed) {
+ return services.groupsService.untrash(resource.uuid).then(() => {
+ dispatch<any>(getProjectList(resource.ownerUuid)).then(() => {
+ dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(SidePanelId.PROJECTS));
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN({ itemId: resource.ownerUuid!!, open: true, recursive: true }));
+ });
+ dispatch(trashPanelActions.REQUEST_ITEMS());
+ dispatch(snackbarActions.CLOSE_SNACKBAR());
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Restored from trash",
+ hideDuration: 2000
+ }));
+ });
+ } else {
+ return services.groupsService.trash(resource.uuid).then(() => {
+ dispatch<any>(getProjectList(resource.ownerUuid)).then(() => {
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN({ itemId: resource.ownerUuid!!, open: true, recursive: true }));
+ });
+ dispatch(snackbarActions.CLOSE_SNACKBAR());
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Added to trash",
+ hideDuration: 2000
+ }));
+ });
+ }
+ };
+
export type ProjectAction = UnionOf<typeof projectActions>;
diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts
index bb60e39..cf47010 100644
--- a/src/store/project/project-reducer.test.ts
+++ b/src/store/project/project-reducer.test.ts
@@ -99,7 +99,7 @@ describe('project-reducer', () => {
updater: { opened: false, uuid: '' }
};
- const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState.items[0].id));
+ const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE({ itemId: initialState.items[0].id }));
expect(state).toEqual(project);
});
@@ -131,7 +131,7 @@ describe('project-reducer', () => {
};
- const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState.items[0].id));
+ const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN({ itemId: initialState.items[0].id }));
expect(state).toEqual(project);
});
});
diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts
index bb07486..6b473cc 100644
--- a/src/store/project/project-reducer.ts
+++ b/src/store/project/project-reducer.ts
@@ -2,8 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import * as _ from "lodash";
-
import { projectActions, ProjectAction } from "./project-action";
import { TreeItem, TreeItemStatus } from "~/components/tree/tree";
import { ProjectResource } from "~/models/project";
@@ -26,25 +24,25 @@ interface ProjectUpdater {
uuid: string;
}
-export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
- let item;
+function rebuildTree<T>(tree: Array<TreeItem<T>>, action: (item: TreeItem<T>, visitedItems: TreeItem<T>[]) => void, visitedItems: TreeItem<T>[] = []): Array<TreeItem<T>> {
+ const newTree: Array<TreeItem<T>> = [];
for (const t of tree) {
- item = t.id === itemId
- ? t
- : findTreeItem(t.items ? t.items : [], itemId);
- if (item) {
- break;
- }
+ const items = t.items
+ ? rebuildTree(t.items, action, visitedItems.concat(t))
+ : undefined;
+ const item: TreeItem<T> = { ...t, items };
+ action(item, visitedItems);
+ newTree.push(item);
}
- return item;
+ return newTree;
}
-export function getActiveTreeItem<T>(tree: Array<TreeItem<T>>): TreeItem<T> | undefined {
+export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
let item;
for (const t of tree) {
- item = t.active
+ item = t.id === itemId
? t
- : getActiveTreeItem(t.items ? t.items : []);
+ : findTreeItem(t.items ? t.items : [], itemId);
if (item) {
break;
}
@@ -66,38 +64,6 @@ export function getTreePath<T>(tree: Array<TreeItem<T>>, itemId: string): Array<
return [];
}
-function resetTreeActivity<T>(tree: Array<TreeItem<T>>) {
- for (const t of tree) {
- t.active = false;
- resetTreeActivity(t.items ? t.items : []);
- }
-}
-
-function updateProjectTree(tree: Array<TreeItem<ProjectResource>>, projects: ProjectResource[], parentItemId?: string): Array<TreeItem<ProjectResource>> {
- let treeItem;
- if (parentItemId) {
- treeItem = findTreeItem(tree, parentItemId);
- if (treeItem) {
- treeItem.status = TreeItemStatus.LOADED;
- }
- }
- const items = projects.map(p => ({
- id: p.uuid,
- open: false,
- active: false,
- status: TreeItemStatus.INITIAL,
- data: p,
- items: []
- } as TreeItem<ProjectResource>));
-
- if (treeItem) {
- treeItem.items = items;
- return tree;
- }
-
- return items;
-}
-
const updateCreator = (state: ProjectState, creator: Partial<ProjectCreator>) => ({
...state,
creator: {
@@ -127,7 +93,6 @@ const initialState: ProjectState = {
}
};
-
export const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
return projectActions.match(action, {
OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }),
@@ -139,55 +104,68 @@ export const projectsReducer = (state: ProjectState = initialState, action: Proj
UPDATE_PROJECT_SUCCESS: () => updateProject(state, { opened: false, uuid: "" }),
REMOVE_PROJECT: () => state,
PROJECTS_REQUEST: itemId => {
- const items = _.cloneDeep(state.items);
- const item = findTreeItem(items, itemId);
- if (item) {
- item.status = TreeItemStatus.PENDING;
- state.items = items;
- }
- return { ...state, items };
- },
- PROJECTS_SUCCESS: ({ projects, parentItemId }) => {
- const items = _.cloneDeep(state.items);
return {
...state,
- items: updateProjectTree(items, projects, parentItemId)
+ items: rebuildTree(state.items, item => {
+ if (item.id === itemId) {
+ item.status = TreeItemStatus.PENDING;
+ }
+ })
};
},
- TOGGLE_PROJECT_TREE_ITEM_OPEN: itemId => {
- const items = _.cloneDeep(state.items);
- const item = findTreeItem(items, itemId);
- if (item) {
- item.open = !item.open;
- }
- return {
- ...state,
- items,
- currentItemId: itemId
- };
- },
- TOGGLE_PROJECT_TREE_ITEM_ACTIVE: itemId => {
- const items = _.cloneDeep(state.items);
- resetTreeActivity(items);
- const item = findTreeItem(items, itemId);
- if (item) {
- item.active = true;
- }
- return {
- ...state,
- items,
- currentItemId: itemId
- };
- },
- RESET_PROJECT_TREE_ACTIVITY: () => {
- const items = _.cloneDeep(state.items);
- resetTreeActivity(items);
+ PROJECTS_SUCCESS: ({ projects, parentItemId }) => {
+ const items = projects.map(p => ({
+ id: p.uuid,
+ open: false,
+ active: false,
+ status: TreeItemStatus.INITIAL,
+ data: p,
+ items: []
+ }));
return {
...state,
- items,
- currentItemId: ""
+ items: state.items.length > 0 ?
+ rebuildTree(state.items, item => {
+ if (item.id === parentItemId) {
+ item.status = TreeItemStatus.LOADED;
+ item.items = items;
+ }
+ }) : items
};
},
+ TOGGLE_PROJECT_TREE_ITEM_OPEN: ({ itemId, open, recursive }) => ({
+ ...state,
+ items: rebuildTree(state.items, (item, visitedItems) => {
+ if (item.id === itemId) {
+ if (recursive && open !== undefined) {
+ visitedItems.forEach(item => item.open = open);
+ }
+ item.open = open !== undefined ? open : !item.open;
+ }
+ }),
+ currentItemId: itemId
+ }),
+ TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ({ itemId, active, recursive }) => ({
+ ...state,
+ items: rebuildTree(state.items, (item, visitedItems) => {
+ item.active = false;
+ if (item.id === itemId) {
+ if (recursive && active !== undefined) {
+ visitedItems.forEach(item => item.active = active);
+ }
+
+ item.active = active !== undefined ? active : true;
+ }
+ }),
+ currentItemId: itemId
+ }),
+ RESET_PROJECT_TREE_ACTIVITY: () => ({
+ ...state,
+ items: rebuildTree(state.items, item => {
+ item.active = false;
+ }),
+ currentItemId: ""
+ }),
default: () => state
});
};
diff --git a/src/store/side-panel/side-panel-reducer.ts b/src/store/side-panel/side-panel-reducer.ts
index b68ce7a..56785e2 100644
--- a/src/store/side-panel/side-panel-reducer.ts
+++ b/src/store/side-panel/side-panel-reducer.ts
@@ -47,7 +47,7 @@ export const sidePanelItems = [
openAble: true,
activeAction: (dispatch: Dispatch, uuid: string) => {
dispatch(push(getProjectUrl(uuid)));
- dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(uuid));
+ dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE({ itemId: uuid }));
dispatch(projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
dispatch(projectPanelActions.RESET_PAGINATION());
dispatch(projectPanelActions.REQUEST_ITEMS());
diff --git a/src/store/trash-panel/trash-panel-middleware-service.ts b/src/store/trash-panel/trash-panel-middleware-service.ts
index 2d1dbf7..3a4da39 100644
--- a/src/store/trash-panel/trash-panel-middleware-service.ts
+++ b/src/store/trash-panel/trash-panel-middleware-service.ts
@@ -39,13 +39,12 @@ export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
const columnName = sortColumn && sortColumn.name === ProjectPanelColumnNames.NAME ? "name" : "createdAt";
order
.addOrder(sortDirection, columnName, GroupContentsResourcePrefix.COLLECTION)
- .addOrder(sortDirection, columnName, GroupContentsResourcePrefix.PROCESS)
.addOrder(sortDirection, columnName, GroupContentsResourcePrefix.PROJECT);
}
const userUuid = this.services.authService.getUuid()!;
- this.services.trashService
+ this.services.groupsService
.contents(userUuid, {
limit: dataExplorer.rowsPerPage,
offset: dataExplorer.page * dataExplorer.rowsPerPage,
@@ -53,7 +52,6 @@ export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
filters: new FilterBuilder()
.addIsA("uuid", typeFilters.map(f => f.type))
.addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
- .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS)
.addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
.getFilters(),
recursive: true,
diff --git a/src/views-components/context-menu/action-sets/collection-action-set.ts b/src/views-components/context-menu/action-sets/collection-action-set.ts
index 4561f9d..c8fb3cb 100644
--- a/src/views-components/context-menu/action-sets/collection-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-action-set.ts
@@ -8,6 +8,8 @@ import { toggleFavorite } from "~/store/favorites/favorites-actions";
import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "~/components/icon/icon";
import { openUpdater } from "~/store/collections/updater/collection-updater-action";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
+import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
+import { toggleCollectionTrashed } from "~/store/collections/collection-trash-actions";
export const collectionActionSet: ContextMenuActionSet = [[
{
@@ -40,6 +42,12 @@ export const collectionActionSet: ContextMenuActionSet = [[
}
},
{
+ component: ToggleTrashAction,
+ execute: (dispatch, resource) => {
+ dispatch<any>(toggleCollectionTrashed(resource));
+ }
+ },
+ {
icon: CopyIcon,
name: "Copy to project",
execute: (dispatch, resource) => {
diff --git a/src/views-components/context-menu/action-sets/collection-resource-action-set.ts b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts
index 7d8364b..dbc9e23 100644
--- a/src/views-components/context-menu/action-sets/collection-resource-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts
@@ -8,6 +8,8 @@ import { toggleFavorite } from "~/store/favorites/favorites-actions";
import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "~/components/icon/icon";
import { openUpdater } from "~/store/collections/updater/collection-updater-action";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
+import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
+import { toggleCollectionTrashed } from "~/store/collections/collection-trash-actions";
export const collectionResourceActionSet: ContextMenuActionSet = [[
{
@@ -40,6 +42,12 @@ export const collectionResourceActionSet: ContextMenuActionSet = [[
}
},
{
+ component: ToggleTrashAction,
+ execute: (dispatch, resource) => {
+ dispatch<any>(toggleCollectionTrashed(resource));
+ }
+ },
+ {
icon: CopyIcon,
name: "Copy to project",
execute: (dispatch, resource) => {
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 1b000c8..d2412e7 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
@@ -5,12 +5,13 @@
import { reset, 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, toggleProjectTrashed } from "~/store/project/project-action";
import { NewProjectIcon, RenameIcon } 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 { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
export const projectActionSet: ContextMenuActionSet = [[
{
@@ -36,5 +37,11 @@ export const projectActionSet: ContextMenuActionSet = [[
dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
});
}
+ },
+ {
+ component: ToggleTrashAction,
+ execute: (dispatch, resource) => {
+ dispatch<any>(toggleProjectTrashed(resource));
+ }
}
]];
diff --git a/src/views-components/context-menu/actions/trash-action.tsx b/src/views-components/context-menu/actions/trash-action.tsx
new file mode 100644
index 0000000..d6c8b2f
--- /dev/null
+++ b/src/views-components/context-menu/actions/trash-action.tsx
@@ -0,0 +1,29 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { ListItemIcon, ListItemText, ListItem } from "@material-ui/core";
+import { RestoreFromTrashIcon, TrashIcon } from "~/components/icon/icon";
+import { connect } from "react-redux";
+import { RootState } from "~/store/store";
+
+const mapStateToProps = (state: RootState, props: { onClick: () => {} }) => ({
+ isTrashed: state.contextMenu.resource && state.contextMenu.resource.isTrashed,
+ onClick: props.onClick
+});
+
+export const ToggleTrashAction = connect(mapStateToProps)((props: { isTrashed?: boolean, onClick: () => void }) =>
+ <ListItem button
+ onClick={props.onClick}>
+ <ListItemIcon>
+ {props.isTrashed
+ ? <RestoreFromTrashIcon/>
+ : <TrashIcon/>}
+ </ListItemIcon>
+ <ListItemText style={{ textDecoration: 'none' }}>
+ {props.isTrashed
+ ? <>Restore</>
+ : <>Move to trash</>}
+ </ListItemText>
+ </ListItem >);
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index 374cb95..9e32700 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -65,7 +65,6 @@ export const CollectionPanel = withStyles(styles)(
tags: state.collectionPanel.tags
}))(
class extends React.Component<CollectionPanelProps> {
-
render() {
const { classes, item, tags, onContextMenu } = this.props;
return <div>
@@ -131,7 +130,6 @@ export const CollectionPanel = withStyles(styles)(
onItemRouteChange(match.params.id);
}
}
-
}
)
);
diff --git a/src/views/favorite-panel/favorite-panel-item.ts b/src/views/favorite-panel/favorite-panel-item.ts
index 842b6d6..d2e2331 100644
--- a/src/views/favorite-panel/favorite-panel-item.ts
+++ b/src/views/favorite-panel/favorite-panel-item.ts
@@ -14,6 +14,7 @@ export interface FavoritePanelItem {
lastModified: string;
fileSize?: number;
status?: string;
+ isTrashed?: boolean;
}
export function resourceToDataItem(r: GroupContentsResource): FavoritePanelItem {
@@ -24,6 +25,7 @@ export function resourceToDataItem(r: GroupContentsResource): FavoritePanelItem
url: "",
owner: r.ownerUuid,
lastModified: r.modifiedAt,
- status: r.kind === ResourceKind.PROCESS ? r.state : undefined
+ status: r.kind === ResourceKind.PROCESS ? r.state : undefined,
+ isTrashed: r.kind === ResourceKind.GROUP || r.kind === ResourceKind.COLLECTION ? r.isTrashed: undefined
};
}
diff --git a/src/views/project-panel/project-panel-item.ts b/src/views/project-panel/project-panel-item.ts
index f031859..ecc5a7d 100644
--- a/src/views/project-panel/project-panel-item.ts
+++ b/src/views/project-panel/project-panel-item.ts
@@ -15,6 +15,7 @@ export interface ProjectPanelItem {
lastModified: string;
fileSize?: number;
status?: string;
+ isTrashed?: boolean;
}
export function resourceToDataItem(r: GroupContentsResource): ProjectPanelItem {
@@ -26,6 +27,7 @@ export function resourceToDataItem(r: GroupContentsResource): ProjectPanelItem {
url: "",
owner: r.ownerUuid,
lastModified: r.modifiedAt,
- status: r.kind === ResourceKind.PROCESS ? r.state : undefined
+ status: r.kind === ResourceKind.PROCESS ? r.state : undefined,
+ isTrashed: r.kind === ResourceKind.GROUP || r.kind === ResourceKind.COLLECTION ? r.isTrashed: undefined
};
}
diff --git a/src/views/trash-panel/trash-panel-item.ts b/src/views/trash-panel/trash-panel-item.ts
index 8916458..a2f59ac 100644
--- a/src/views/trash-panel/trash-panel-item.ts
+++ b/src/views/trash-panel/trash-panel-item.ts
@@ -9,6 +9,7 @@ export interface TrashPanelItem {
uuid: string;
name: string;
kind: string;
+ owner: string;
fileSize?: number;
trashAt?: string;
deleteAt?: string;
@@ -20,6 +21,7 @@ export function resourceToDataItem(r: GroupContentsResource): TrashPanelItem {
uuid: r.uuid,
name: r.name,
kind: r.kind,
+ owner: r.ownerUuid,
trashAt: (r as TrashResource).trashAt,
deleteAt: (r as TrashResource).deleteAt,
isTrashed: (r as TrashResource).isTrashed
diff --git a/src/views/trash-panel/trash-panel.tsx b/src/views/trash-panel/trash-panel.tsx
index c5a302e..fa73c0b 100644
--- a/src/views/trash-panel/trash-panel.tsx
+++ b/src/views/trash-panel/trash-panel.tsx
@@ -11,7 +11,6 @@ import { DataColumns } from '~/components/data-table/data-table';
import { RouteComponentProps } from 'react-router';
import { RootState } from '~/store/store';
import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
-import { ProcessState } from '~/models/process';
import { SortDirection } from '~/components/data-table/data-column';
import { ResourceKind } from '~/models/resource';
import { resourceLabel } from '~/common/labels';
@@ -41,7 +40,7 @@ export enum TrashPanelColumnNames {
}
export interface TrashPanelFilter extends DataTableFilterItem {
- type: ResourceKind | ProcessState;
+ type: ResourceKind;
}
export const columns: DataColumns<TrashPanelItem, TrashPanelFilter> = [
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index a3f7624..8028f2c 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -219,6 +219,8 @@ export const Workbench = withStyles(styles)(
toggleOpen={itemId => this.props.dispatch(setProjectItem(itemId, ItemMode.OPEN))}
onContextMenu={(event, item) => this.openContextMenu(event, {
uuid: item.data.uuid,
+ ownerUuid: item.data.ownerUuid || this.props.authService.getUuid(),
+ isTrashed: item.data.isTrashed,
name: item.data.name,
kind: ContextMenuKind.PROJECT
})}
@@ -268,6 +270,7 @@ export const Workbench = withStyles(styles)(
uuid: item.uuid,
name: item.name,
description: item.description,
+ isTrashed: item.isTrashed,
kind: ContextMenuKind.COLLECTION
});
}}
@@ -290,6 +293,8 @@ export const Workbench = withStyles(styles)(
uuid: item.uuid,
name: item.name,
description: item.description,
+ isTrashed: item.isTrashed,
+ ownerUuid: item.owner || this.props.authService.getUuid(),
kind
});
}}
@@ -318,6 +323,7 @@ export const Workbench = withStyles(styles)(
this.openContextMenu(event, {
uuid: item.uuid,
name: item.name,
+ isTrashed: item.isTrashed,
kind,
});
}}
@@ -341,26 +347,28 @@ export const Workbench = withStyles(styles)(
renderTrashPanel = (props: RouteComponentProps<{ id: string }>) => <TrashPanel
onItemRouteChange={() => this.props.dispatch(trashPanelActions.REQUEST_ITEMS())}
onContextMenu={(event, item) => {
- const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.RESOURCE;
+ const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.COLLECTION;
this.openContextMenu(event, {
uuid: item.uuid,
name: item.name,
+ isTrashed: item.isTrashed,
+ ownerUuid: item.owner,
kind,
});
}}
onDialogOpen={this.handleProjectCreationDialogOpen}
onItemClick={item => {
- this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
+ // 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));
- }
+ // 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} />
@@ -410,7 +418,7 @@ export const Workbench = withStyles(styles)(
this.props.dispatch(collectionCreateActions.OPEN_COLLECTION_CREATOR({ ownerUuid: itemUuid }));
}
- openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: { name: string; uuid: string; description?: string; kind: ContextMenuKind; }) => {
+ openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: { name: string; uuid: string; description?: string; isTrashed?: boolean, ownerUuid?: string, kind: ContextMenuKind; }) => {
event.preventDefault();
this.props.dispatch(
contextMenuActions.OPEN_CONTEXT_MENU({
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list