Add trash view
[arvados-workbench2.git] / src / store / project / project-reducer.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as _ from "lodash";
6
7 import { projectActions, ProjectAction } from "./project-action";
8 import { TreeItem, TreeItemStatus } from "~/components/tree/tree";
9 import { ProjectResource } from "~/models/project";
10
11 export type ProjectState = {
12     items: Array<TreeItem<ProjectResource>>,
13     currentItemId: string,
14     creator: ProjectCreator,
15     updater: ProjectUpdater
16 };
17
18 interface ProjectCreator {
19     opened: boolean;
20     ownerUuid: string;
21     error?: string;
22 }
23
24 interface ProjectUpdater {
25     opened: boolean;
26     uuid: string;
27 }
28
29 export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
30     let item;
31     for (const t of tree) {
32         item = t.id === itemId
33             ? t
34             : findTreeItem(t.items ? t.items : [], itemId);
35         if (item) {
36             break;
37         }
38     }
39     return item;
40 }
41
42 export function getActiveTreeItem<T>(tree: Array<TreeItem<T>>): TreeItem<T> | undefined {
43     let item;
44     for (const t of tree) {
45         item = t.active
46             ? t
47             : getActiveTreeItem(t.items ? t.items : []);
48         if (item) {
49             break;
50         }
51     }
52     return item;
53 }
54
55 export function getTreePath<T>(tree: Array<TreeItem<T>>, itemId: string): Array<TreeItem<T>> {
56     for (const item of tree) {
57         if (item.id === itemId) {
58             return [item];
59         } else {
60             const branch = getTreePath(item.items || [], itemId);
61             if (branch.length > 0) {
62                 return [item, ...branch];
63             }
64         }
65     }
66     return [];
67 }
68
69 function resetTreeActivity<T>(tree: Array<TreeItem<T>>) {
70     for (const t of tree) {
71         t.active = false;
72         resetTreeActivity(t.items ? t.items : []);
73     }
74 }
75
76 function updateProjectTree(tree: Array<TreeItem<ProjectResource>>, projects: ProjectResource[], parentItemId?: string): Array<TreeItem<ProjectResource>> {
77     let treeItem;
78     if (parentItemId) {
79         treeItem = findTreeItem(tree, parentItemId);
80         if (treeItem) {
81             treeItem.status = TreeItemStatus.LOADED;
82         }
83     }
84     const items = projects.map(p => ({
85         id: p.uuid,
86         open: false,
87         active: false,
88         status: TreeItemStatus.INITIAL,
89         data: p,
90         items: []
91     } as TreeItem<ProjectResource>));
92
93     if (treeItem) {
94         treeItem.items = items;
95         return tree;
96     }
97
98     return items;
99 }
100
101 const updateCreator = (state: ProjectState, creator: Partial<ProjectCreator>) => ({
102     ...state,
103     creator: {
104         ...state.creator,
105         ...creator
106     }
107 });
108
109 const updateProject = (state: ProjectState, updater?: Partial<ProjectUpdater>) => ({
110     ...state,
111     updater: {
112         ...state.updater,
113         ...updater
114     }
115 });
116
117 const initialState: ProjectState = {
118     items: [],
119     currentItemId: "",
120     creator: {
121         opened: false,
122         ownerUuid: ""
123     },
124     updater: {
125         opened: false,
126         uuid: ''
127     }
128 };
129
130
131 export const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
132     return projectActions.match(action, {
133         OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }),
134         CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }),
135         CREATE_PROJECT: () => updateCreator(state, { error: undefined }),
136         CREATE_PROJECT_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "" }),
137         OPEN_PROJECT_UPDATER: ({ uuid }) => updateProject(state, { uuid, opened: true }),
138         CLOSE_PROJECT_UPDATER: () => updateProject(state, { opened: false, uuid: "" }),
139         UPDATE_PROJECT_SUCCESS: () => updateProject(state, { opened: false, uuid: "" }),
140         REMOVE_PROJECT: () => state,
141         PROJECTS_REQUEST: itemId => {
142             const items = _.cloneDeep(state.items);
143             const item = findTreeItem(items, itemId);
144             if (item) {
145                 item.status = TreeItemStatus.PENDING;
146                 state.items = items;
147             }
148             return { ...state, items };
149         },
150         PROJECTS_SUCCESS: ({ projects, parentItemId }) => {
151             const items = _.cloneDeep(state.items);
152             return {
153                 ...state,
154                 items: updateProjectTree(items, projects, parentItemId)
155             };
156         },
157         TOGGLE_PROJECT_TREE_ITEM_OPEN: itemId => {
158             const items = _.cloneDeep(state.items);
159             const item = findTreeItem(items, itemId);
160             if (item) {
161                 item.open = !item.open;
162             }
163             return {
164                 ...state,
165                 items,
166                 currentItemId: itemId
167             };
168         },
169         TOGGLE_PROJECT_TREE_ITEM_ACTIVE: itemId => {
170             const items = _.cloneDeep(state.items);
171             resetTreeActivity(items);
172             const item = findTreeItem(items, itemId);
173             if (item) {
174                 item.active = true;
175             }
176             return {
177                 ...state,
178                 items,
179                 currentItemId: itemId
180             };
181         },
182         RESET_PROJECT_TREE_ACTIVITY: () => {
183             const items = _.cloneDeep(state.items);
184             resetTreeActivity(items);
185             return {
186                 ...state,
187                 items,
188                 currentItemId: ""
189             };
190         },
191         default: () => state
192     });
193 };