refs #master Merge branch 'origin/master' into 14186-progress-indicator-store
authorDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 18 Sep 2018 11:32:23 +0000 (13:32 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 18 Sep 2018 11:32:23 +0000 (13:32 +0200)
# Conflicts:
# src/services/groups-service/groups-service.ts
# src/store/store.ts
# src/views/project-panel/project-panel.tsx
# src/views/workbench/workbench.tsx

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

26 files changed:
package.json
src/common/custom-theme.ts
src/components/data-table-filters/data-table-filters.tsx
src/components/icon/icon.tsx
src/components/search-bar/search-bar.tsx
src/components/search-input/search-input.tsx
src/routes/route-change-handlers.ts
src/routes/routes.ts
src/services/ancestors-service/ancestors-service.ts
src/services/groups-service/groups-service.ts
src/store/breadcrumbs/breadcrumbs-actions.ts
src/store/navigation/navigation-action.ts
src/store/project-panel/project-panel-middleware-service.ts
src/store/shared-with-me-panel/shared-with-me-middleware-service.ts [new file with mode: 0644]
src/store/shared-with-me-panel/shared-with-me-panel-actions.ts [new file with mode: 0644]
src/store/side-panel-tree/side-panel-tree-actions.ts
src/store/side-panel/side-panel-action.ts
src/store/store.ts
src/store/workbench/workbench-actions.ts
src/views-components/details-panel/details-data.tsx
src/views-components/side-panel-button/side-panel-button.tsx
src/views/project-panel/project-panel.tsx
src/views/shared-with-me-panel/shared-with-me-panel.tsx [new file with mode: 0644]
src/views/workbench/workbench.tsx
typings/global.d.ts
yarn.lock

index dd16eea932fdce1151b53295346ed7cf0345ef0f..620ff5a6d03a6d7d616ab9d32b0ec2f47c0530ac 100644 (file)
@@ -21,6 +21,7 @@
     "react-router-dom": "4.3.1",
     "react-router-redux": "5.0.0-alpha.9",
     "react-scripts-ts": "2.17.0",
+    "react-splitter-layout": "3.0.1",
     "react-transition-group": "2.4.0",
     "redux": "4.0.0",
     "redux-thunk": "2.3.0",
index be8e77ff68b37698ec8f8fd6d438f7f03a29d5b5..ff0eb5e34ce7449f672464485b5879cc98f5d382 100644 (file)
@@ -10,6 +10,7 @@ import grey from '@material-ui/core/colors/grey';
 import green from '@material-ui/core/colors/green';
 import yellow from '@material-ui/core/colors/yellow';
 import red from '@material-ui/core/colors/red';
+import teal from '@material-ui/core/colors/teal';
 
 export interface ArvadosThemeOptions extends ThemeOptions {
     customs: any;
@@ -28,10 +29,8 @@ interface Colors {
     blue500: string;
 }
 
-const red900 = red["900"];
+const arvadosPurple = '#361336';
 const purple800 = purple["800"];
-const grey200 = grey["200"];
-const grey300 = grey["300"];
 const grey500 = grey["500"];
 const grey600 = grey["600"];
 const grey700 = grey["700"];
@@ -55,7 +54,7 @@ export const themeOptions: ArvadosThemeOptions = {
         },
         MuiAppBar: {
             colorPrimary: {
-                backgroundColor: purple800
+                backgroundColor: arvadosPurple
             }
         },
         MuiTabs: {
@@ -63,13 +62,13 @@ export const themeOptions: ArvadosThemeOptions = {
                 color: grey600
             },
             indicator: {
-                backgroundColor: purple800
+                backgroundColor: arvadosPurple
             }
         },
         MuiTab: {
             selected: {
                 fontWeight: 700,
-                color: purple800
+                color: arvadosPurple
             }
         },
         MuiList: {
@@ -108,7 +107,7 @@ export const themeOptions: ArvadosThemeOptions = {
             },
             underline: {
                 '&:after': {
-                    borderBottomColor: purple800
+                    borderBottomColor: arvadosPurple
                 },
                 '&:hover:not($disabled):not($focused):not($error):before': {
                     borderBottom: '1px solid inherit'
@@ -121,7 +120,7 @@ export const themeOptions: ArvadosThemeOptions = {
             },
             focused: {
                 "&$focused:not($error)": {
-                    color: purple800
+                    color: arvadosPurple
                 }
             }
         }
@@ -133,8 +132,9 @@ export const themeOptions: ArvadosThemeOptions = {
     },
     palette: {
         primary: {
-            main: rocheBlue,
-            dark: blue.A100
+            main: teal.A700,
+            dark: blue.A100,
+            contrastText: '#fff'
         }
     }
 };
index d288a5a3dbfc8997cee987598cfb767048e748f9..b8a6e834869eb5a3e59aa1125b5e8a867d8fe5e6 100644 (file)
@@ -18,7 +18,8 @@ import {
     Card,
     CardActions,
     Typography,
-    CardContent
+    CardContent,
+    Tooltip
 } from "@material-ui/core";
 import * as classnames from "classnames";
 import { DefaultTransformOrigin } from "../popover/helpers";
@@ -88,16 +89,18 @@ export const DataTableFilters = withStyles(styles)(
             const { name, classes, children } = this.props;
             const isActive = this.state.filters.some(f => f.selected);
             return <>
-                <ButtonBase
-                    className={classnames([classes.root, { [classes.active]: isActive }])}
-                    component="span"
-                    onClick={this.open}
-                    disableRipple>
-                    {children}
-                    <i className={classnames(["fas fa-filter", classes.icon])}
-                        data-fa-transform="shrink-3"
-                        ref={this.icon} />
-                </ButtonBase>
+                <Tooltip title='Filters'>
+                    <ButtonBase
+                        className={classnames([classes.root, { [classes.active]: isActive }])}
+                        component="span"
+                        onClick={this.open}
+                        disableRipple>
+                        {children}
+                        <i className={classnames(["fas fa-filter", classes.icon])}
+                            data-fa-transform="shrink-3"
+                            ref={this.icon} />
+                    </ButtonBase>
+                </Tooltip>
                 <Popover
                     anchorEl={this.state.anchorEl}
                     open={!!this.state.anchorEl}
index c0668b8291fa3e7b89010ef0638cb8f2c16bafdd..06a56172ffb52077d48e6c05734b5a627dd4b697 100644 (file)
@@ -64,8 +64,11 @@ export const DetailsIcon: IconType = (props) => <Info {...props} />;
 export const DownloadIcon: IconType = (props) => <GetApp {...props} />;
 export const FavoriteIcon: IconType = (props) => <Star {...props} />;
 export const HelpIcon: IconType = (props) => <Help {...props} />;
+export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
+export const ImportContactsIcon: IconType = (props) => <ImportContacts {...props} />;
 export const InputIcon: IconType = (props) => <InsertDriveFile {...props} />;
 export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
+export const MailIcon: IconType = (props) => <Mail {...props} />;
 export const MoreOptionsIcon: IconType = (props) => <MoreVert {...props} />;
 export const MoveToIcon: IconType = (props) => <Input {...props} />;
 export const NewProjectIcon: IconType = (props) => <CreateNewFolder {...props} />;
@@ -92,6 +95,3 @@ export const TrashIcon: IconType = (props) => <Delete {...props} />;
 export const UserPanelIcon: IconType = (props) => <Person {...props} />;
 export const UsedByIcon: IconType = (props) => <Folder {...props} />;
 export const WorkflowIcon: IconType = (props) => <Code {...props} />;
-export const ImportContactsIcon: IconType = (props) => <ImportContacts {...props} />;
-export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
-export const MailIcon: IconType = (props) => <Mail {...props} />;
index de9e7de5261c299870cc16d4618971869dfc273b..ab25e1379ddee64f55a016bfeff8680754d1d3be 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { IconButton, Paper, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core';
+import { IconButton, Paper, StyleRulesCallback, withStyles, WithStyles, Tooltip } from '@material-ui/core';
 import SearchIcon from '@material-ui/icons/Search';
 
 type CssRules = 'container' | 'input' | 'button';
@@ -58,7 +58,7 @@ export const SearchBar = withStyles(styles)(
         timeout: number;
 
         render() {
-            const {classes} = this.props;
+            const { classes } = this.props;
             return <Paper className={classes.container}>
                 <form onSubmit={this.handleSubmit}>
                     <input
@@ -68,19 +68,21 @@ export const SearchBar = withStyles(styles)(
                         value={this.state.value}
                     />
                     <IconButton className={classes.button}>
-                        <SearchIcon/>
+                        <Tooltip title='Search'>
+                            <SearchIcon />
+                        </Tooltip>
                     </IconButton>
                 </form>
             </Paper>;
         }
 
         componentDidMount() {
-            this.setState({value: this.props.value});
+            this.setState({ value: this.props.value });
         }
 
         componentWillReceiveProps(nextProps: SearchBarProps) {
             if (nextProps.value !== this.props.value) {
-                this.setState({value: nextProps.value});
+                this.setState({ value: nextProps.value });
             }
         }
 
@@ -96,7 +98,7 @@ export const SearchBar = withStyles(styles)(
 
         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
             clearTimeout(this.timeout);
-            this.setState({value: event.target.value});
+            this.setState({ value: event.target.value });
             this.timeout = window.setTimeout(
                 () => this.props.onSearch(this.state.value),
                 this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
index dc02cd3d13ba13bedd50daf01963bb5a76d98f80..da2f3a9b6ab07dccd3691a5e0396ca60ee7f16f8 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { IconButton, StyleRulesCallback, withStyles, WithStyles, FormControl, InputLabel, Input, InputAdornment } from '@material-ui/core';
+import { IconButton, StyleRulesCallback, withStyles, WithStyles, FormControl, InputLabel, Input, InputAdornment, Tooltip } from '@material-ui/core';
 import SearchIcon from '@material-ui/icons/Search';
 
 type CssRules = 'container' | 'input' | 'button';
@@ -70,10 +70,12 @@ export const SearchInput = withStyles(styles)(
                             <InputAdornment position="end">
                                 <IconButton
                                     onClick={this.handleSubmit}>
-                                    <SearchIcon/>
+                                    <Tooltip title='Search'>
+                                        <SearchIcon />
+                                    </Tooltip>
                                 </IconButton>
                             </InputAdornment>
-                        }/>
+                        } />
                 </FormControl>
             </form>;
         }
index 00fb4bc05acbfcf8a4dc47e3c20f19516443cf19..33e0bef753c6f5535a18a02e94b7e8e3012052ab 100644 (file)
@@ -4,9 +4,11 @@
 
 import { History, Location } from 'history';
 import { RootStore } from '~/store/store';
-import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute } from './routes';
+import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute } from './routes';
 import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog } from '~/store/workbench/workbench-actions';
 import { navigateToRootProject } from '~/store/navigation/navigation-action';
+import { navigateToSharedWithMe } from '../store/navigation/navigation-action';
+import { loadSharedWithMe } from '../store/workbench/workbench-actions';
 
 export const addRouteChangeHandlers = (history: History, store: RootStore) => {
     const handler = handleLocationChange(store);
@@ -22,7 +24,8 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     const trashMatch = matchTrashRoute(pathname);
     const processMatch = matchProcessRoute(pathname);
     const processLogMatch = matchProcessLogRoute(pathname);
-    
+    const sharedWithMeMatch = matchSharedWithMeRoute(pathname);
+
     if (projectMatch) {
         store.dispatch(loadProject(projectMatch.params.id));
     } else if (collectionMatch) {
@@ -37,5 +40,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
         store.dispatch(loadProcessLog(processLogMatch.params.id));
     } else if (rootMatch) {
         store.dispatch(navigateToRootProject);
+    } else if (sharedWithMeMatch) {
+        store.dispatch(loadSharedWithMe);
     }
 };
index 29941b47e89989cec2417e653c8364fa5b3a21f7..fb28bd05bee5ff360c17cedd01bf2487d7cf4f22 100644 (file)
@@ -15,7 +15,8 @@ export const Routes = {
     PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`,
     FAVORITES: '/favorites',
     TRASH: '/trash',
-    PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`
+    PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`,
+    SHARED_WITH_ME: '/shared-with-me',
 };
 
 export const getResourceUrl = (uuid: string) => {
@@ -60,3 +61,6 @@ export const matchProcessRoute = (route: string) =>
 
 export const matchProcessLogRoute = (route: string) =>
     matchPath<ResourceRouteParams>(route, { path: Routes.PROCESS_LOGS });
+
+export const matchSharedWithMeRoute = (route: string) =>
+    matchPath(route, { path: Routes.SHARED_WITH_ME });
index f90b4a3053ca1744c2228a51c4c45177bf644c87..44e4eef5c944b271eba16c558b43a4d700c4f886 100644 (file)
@@ -14,17 +14,21 @@ export class AncestorService {
         private userService: UserService
     ) { }
 
-    async ancestors(uuid: string, rootUuid: string): Promise<Array<UserResource | GroupResource | TrashableResource>> {
+    async ancestors(uuid: string, rootUuid: string): Promise<Array<UserResource | GroupResource>> {
         const service = this.getService(extractUuidObjectType(uuid));
         if (service) {
-            const resource = await service.get(uuid);
-            if (uuid === rootUuid) {
-                return [resource];
-            } else {
-                return [
-                    ...await this.ancestors(resource.ownerUuid, rootUuid),
-                    resource
-                ];
+            try {
+                const resource = await service.get(uuid);
+                if (uuid === rootUuid) {
+                    return [resource];
+                } else {
+                    return [
+                        ...await this.ancestors(resource.ownerUuid, rootUuid),
+                        resource
+                    ];
+                }
+            } catch (e) {
+                return [];
             }
         } else {
             return [];
index c2b559b78b164b1139aa0df920c255c6991b584a..e705b6e5377541f5eaa245d2cf7afbc0b1c40dcf 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as _ from "lodash";
-import { CommonResourceService, ListResults } from "~/services/common-service/common-resource-service";
+import { CommonResourceService, ListResults, ListArguments } from '~/services/common-service/common-resource-service';
 import { AxiosInstance } from "axios";
 import { CollectionResource } from "~/models/collection";
 import { ProjectResource } from "~/models/project";
@@ -11,6 +11,7 @@ import { ProcessResource } from "~/models/process";
 import { TrashableResource } from "~/models/resource";
 import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
 import { ApiActions } from "~/services/api/api-actions";
+import { GroupResource } from "~/models/group";
 
 export interface ContentsArguments {
     limit?: number;
@@ -21,12 +22,16 @@ export interface ContentsArguments {
     includeTrash?: boolean;
 }
 
+export interface SharedArguments extends ListArguments {
+    include?: string;
+}
+
 export type GroupContentsResource =
     CollectionResource |
     ProjectResource |
     ProcessResource;
 
-export class GroupsService<T extends TrashableResource = TrashableResource> extends TrashableResourceService<T> {
+export class GroupsService<T extends GroupResource = GroupResource> extends TrashableResourceService<T> {
 
     constructor(serverApi: AxiosInstance, actions: ApiActions) {
         super(serverApi, "groups", actions);
@@ -47,6 +52,14 @@ export class GroupsService<T extends TrashableResource = TrashableResource> exte
             this.actions
         );
     }
+
+    shared(params: SharedArguments = {}): Promise<ListResults<GroupContentsResource>> {
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .get(this.resourceType + 'shared', { params }),
+            this.actions
+        );
+    }
 }
 
 export enum GroupContentsResourcePrefix {
index cc7bb1dd2d2d6f9237e448dfe0c1fbca4692a6f3..4ac07b3bd5bbb39643e87d20fbba068999f4f03f 100644 (file)
@@ -7,9 +7,13 @@ import { RootState } from '~/store/store';
 import { Breadcrumb } from '~/components/breadcrumbs/breadcrumbs';
 import { getResource } from '~/store/resources/resources';
 import { TreePicker } from '../tree-picker/tree-picker';
-import { getSidePanelTreeBranch } from '../side-panel-tree/side-panel-tree-actions';
+import { getSidePanelTreeBranch, getSidePanelTreeNodeAncestorsIds } from '../side-panel-tree/side-panel-tree-actions';
 import { propertiesActions } from '../properties/properties-actions';
 import { getProcess } from '~/store/processes/process';
+import { ServiceRepository } from '~/services/services';
+import { SidePanelTreeCategory, activateSidePanelTreeItem } from '~/store/side-panel-tree/side-panel-tree-actions';
+import { updateResources } from '../resources/resources-actions';
+import { ResourceKind } from '~/models/resource';
 
 export const BREADCRUMBS = 'breadcrumbs';
 
@@ -35,7 +39,33 @@ export const setSidePanelBreadcrumbs = (uuid: string) =>
         dispatch(setBreadcrumbs(breadcrumbs));
     };
 
-export const setProjectBreadcrumbs = setSidePanelBreadcrumbs;
+export const setSharedWithMeBreadcrumbs = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const ancestors = await services.ancestorsService.ancestors(uuid, '');
+        dispatch(updateResources(ancestors));
+        const initialBreadcrumbs: ResourceBreadcrumb[] = [
+            { label: SidePanelTreeCategory.SHARED_WITH_ME, uuid: SidePanelTreeCategory.SHARED_WITH_ME }
+        ];
+        const breadrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
+            ancestor.kind === ResourceKind.GROUP
+                ? [...breadcrumbs, { label: ancestor.name, uuid: ancestor.uuid }]
+                : breadcrumbs,
+            initialBreadcrumbs);
+
+        dispatch(setBreadcrumbs(breadrumbs));
+    };
+
+export const setProjectBreadcrumbs = (uuid: string) =>
+    (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+        const ancestors = getSidePanelTreeNodeAncestorsIds(uuid)(getState().treePicker);
+        const rootUuid = services.authService.getUuid();
+        if (uuid === rootUuid ||ancestors.find(uuid => uuid === rootUuid)) {
+            dispatch(setSidePanelBreadcrumbs(uuid));
+        } else {
+            dispatch(setSharedWithMeBreadcrumbs(uuid));
+            dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
+        }
+    };
 
 export const setCollectionBreadcrumbs = (collectionUuid: string) =>
     (dispatch: Dispatch, getState: () => RootState) => {
index 0e87769e24a57095f4e74fd578f5c46ed1358c59..c68c5398754e13c6a439af6176cf53a36def01d7 100644 (file)
@@ -24,6 +24,8 @@ export const navigateTo = (uuid: string) =>
         }
         if (uuid === SidePanelTreeCategory.FAVORITES) {
             dispatch<any>(navigateToFavorites);
+        } else if(uuid === SidePanelTreeCategory.SHARED_WITH_ME){
+            dispatch(navigateToSharedWithMe);
         }
     };
 
@@ -44,4 +46,6 @@ export const navigateToRootProject = (dispatch: Dispatch, getState: () => RootSt
     if (rootProjectUuid) {
         dispatch(navigateToProject(rootProjectUuid));
     }
-};
\ No newline at end of file
+};
+
+export const navigateToSharedWithMe = push(Routes.SHARED_WITH_ME);
index 84eaa856ece812b602e86d450c20a48b1164ef06..09e76ae28b99ef30697228df98f97b78531559f7 100644 (file)
@@ -76,19 +76,19 @@ export const loadMissingProcessesInformation = (resources: GroupContentsResource
         }
     };
 
-const setItems = (listResults: ListResults<GroupContentsResource>) =>
+export const setItems = (listResults: ListResults<GroupContentsResource>) =>
     projectPanelActions.SET_ITEMS({
         ...listResultsToDataExplorerItemsMeta(listResults),
         items: listResults.items.map(resource => resource.uuid),
     });
 
-const getParams = (dataExplorer: DataExplorer) => ({
+export const getParams = (dataExplorer: DataExplorer) => ({
     ...dataExplorerToListParams(dataExplorer),
     order: getOrder(dataExplorer),
     filters: getFilters(dataExplorer),
 });
 
-const getFilters = (dataExplorer: DataExplorer) => {
+export const getFilters = (dataExplorer: DataExplorer) => {
     const columns = dataExplorer.columns as DataColumns<string, ProjectPanelFilter>;
     const typeFilters = getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.TYPE);
     const statusFilters = getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.STATUS);
@@ -100,7 +100,7 @@ const getFilters = (dataExplorer: DataExplorer) => {
         .getFilters();
 };
 
-const getOrder = (dataExplorer: DataExplorer) => {
+export const getOrder = (dataExplorer: DataExplorer) => {
     const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
     const order = new OrderBuilder<ProjectResource>();
     if (sortColumn) {
diff --git a/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts b/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts
new file mode 100644 (file)
index 0000000..1ebb13e
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { DataExplorerMiddlewareService, listResultsToDataExplorerItemsMeta, dataExplorerToListParams } from '../data-explorer/data-explorer-middleware-service';
+import { ServiceRepository } from '~/services/services';
+import { MiddlewareAPI, Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { getDataExplorer, DataExplorer } from '~/store/data-explorer/data-explorer-reducer';
+import { updateFavorites } from '~/store/favorites/favorites-actions';
+import { updateResources } from '~/store/resources/resources-actions';
+import { loadMissingProcessesInformation } from '~/store/project-panel/project-panel-middleware-service';
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+import { sharedWithMePanelActions } from './shared-with-me-panel-actions';
+import { ListResults } from '~/services/common-service/common-resource-service';
+import { GroupContentsResource } from '~/services/groups-service/groups-service';
+import { SortDirection } from '~/components/data-table/data-column';
+import { OrderBuilder, OrderDirection } from '~/services/api/order-builder';
+import { ProjectResource } from '~/models/project';
+import { ProjectPanelColumnNames } from '~/views/project-panel/project-panel';
+import { FilterBuilder } from '~/services/api/filter-builder';
+
+export class SharedWithMeMiddlewareService extends DataExplorerMiddlewareService {
+    constructor(private services: ServiceRepository, id: string) {
+        super(id);
+    }
+
+    async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+        const state = api.getState();
+        const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
+        try {
+            const response = await this.services.groupsService.shared(getParams(dataExplorer));
+            api.dispatch<any>(updateFavorites(response.items.map(item => item.uuid)));
+            api.dispatch(updateResources(response.items));
+            await api.dispatch<any>(loadMissingProcessesInformation(response.items));
+            api.dispatch(setItems(response));
+        } catch (e) {
+            api.dispatch(couldNotFetchSharedItems());
+        }
+
+    }
+}
+
+export const getParams = (dataExplorer: DataExplorer) => ({
+    ...dataExplorerToListParams(dataExplorer),
+    order: getOrder(dataExplorer),
+    filters: getFilters(dataExplorer),
+});
+
+export const getFilters = (dataExplorer: DataExplorer) => {
+    const filters = new FilterBuilder()
+        .addILike("name", dataExplorer.searchValue)
+        .getFilters();
+    return `[${filters}]`;
+};
+
+export const getOrder = (dataExplorer: DataExplorer) => {
+    const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
+    const order = new OrderBuilder<ProjectResource>();
+    if (sortColumn) {
+        const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
+            ? OrderDirection.ASC
+            : OrderDirection.DESC;
+        const columnName = sortColumn && sortColumn.name === ProjectPanelColumnNames.NAME ? "name" : "createdAt";
+        return order
+            .addOrder(sortDirection, columnName)
+            .getOrder();
+    } else {
+        return order.getOrder();
+    }
+};
+
+export const setItems = (listResults: ListResults<GroupContentsResource>) =>
+    sharedWithMePanelActions.SET_ITEMS({
+        ...listResultsToDataExplorerItemsMeta(listResults),
+        items: listResults.items.map(resource => resource.uuid),
+    });
+
+const couldNotFetchSharedItems = () =>
+    snackbarActions.OPEN_SNACKBAR({
+        message: 'Could not fetch shared items.'
+    });
diff --git a/src/store/shared-with-me-panel/shared-with-me-panel-actions.ts b/src/store/shared-with-me-panel/shared-with-me-panel-actions.ts
new file mode 100644 (file)
index 0000000..2553086
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { bindDataExplorerActions } from "../data-explorer/data-explorer-action";
+import { Dispatch } from 'redux';
+import { ServiceRepository } from "~/services/services";
+import { RootState } from '~/store/store';
+
+export const SHARED_WITH_ME_PANEL_ID = "sharedWithMePanel";
+export const sharedWithMePanelActions = bindDataExplorerActions(SHARED_WITH_ME_PANEL_ID);
+
+export const loadSharedWithMePanel = () =>
+    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(sharedWithMePanelActions.REQUEST_ITEMS());
+    };
+
+
index 561df1d7ed5a2f4e6dd8391a953c0a90222ea106..23c5ea2217972d07765cc720b0e3253f4ac50b2a 100644 (file)
@@ -148,14 +148,14 @@ export const expandSidePanelTreeItem = (nodeId: string) =>
         }
     };
 
-const getSidePanelTreeNode = (nodeId: string) => (treePicker: TreePicker) => {
+export const getSidePanelTreeNode = (nodeId: string) => (treePicker: TreePicker) => {
     const sidePanelTree = getTreePicker(SIDE_PANEL_TREE)(treePicker);
     return sidePanelTree
         ? getNodeValue(nodeId)(sidePanelTree)
         : undefined;
 };
 
-const getSidePanelTreeNodeAncestorsIds = (nodeId: string) => (treePicker: TreePicker) => {
+export const getSidePanelTreeNodeAncestorsIds = (nodeId: string) => (treePicker: TreePicker) => {
     const sidePanelTree = getTreePicker(SIDE_PANEL_TREE)(treePicker);
     return sidePanelTree
         ? getNodeAncestorsIds(nodeId)(sidePanelTree)
index 3b66157dcd83262b1edfc35e5e3b131e7d885cd0..2a5fdd0c2d17c1aae6c3455034fd303ddb523a36 100644 (file)
@@ -4,7 +4,7 @@
 
 import { Dispatch } from 'redux';
 import { isSidePanelTreeCategory, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
-import { navigateToFavorites, navigateTo, navigateToTrash } from '../navigation/navigation-action';
+import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe } from '../navigation/navigation-action';
 import { snackbarActions } from '~/store/snackbar/snackbar-actions';
 
 export const navigateFromSidePanel = (id: string) =>
@@ -22,6 +22,8 @@ const getSidePanelTreeCategoryAction = (id: string) => {
             return navigateToFavorites;
         case SidePanelTreeCategory.TRASH:
             return navigateToTrash;
+        case SidePanelTreeCategory.SHARED_WITH_ME:
+            return navigateToSharedWithMe;
         default:
             return sidePanelTreeCategoryNotAvailable(id);
     }
index 205a21e16c8caf97897fc9e665e426b5f6e5f927..012b747425b72e714472a5b2a0cfe89d03dc2546 100644 (file)
@@ -32,6 +32,8 @@ import { TrashPanelMiddlewareService } from "~/store/trash-panel/trash-panel-mid
 import { TRASH_PANEL_ID } from "~/store/trash-panel/trash-panel-action";
 import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
 import { processPanelReducer } from '~/store/process-panel/process-panel-reducer';
+import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
+import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
 import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
 
 const composeEnhancers =
@@ -55,13 +57,17 @@ export function configureStore(history: History, services: ServiceRepository): R
     const trashPanelMiddleware = dataExplorerMiddleware(
         new TrashPanelMiddlewareService(services, TRASH_PANEL_ID)
     );
+    const sharedWithMePanelMiddleware = dataExplorerMiddleware(
+        new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
+    );
 
     const middlewares: Middleware[] = [
         routerMiddleware(history),
         thunkMiddleware.withExtraArgument(services),
         projectPanelMiddleware,
         favoritePanelMiddleware,
-        trashPanelMiddleware
+        trashPanelMiddleware,
+        sharedWithMePanelMiddleware,
     ];
     const enhancer = composeEnhancers(applyMiddleware(...middlewares));
     return createStore(rootReducer, enhancer);
index d1b0fae953281b562d7edebeb2c9c5d1bcf8435d..c79dc48f9f58e8c0bf81b659dcb3b00493001702 100644 (file)
@@ -9,13 +9,13 @@ import { loadCollectionPanel } from '~/store/collection-panel/collection-panel-a
 import { snackbarActions } from '../snackbar/snackbar-actions';
 import { loadFavoritePanel } from '../favorite-panel/favorite-panel-action';
 import { openProjectPanel, projectPanelActions } from '~/store/project-panel/project-panel-action';
-import { activateSidePanelTreeItem, initSidePanelTree, SidePanelTreeCategory, loadSidePanelTreeProjects } from '../side-panel-tree/side-panel-tree-actions';
+import { activateSidePanelTreeItem, initSidePanelTree, SidePanelTreeCategory, loadSidePanelTreeProjects, getSidePanelTreeNodeAncestorsIds } from '../side-panel-tree/side-panel-tree-actions';
 import { loadResource, updateResources } from '../resources/resources-actions';
 import { favoritePanelActions } from '~/store/favorite-panel/favorite-panel-action';
 import { projectPanelColumns } from '~/views/project-panel/project-panel';
 import { favoritePanelColumns } from '~/views/favorite-panel/favorite-panel';
 import { matchRootRoute } from '~/routes/routes';
-import { setCollectionBreadcrumbs, setProjectBreadcrumbs, setSidePanelBreadcrumbs, setProcessBreadcrumbs } from '../breadcrumbs/breadcrumbs-actions';
+import { setCollectionBreadcrumbs, setProjectBreadcrumbs, setSidePanelBreadcrumbs, setProcessBreadcrumbs, setSharedWithMeBreadcrumbs } from '../breadcrumbs/breadcrumbs-actions';
 import { navigateToProject } from '../navigation/navigation-action';
 import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
 import { ServiceRepository } from '~/services/services';
@@ -36,6 +36,9 @@ import { trashPanelColumns } from "~/views/trash-panel/trash-panel";
 import { loadTrashPanel, trashPanelActions } from "~/store/trash-panel/trash-panel-action";
 import { initProcessLogsPanel } from '../process-logs-panel/process-logs-panel-actions';
 import { loadProcessPanel } from '~/store/process-panel/process-panel-actions';
+import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
+import { loadSharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel-actions';
+
 import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
 
 export const loadWorkbench = () =>
@@ -48,6 +51,7 @@ export const loadWorkbench = () =>
                 dispatch(projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
                 dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns }));
                 dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
+                dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
                 dispatch<any>(initSidePanelTree());
                 if (router.location) {
                     const match = matchRootRoute(router.location.pathname);
@@ -78,10 +82,10 @@ export const loadTrash = () =>
     };
 
 export const loadProject = (uuid: string) =>
-    async (dispatch: Dispatch) => {
-        await dispatch<any>(activateSidePanelTreeItem(uuid));
-        dispatch<any>(setProjectBreadcrumbs(uuid));
-        dispatch<any>(openProjectPanel(uuid));
+    async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(openProjectPanel(uuid));
+        await dispatch(activateSidePanelTreeItem(uuid));
+        dispatch(setProjectBreadcrumbs(uuid));
         dispatch(loadDetailsPanel(uuid));
     };
 
@@ -266,3 +270,9 @@ export const reloadProjectMatchingUuid = (matchingUuids: string[]) =>
             dispatch<any>(loadProject(currentProjectPanelUuid));
         }
     };
+
+export const loadSharedWithMe = (dispatch: Dispatch) => {
+    dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
+    dispatch<any>(loadSharedWithMePanel());
+    dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME));
+};
index 5c061883f9e0b6d57179cdc57a155f2a6fa90a3c..b5ebc36e0eab508f048595bd092b397eb59421c9 100644 (file)
@@ -9,7 +9,7 @@ export abstract class DetailsData<T extends DetailsResource = DetailsResource> {
     constructor(protected item: T) {}
 
     getTitle(): string {
-        return this.item.name;
+        return this.item.name || 'Projects';
     }
 
     abstract getIcon(className?: string): React.ReactElement<any>;
index e39b3782739adbfe48b367c6334c1b8ab18406cf..89c3400b55cb00bb14bb93901d74e0b7b93a89c7 100644 (file)
@@ -45,7 +45,7 @@ type SidePanelProps = SidePanelDataProps & DispatchProp & WithStyles<CssRules>;
 
 const transformOrigin: PopoverOrigin = {
     vertical: -50,
-    horizontal: 45
+    horizontal: 0
 };
 
 const isButtonVisible = ({ router }: RootState) => {
@@ -70,7 +70,7 @@ export const SidePanelButton = withStyles(styles)(
                 const { anchorEl } = this.state;
                 return <Toolbar>
                     {buttonVisible  && <Grid container>
-                        <Grid container item xs alignItems="center" justify="center">
+                        <Grid container item xs alignItems="center" justify="flex-start">
                             <Button variant="contained" color="primary" size="small" className={classes.button}
                                 aria-owns={anchorEl ? 'aside-menu-list' : undefined}
                                 aria-haspopup="true"
index 61f6766c443115daa18948f98855f7a452abf7da..2b2be2e8905ec5d111faf8c121da72dddba94371 100644 (file)
@@ -3,7 +3,6 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
 import { DispatchProp, connect } from 'react-redux';
 import { DataColumns } from '~/components/data-table/data-table';
@@ -14,7 +13,6 @@ import { ContainerRequestState } from '~/models/container-request';
 import { SortDirection } from '~/components/data-table/data-column';
 import { ResourceKind, Resource } from '~/models/resource';
 import { resourceLabel } from '~/common/labels';
-import { ArvadosTheme } from '~/common/custom-theme';
 import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
 import { ProjectIcon } from '~/components/icon/icon';
 import { ResourceName } from '~/views-components/data-explorer/renderers';
@@ -25,11 +23,10 @@ import { ProjectResource } from '~/models/project';
 import { navigateTo } from '~/store/navigation/navigation-action';
 import { getProperty } from '~/store/properties/properties';
 import { PROJECT_PANEL_CURRENT_UUID } from '~/store/project-panel/project-panel-action';
-import { openCollectionCreateDialog } from '~/store/collections/collection-create-actions';
-import { openProjectCreateDialog } from '~/store/projects/project-create-actions';
-import { filterResources } from '~/store/resources/resources';
-import { PanelDefaultView } from '~/components/panel-default-view/panel-default-view';
 import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { StyleRulesCallback, WithStyles } from "@material-ui/core";
+import { ArvadosTheme } from "~/common/custom-theme";
+import withStyles from "@material-ui/core/styles/withStyles";
 
 type CssRules = 'root' | "button";
 
diff --git a/src/views/shared-with-me-panel/shared-with-me-panel.tsx b/src/views/shared-with-me-panel/shared-with-me-panel.tsx
new file mode 100644 (file)
index 0000000..9106f87
--- /dev/null
@@ -0,0 +1,77 @@
+// 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';
+import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
+import { connect, DispatchProp } from 'react-redux';
+import { RootState } from '~/store/store';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { ShareMeIcon } from '~/components/icon/icon';
+import { ResourcesState, getResource } from '~/store/resources/resources';
+import { navigateTo } from "~/store/navigation/navigation-action";
+import { loadDetailsPanel } from "~/store/details-panel/details-panel-action";
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
+import { openContextMenu } from '~/store/context-menu/context-menu-actions';
+import { GroupResource } from '~/models/group';
+import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+
+type CssRules = "toolbar" | "button";
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    toolbar: {
+        paddingBottom: theme.spacing.unit * 3,
+        textAlign: "right"
+    },
+    button: {
+        marginLeft: theme.spacing.unit
+    },
+});
+
+interface SharedWithMePanelDataProps {
+    resources: ResourcesState;
+}
+
+type SharedWithMePanelProps = SharedWithMePanelDataProps & DispatchProp & WithStyles<CssRules>;
+
+export const SharedWithMePanel = withStyles(styles)(
+    connect((state: RootState) => ({
+        resources: state.resources
+    }))(
+        class extends React.Component<SharedWithMePanelProps> {
+            render() {
+                return <DataExplorer
+                    id={SHARED_WITH_ME_PANEL_ID}
+                    onRowClick={this.handleRowClick}
+                    onRowDoubleClick={this.handleRowDoubleClick}
+                    onContextMenu={this.handleContextMenu}
+                    contextMenuColumn={false}
+                    dataTableDefaultView={<DataTableDefaultView icon={ShareMeIcon} />} />;
+            }
+
+            handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
+                const resource = getResource<GroupResource>(resourceUuid)(this.props.resources);
+                if (resource) {
+                    this.props.dispatch<any>(openContextMenu(event, {
+                        name: '',
+                        uuid: resource.uuid,
+                        ownerUuid: resource.ownerUuid,
+                        isTrashed: resource.isTrashed,
+                        kind: resource.kind,
+                        menuKind: ContextMenuKind.PROJECT,
+                    }));
+                }
+            }
+
+            handleRowDoubleClick = (uuid: string) => {
+                this.props.dispatch<any>(navigateTo(uuid));
+            }
+
+            handleRowClick = (uuid: string) => {
+                this.props.dispatch(loadDetailsPanel(uuid));
+            }
+        }
+    )
+);
index c22dde25f2a80be37775c27f19e701f3ad43a028..ad1a266881993b8f5e38eb568ac0f8f2c175ef3d 100644 (file)
@@ -42,10 +42,12 @@ import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/par
 import { TrashPanel } from "~/views/trash-panel/trash-panel";
 import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
 import { Grid, LinearProgress } from '@material-ui/core';
+import { SharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel';
+import SplitterLayout from 'react-splitter-layout';
 import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
 import { isSystemWorking } from "~/store/progress-indicator/progress-indicator-reducer";
 
-type CssRules = 'root' | 'asidePanel' | 'contentWrapper' | 'content' | 'appBar';
+type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content' | 'appBar';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
@@ -54,8 +56,16 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         height: '100vh',
         paddingTop: theme.spacing.unit * 8
     },
+    container: {
+        position: 'relative'
+    },
+    splitter: {
+        '& > .layout-splitter': {
+            width: '2px'
+        }
+    },
     asidePanel: {
-        maxWidth: '240px',
+        height: '100%',
         background: theme.palette.background.default
     },
     contentWrapper: {
@@ -64,7 +74,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     content: {
         minWidth: 0,
-        overflow: 'auto',
         paddingLeft: theme.spacing.unit * 3,
         paddingRight: theme.spacing.unit * 3,
     },
@@ -115,28 +124,35 @@ export const Workbench = withStyles(styles)(
                     <Grid container direction="column" className={classes.root}>
                         {this.props.user &&
                             <Grid container item xs alignItems="stretch" wrap="nowrap">
-                                <Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
-                                    <SidePanel />
-                                </Grid>
-                                <Grid container item xs component="main" direction="column" className={classes.contentWrapper}>
-                                    <Grid item>
-                                        <MainContentBar />
-                                    </Grid>
-                                    <Grid item xs className={classes.content}>
-                                        <Switch>
-                                            <Route path={Routes.PROJECTS} component={ProjectPanel} />
-                                            <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
-                                            <Route path={Routes.FAVORITES} component={FavoritePanel} />
-                                            <Route path={Routes.PROCESSES} component={ProcessPanel} />
-                                            <Route path={Routes.TRASH} component={TrashPanel} />
-                                            <Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
-                                        </Switch>
-                                    </Grid>
+                                <Grid container item className={classes.container}>
+                                <SplitterLayout customClassName={classes.splitter} percentage={true}
+                                    primaryIndex={0} primaryMinSize={20} secondaryInitialSize={80} secondaryMinSize={40}>
+                                        <Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
+                                            <SidePanel />
+                                        </Grid>
+                                        <Grid container item xs component="main" direction="column" className={classes.contentWrapper}>
+                                            <Grid item>
+                                                <MainContentBar />
+                                            </Grid>
+                                            <Grid item xs className={classes.content}>
+                                                <Switch>
+                                                    <Route path={Routes.PROJECTS} component={ProjectPanel} />
+                                                    <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
+                                                    <Route path={Routes.FAVORITES} component={FavoritePanel} />
+                                                    <Route path={Routes.PROCESSES} component={ProcessPanel} />
+                                                    <Route path={Routes.TRASH} component={TrashPanel} />
+                                                    <Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
+                                                    <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
+                                                </Switch>
+                                            </Grid>
+                                        </Grid>
+                                    </SplitterLayout>
                                 </Grid>
                                 <Grid item>
                                     <DetailsPanel />
                                 </Grid>
-                            </Grid>}
+                            </Grid>
+                        }
                     </Grid>
                     <ContextMenu />
                     <CopyCollectionDialog />
index 70a3fe53e1704c60bf533392163daa297200ef9d..da8e4415d1f6c2d863eb47cb8586ab9214a5ba32 100644 (file)
@@ -11,3 +11,5 @@ declare interface System {
   import<T = any>(module: string): Promise<T>
 }
 declare var System: System;
+
+declare module 'react-splitter-layout';
\ No newline at end of file
index 45c879fd84f5f8d224c08ea3ca7f78f739707c57..30e94bdefb4c9be9d37fc394908c15653130d55f 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
   dependencies:
     "@types/enzyme" "*"
 
-"@types/enzyme@*":
-  version "3.1.12"
-  resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.12.tgz#293bb07c1ef5932d37add3879e72e0f5bc614f3c"
-  dependencies:
-    "@types/cheerio" "*"
-    "@types/react" "*"
-
-"@types/enzyme@3.1.13":
+"@types/enzyme@*", "@types/enzyme@3.1.13":
   version "3.1.13"
   resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.13.tgz#4bbc5c81fa40c9fc7efee25c4a23cb37119a33ea"
   dependencies:
   version "4.14.116"
   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9"
 
-"@types/node@*":
-  version "10.5.2"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.5.2.tgz#f19f05314d5421fe37e74153254201a7bf00a707"
-
-"@types/node@10.7.1":
+"@types/node@*", "@types/node@10.7.1":
   version "10.7.1"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-10.7.1.tgz#b704d7c259aa40ee052eec678758a68d07132a2e"
 
@@ -1707,14 +1696,10 @@ color-convert@^1.3.0, color-convert@^1.9.0:
   dependencies:
     color-name "1.1.1"
 
-color-name@1.1.1:
+color-name@1.1.1, color-name@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689"
 
-color-name@^1.0.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
-
 color-string@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
@@ -2363,20 +2348,13 @@ domutils@1.1:
   dependencies:
     domelementtype "1"
 
-domutils@1.5.1:
+domutils@1.5.1, domutils@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
   dependencies:
     dom-serializer "0"
     domelementtype "1"
 
-domutils@^1.5.1:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
-  dependencies:
-    dom-serializer "0"
-    domelementtype "1"
-
 dot-prop@^4.1.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
@@ -2838,14 +2816,10 @@ extract-text-webpack-plugin@3.0.2:
     schema-utils "^0.3.0"
     webpack-sources "^1.0.1"
 
-extsprintf@1.3.0:
+extsprintf@1.3.0, extsprintf@^1.2.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
 
-extsprintf@^1.2.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
-
 fast-deep-equal@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
@@ -4940,7 +4914,7 @@ minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist@0.0.8:
+minimist@0.0.8, minimist@~0.0.1:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
 
@@ -4948,10 +4922,6 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
 
-minimist@~0.0.1:
-  version "0.0.10"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
-
 minipass@^2.2.1, minipass@^2.3.3:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
@@ -5351,18 +5321,12 @@ onetime@^2.0.0:
   dependencies:
     mimic-fn "^1.0.0"
 
-opn@5.2.0:
+opn@5.2.0, opn@^5.1.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225"
   dependencies:
     is-wsl "^1.1.0"
 
-opn@^5.1.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
-  dependencies:
-    is-wsl "^1.1.0"
-
 optimist@^0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@@ -6042,14 +6006,10 @@ q@^1.1.2:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
 
-qs@6.5.1:
+qs@6.5.1, qs@~6.5.1:
   version "6.5.1"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
 
-qs@~6.5.1:
-  version "6.5.2"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
-
 query-string@^4.1.0:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@@ -6187,11 +6147,7 @@ react-event-listener@^0.6.2:
     prop-types "^15.6.0"
     warning "^4.0.1"
 
-react-is@^16.4.1:
-  version "16.4.1"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e"
-
-react-is@^16.4.2:
+react-is@^16.4.1, react-is@^16.4.2:
   version "16.4.2"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.2.tgz#84891b56c2b6d9efdee577cc83501dfc5ecead88"
 
@@ -6304,6 +6260,10 @@ react-scripts-ts@2.17.0:
   optionalDependencies:
     fsevents "^1.1.3"
 
+react-splitter-layout@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/react-splitter-layout/-/react-splitter-layout-3.0.1.tgz#c2e00e69b35d240ab7a44f395d41803c5f4b70ef"
+
 react-test-renderer@^16.0.0-0:
   version "16.4.1"
   resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70"
@@ -6671,18 +6631,12 @@ resolve@1.1.7:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
 
-resolve@1.6.0:
+resolve@1.6.0, resolve@^1.1.7, resolve@^1.3.2:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.6.0.tgz#0fbd21278b27b4004481c395349e7aba60a9ff5c"
   dependencies:
     path-parse "^1.0.5"
 
-resolve@^1.1.7, resolve@^1.3.2:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
-  dependencies:
-    path-parse "^1.0.5"
-
 restore-cursor@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -7130,11 +7084,7 @@ static-extend@^0.1.1:
     define-property "^0.2.5"
     object-copy "^0.1.0"
 
-"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2":
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
-
-statuses@~1.4.0:
+"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2", statuses@~1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
 
@@ -8004,14 +7954,10 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
   dependencies:
     iconv-lite "0.4.19"
 
-whatwg-fetch@2.0.3:
+whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
 
-whatwg-fetch@>=0.10.0:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
-
 whatwg-mimetype@^2.0.0, whatwg-mimetype@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4"
@@ -8058,14 +8004,10 @@ window-size@0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
 
-wordwrap@0.0.2:
+wordwrap@0.0.2, wordwrap@~0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
 
-wordwrap@~0.0.2:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-
 wordwrap@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"