"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",
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;
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"];
},
MuiAppBar: {
colorPrimary: {
- backgroundColor: purple800
+ backgroundColor: arvadosPurple
}
},
MuiTabs: {
color: grey600
},
indicator: {
- backgroundColor: purple800
+ backgroundColor: arvadosPurple
}
},
MuiTab: {
selected: {
fontWeight: 700,
- color: purple800
+ color: arvadosPurple
}
},
MuiList: {
},
underline: {
'&:after': {
- borderBottomColor: purple800
+ borderBottomColor: arvadosPurple
},
'&:hover:not($disabled):not($focused):not($error):before': {
borderBottom: '1px solid inherit'
},
focused: {
"&$focused:not($error)": {
- color: purple800
+ color: arvadosPurple
}
}
}
},
palette: {
primary: {
- main: rocheBlue,
- dark: blue.A100
+ main: teal.A700,
+ dark: blue.A100,
+ contrastText: '#fff'
}
}
};
Card,
CardActions,
Typography,
- CardContent
+ CardContent,
+ Tooltip
} from "@material-ui/core";
import * as classnames from "classnames";
import { DefaultTransformOrigin } from "../popover/helpers";
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}
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} />;
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} />;
// 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';
timeout: number;
render() {
- const {classes} = this.props;
+ const { classes } = this.props;
return <Paper className={classes.container}>
<form onSubmit={this.handleSubmit}>
<input
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 });
}
}
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
// 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';
<InputAdornment position="end">
<IconButton
onClick={this.handleSubmit}>
- <SearchIcon/>
+ <Tooltip title='Search'>
+ <SearchIcon />
+ </Tooltip>
</IconButton>
</InputAdornment>
- }/>
+ } />
</FormControl>
</form>;
}
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);
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) {
store.dispatch(loadProcessLog(processLogMatch.params.id));
} else if (rootMatch) {
store.dispatch(navigateToRootProject);
+ } else if (sharedWithMeMatch) {
+ store.dispatch(loadSharedWithMe);
}
};
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) => {
export const matchProcessLogRoute = (route: string) =>
matchPath<ResourceRouteParams>(route, { path: Routes.PROCESS_LOGS });
+
+export const matchSharedWithMeRoute = (route: string) =>
+ matchPath(route, { path: Routes.SHARED_WITH_ME });
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 [];
// 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";
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;
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);
this.actions
);
}
+
+ shared(params: SharedArguments = {}): Promise<ListResults<GroupContentsResource>> {
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .get(this.resourceType + 'shared', { params }),
+ this.actions
+ );
+ }
}
export enum GroupContentsResourcePrefix {
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';
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) => {
}
if (uuid === SidePanelTreeCategory.FAVORITES) {
dispatch<any>(navigateToFavorites);
+ } else if(uuid === SidePanelTreeCategory.SHARED_WITH_ME){
+ dispatch(navigateToSharedWithMe);
}
};
if (rootProjectUuid) {
dispatch(navigateToProject(rootProjectUuid));
}
-};
\ No newline at end of file
+};
+
+export const navigateToSharedWithMe = push(Routes.SHARED_WITH_ME);
}
};
-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);
.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) {
--- /dev/null
+// 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.'
+ });
--- /dev/null
+// 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());
+ };
+
+
}
};
-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)
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) =>
return navigateToFavorites;
case SidePanelTreeCategory.TRASH:
return navigateToTrash;
+ case SidePanelTreeCategory.SHARED_WITH_ME:
+ return navigateToSharedWithMe;
default:
return sidePanelTreeCategoryNotAvailable(id);
}
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 =
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);
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';
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 = () =>
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);
};
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));
};
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));
+};
constructor(protected item: T) {}
getTitle(): string {
- return this.item.name;
+ return this.item.name || 'Projects';
}
abstract getIcon(className?: string): React.ReactElement<any>;
const transformOrigin: PopoverOrigin = {
vertical: -50,
- horizontal: 45
+ horizontal: 0
};
const isButtonVisible = ({ router }: RootState) => {
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"
// 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';
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';
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";
--- /dev/null
+// 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));
+ }
+ }
+ )
+);
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: {
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: {
},
content: {
minWidth: 0,
- overflow: 'auto',
paddingLeft: theme.spacing.unit * 3,
paddingRight: theme.spacing.unit * 3,
},
<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 />
import<T = any>(module: string): Promise<T>
}
declare var System: System;
+
+declare module 'react-splitter-layout';
\ No newline at end of file
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"
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"