</Grid>
)}
{!!progressBar && progressBar}
- {this.multiSelectToolbarInTitle && !this.state.msToolbarInDetailsCard && <MultiselectToolbar injectedStyles={classes.msToolbarStyles} />}
- {this.multiSelectToolbarInTitle && <MultiselectToolbar />}
++ {this.multiSelectToolbarInTitle && <MultiselectToolbar injectedStyles={classes.msToolbarStyles} />}
{(!hideColumnSelector || !hideSearchInput || !!actions) && (
<Grid
className={classes.headerMenu}
onFiltersChange={onFiltersChange}
onSortToggle={onSortToggle}
extractKey={extractKey}
- working={this.state.showLoading}
defaultViewIcon={defaultViewIcon}
defaultViewMessages={defaultViewMessages}
- currentItemUuid={currentItemUuid}
currentRoute={paperKey}
toggleMSToolbar={toggleMSToolbar}
setCheckedListOnStore={setCheckedListOnStore}
checkedList={checkedList}
+ selectedResourceUuid={selectedResourceUuid}
+ setSelectedUuid={this.props.setSelectedUuid}
+ currentRouteUuid={this.props.currentRouteUuid}
+ working={working}
+ isNotFound={this.props.isNotFound}
/>
</Grid>
<Grid
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import { createTree } from "models/tree";
import { DataTableMultiselectOption } from "../data-table-multiselect-popover/data-table-multiselect-popover";
+import { isExactlyOneSelected } from "store/multiselect/multiselect-actions";
+ import { PendingIcon } from "components/icon/icon";
export type DataColumns<I, R> = Array<DataColumn<I, R>>;
working?: boolean;
defaultViewIcon?: IconType;
defaultViewMessages?: string[];
- currentItemUuid?: string;
- currentRoute?: string;
toggleMSToolbar: (isVisible: boolean) => void;
setCheckedListOnStore: (checkedList: TCheckedList) => void;
+ currentRoute?: string;
+ currentRouteUuid: string;
checkedList: TCheckedList;
+ selectedResourceUuid: string;
+ setSelectedUuid: (uuid: string | null) => void;
+ isNotFound?: boolean;
}
type CssRules =
if (prevProps.currentRoute !== this.props.currentRoute) {
this.initializeCheckedList([])
}
+ if (singleSelected && singleSelected !== isExactlyOneSelected(prevProps.checkedList)) {
+ this.props.setSelectedUuid(singleSelected);
+ }
+ if (!singleSelected && !!currentRouteUuid && !this.isAnySelected()) {
+ this.props.setSelectedUuid(currentRouteUuid);
+ }
+ if (!singleSelected && this.isAnySelected()) {
+ this.props.setSelectedUuid(null);
+ }
+ if(prevProps.working === true && this.props.working === false) {
+ this.setState({ isLoaded: true });
+ }
+ if((this.props.items.length > 0) && !this.state.isLoaded) {
+ this.setState({ isLoaded: true });
+ }
}
componentWillUnmount(): void {
import { getProcess } from "store/processes/process";
import { Process } from "store/processes/process";
import { PublicFavoritesState } from "store/public-favorites/public-favorites-reducer";
-import { isExactlyOneSelected } from "store/multiselect/multiselect-actions";
+import { AuthState } from "store/auth/auth-reducer";
import { IntersectionObserverWrapper } from "./ms-toolbar-overflow-wrapper";
+import classNames from "classnames";
+ import { ContextMenuKind, sortMenuItems, menuDirection } from 'views-components/context-menu/menu-item-sort';
- const WIDTH_TRANSITION = 150
-
- type CssRules = "root" | "transition" | "button" | "iconContainer" | "icon";
+ type CssRules = "root" | "button" | "iconContainer" | "icon" | "divider";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
display: "flex",
flexDirection: "row",
width: 0,
- height: '2.7rem',
+ height: '2.5rem',
padding: 0,
- transition: `width ${WIDTH_TRANSITION}ms`,
+ margin: "1rem auto auto 0.3rem",
overflow: 'hidden',
},
- transition: {
- display: "flex",
- flexDirection: "row",
- height: '2.5rem',
- padding: 0,
- overflow: 'hidden',
- transition: `width ${WIDTH_TRANSITION}ms`,
- },
button: {
width: "2.5rem",
height: "2.5rem ",
mapDispatchToProps
)(
withStyles(styles)((props: MultiselectToolbarProps & WithStyles<CssRules>) => {
- const { classes, checkedList, singleSelectedUuid, iconProps, user, disabledButtons } = props;
- const singleResourceKind = singleSelectedUuid ? [resourceToMsResourceKind(singleSelectedUuid, iconProps.resources, user)] : null
+ const { classes, checkedList, iconProps, user, disabledButtons, location, isSubPanel, injectedStyles } = props;
+ const selectedResourceUuid = isPathDisallowed(location) ? null : props.selectedResourceUuid;
+ const singleResourceKind = selectedResourceUuid && !isSubPanel ? [resourceToMsResourceKind(selectedResourceUuid, iconProps.resources, user)] : null
const currentResourceKinds = singleResourceKind ? singleResourceKind : Array.from(selectedToKindSet(checkedList));
- const currentPathIsTrash = location.includes("/trash");
- const [isTransitioning, setIsTransitioning] = useState(false);
- let transitionTimeout;
-
- const handleTransition = () => {
- setIsTransitioning(true)
- transitionTimeout = setTimeout(() => {
- setIsTransitioning(false)
- }, WIDTH_TRANSITION);
- }
-
- useEffect(()=>{
- handleTransition()
- return () => {
- if(transitionTimeout) clearTimeout(transitionTimeout)
- };
- // eslint-disable-next-line
- }, [checkedList])
+ const currentPathIsTrash = window.location.pathname === "/trash";
- const actions =
+ const rawActions =
currentPathIsTrash && selectedToKindSet(checkedList).size
? [msToggleTrashAction]
: selectActionsByKind(currentResourceKinds as string[], multiselectActionsFilters).filter((action) =>
- singleSelectedUuid === null ? action.isForMulti : true
+ selectedResourceUuid === null ? action.isForMulti : true
);
+
+ const actions: ContextMenuAction[] | MultiSelectMenuAction[] = sortMenuItems(
+ singleResourceKind && singleResourceKind.length ? (singleResourceKind[0] as ContextMenuKind) : ContextMenuKind.MULTI,
+ rawActions,
+ menuDirection.HORIZONTAL
+ );
+ const targetResources = selectedResourceUuid ? {[selectedResourceUuid]: true} as TCheckedList : checkedList
+
return (
<React.Fragment>
<Toolbar
<Tooltip
className={classes.button}
data-targetid={name}
- title={currentPathIsTrash || (useAlts && useAlts(singleSelectedUuid, iconProps)) ? altName : name}
+ title={currentPathIsTrash || (useAlts && useAlts(selectedResourceUuid, iconProps)) ? altName : name}
key={i}
disableFocusListener
- >
+ >
<span className={classes.iconContainer}>
<IconButton
data-cy='multiselect-button'
case ResourceKind.PROCESS:
return isAdmin && isEditable
? resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process)
- ? msMenuResourceKind.RUNNING_PROCESS_ADMIN
- : msMenuResourceKind.PROCESS_ADMIN
+ ? ContextMenuKind.RUNNING_PROCESS_ADMIN
+ : ContextMenuKind.PROCESS_ADMIN
: readonly
- ? msMenuResourceKind.READONLY_PROCESS_RESOURCE
+ ? ContextMenuKind.READONLY_PROCESS_RESOURCE
: resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process)
- ? msMenuResourceKind.RUNNING_PROCESS_RESOURCE
- : msMenuResourceKind.PROCESS_RESOURCE;
+ ? ContextMenuKind.RUNNING_PROCESS_RESOURCE
+ : ContextMenuKind.PROCESS_RESOURCE;
case ResourceKind.USER:
- return isAdmin ? msMenuResourceKind.ROOT_PROJECT_ADMIN : msMenuResourceKind.ROOT_PROJECT;
- return ContextMenuKind.ROOT_PROJECT;
++ return isAdmin ? ContextMenuKind.ROOT_PROJECT_ADMIN : ContextMenuKind.ROOT_PROJECT;
case ResourceKind.LINK:
- return msMenuResourceKind.LINK;
+ return ContextMenuKind.LINK;
case ResourceKind.WORKFLOW:
- return isEditable ? msMenuResourceKind.WORKFLOW : msMenuResourceKind.READONLY_WORKFLOW;
+ return isEditable ? ContextMenuKind.WORKFLOW : ContextMenuKind.READONLY_WORKFLOW;
default:
return;
}
return {
executeMulti: (selectedAction: ContextMenuAction, checkedList: TCheckedList, resources: ResourcesState): void => {
const kindGroups = groupByKind(checkedList, resources);
+ const currentList = selectedToArray(checkedList)
switch (selectedAction.name) {
- case MultiSelectMenuActionNames.MOVE_TO:
- case MultiSelectMenuActionNames.REMOVE:
- const firstResource = getResource(currentList[0])(resources) as ContainerRequestResource | Resource;
+ case ContextMenuActionNames.MOVE_TO:
+ case ContextMenuActionNames.REMOVE:
+ const firstResource = getResource(selectedToArray(checkedList)[0])(resources) as ContainerRequestResource | Resource;
const action = findActionByName(selectedAction.name as string, kindToActionSet[firstResource.kind]);
if (action) action.execute(dispatch, kindGroups[firstResource.kind]);
break;
} from 'views-components/multiselect-toolbar/ms-project-action-set';
import { msProcessActionSet, msCommonProcessActionFilter, msAdminProcessActionFilter, msRunningProcessActionFilter } from 'views-components/multiselect-toolbar/ms-process-action-set';
import { msWorkflowActionSet, msWorkflowActionFilter, msReadOnlyWorkflowActionFilter } from 'views-components/multiselect-toolbar/ms-workflow-action-set';
+import { UserDetailsActionSet } from 'views-components/multiselect-toolbar/ms-user-details-action-set';
import { ResourceKind } from 'models/resource';
-
- export enum msMenuResourceKind {
- API_CLIENT_AUTHORIZATION = 'ApiClientAuthorization',
- ROOT_PROJECT = 'RootProject',
- ROOT_PROJECT_ADMIN = 'RootProjectAdmin',
- PROJECT = 'Project',
- FILTER_GROUP = 'FilterGroup',
- READONLY_PROJECT = 'ReadOnlyProject',
- FROZEN_PROJECT = 'FrozenProject',
- FROZEN_PROJECT_ADMIN = 'FrozenProjectAdmin',
- PROJECT_ADMIN = 'ProjectAdmin',
- FILTER_GROUP_ADMIN = 'FilterGroupAdmin',
- RESOURCE = 'Resource',
- FAVORITE = 'Favorite',
- TRASH = 'Trash',
- COLLECTION_FILES = 'CollectionFiles',
- COLLECTION_FILES_MULTIPLE = 'CollectionFilesMultiple',
- READONLY_COLLECTION_FILES = 'ReadOnlyCollectionFiles',
- READONLY_COLLECTION_FILES_MULTIPLE = 'ReadOnlyCollectionFilesMultiple',
- COLLECTION_FILES_NOT_SELECTED = 'CollectionFilesNotSelected',
- COLLECTION_FILE_ITEM = 'CollectionFileItem',
- COLLECTION_DIRECTORY_ITEM = 'CollectionDirectoryItem',
- READONLY_COLLECTION_FILE_ITEM = 'ReadOnlyCollectionFileItem',
- READONLY_COLLECTION_DIRECTORY_ITEM = 'ReadOnlyCollectionDirectoryItem',
- COLLECTION = 'Collection',
- COLLECTION_ADMIN = 'CollectionAdmin',
- READONLY_COLLECTION = 'ReadOnlyCollection',
- OLD_VERSION_COLLECTION = 'OldVersionCollection',
- TRASHED_COLLECTION = 'TrashedCollection',
- PROCESS = 'Process',
- RUNNING_PROCESS_ADMIN = 'RunningProcessAdmin',
- PROCESS_ADMIN = 'ProcessAdmin',
- RUNNING_PROCESS_RESOURCE = 'RunningProcessResource',
- PROCESS_RESOURCE = 'ProcessResource',
- READONLY_PROCESS_RESOURCE = 'ReadOnlyProcessResource',
- PROCESS_LOGS = 'ProcessLogs',
- REPOSITORY = 'Repository',
- SSH_KEY = 'SshKey',
- VIRTUAL_MACHINE = 'VirtualMachine',
- KEEP_SERVICE = 'KeepService',
- USER = 'User',
- GROUPS = 'Group',
- GROUP_MEMBER = 'GroupMember',
- PERMISSION_EDIT = 'PermissionEdit',
- LINK = 'Link',
- WORKFLOW = 'Workflow',
- READONLY_WORKFLOW = 'ReadOnlyWorkflow',
- SEARCH_RESULTS = 'SearchResults',
- }
+ import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
const {
COLLECTION,
api.dispatch(projectPanelDataExplorerIsNotSet());
} else {
try {
+ api.dispatch<any>(dataExplorerActions.SET_IS_NOT_FOUND({ id: this.id, isNotFound: false }));
if (!background) { api.dispatch(progressIndicatorActions.START_WORKING(this.getId())); }
const response = await this.services.groupsService.contents(projectUuid, getParams(dataExplorer, !!isProjectTrashed));
- const resourceUuids = response.items.map(item => item.uuid);
+ const resourceUuids = [...response.items.map(item => item.uuid), projectUuid];
api.dispatch<any>(updateFavorites(resourceUuids));
api.dispatch<any>(updatePublicFavorites(resourceUuids));
api.dispatch(updateResources(response.items));
import { projectPanelActions } from "store/project-panel/project-panel-action-bind";
import { loadResource } from "store/resources/resources-actions";
import { RootState } from "store/store";
- import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+ import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
import { addDisabledButton, removeDisabledButton } from "store/multiselect/multiselect-actions";
+import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
export const freezeProject = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.FREEZE_PROJECT))
+ dispatch<any>(addDisabledButton(ContextMenuActionNames.FREEZE_PROJECT))
const userUUID = getState().auth.user!.uuid;
-
- const updatedProject = await services.projectService.update(uuid, {
- frozenByUuid: userUUID,
- });
-
+ let updatedProject;
+
+ try {
+ updatedProject = await services.projectService.update(uuid, {
+ frozenByUuid: userUUID,
+ });
+ } catch (e) {
+ console.error(e);
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not freeze project', hideDuration: 4000, kind: SnackbarKind.ERROR }));
+ }
+
dispatch(projectPanelActions.REQUEST_ITEMS());
dispatch<any>(loadResource(uuid, false));
- dispatch<any>(removeDisabledButton(MultiSelectMenuActionNames.FREEZE_PROJECT))
+ dispatch<any>(removeDisabledButton(ContextMenuActionNames.FREEZE_PROJECT))
return updatedProject;
};
+
export const unfreezeProject = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.FREEZE_PROJECT))
+ dispatch<any>(addDisabledButton(ContextMenuActionNames.FREEZE_PROJECT))
const updatedProject = await services.projectService.update(uuid, {
frozenByUuid: null,
});
import { DataColumn } from "components/data-table/data-column";
import { DataColumns, TCheckedList } from "components/data-table/data-table";
import { DataTableFilters } from "components/data-table-filters/data-table-filters-tree";
- import { LAST_REFRESH_TIMESTAMP } from "components/refresh-button/refresh-button";
import { toggleMSToolbar, setCheckedListOnStore } from "store/multiselect/multiselect-actions";
+import { setSelectedResourceUuid } from "store/selected-resource/selected-resource-actions";
interface Props {
id: string;
onContextMenu?: (event: React.MouseEvent<HTMLElement>, item: any, isAdmin?: boolean) => void;
onRowDoubleClick: (item: any) => void;
extractKey?: (item: any) => React.Key;
+ working?: boolean;
}
- const mapStateToProps = ({ progressIndicator, dataExplorer, router, multiselect, properties, selectedResourceUuid}: RootState, { id }: Props) => {
- const progress = progressIndicator.find(p => p.id === id);
+ const mapStateToProps = ({ progressIndicator, dataExplorer, router, multiselect, detailsPanel, properties}: RootState, { id }: Props) => {
+ const working = !!progressIndicator.some(p => p.id === id && p.working);
const dataExplorerState = getDataExplorer(dataExplorer, id);
const currentRoute = router.location ? router.location.pathname : "";
- const currentRefresh = localStorage.getItem(LAST_REFRESH_TIMESTAMP) || "";
- const isDetailsResourceChecked = multiselect.checkedList[detailsPanel.resourceUuid]
- const isOnlyOneSelected = Object.values(multiselect.checkedList).filter(x => x === true).length === 1;
- const currentItemUuid =
- currentRoute === '/workflows' ? properties.workflowPanelDetailsUuid : isDetailsResourceChecked && isOnlyOneSelected ? detailsPanel.resourceUuid : multiselect.selectedUuid;
const isMSToolbarVisible = multiselect.isVisible;
return {
...dataExplorerState,
- working: !!progress?.working,
- currentRefresh: currentRefresh,
currentRoute: currentRoute,
paperKey: currentRoute,
- currentItemUuid,
+ currentRouteUuid: properties.currentRouteUuid,
- selectedResourceUuid: selectedResourceUuid,
isMSToolbarVisible,
checkedList: multiselect.checkedList,
+ working,
};
};
import { CopyToClipboardSnackbar } from "components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar";
import { ProjectResource } from "models/project";
import { ProcessResource } from "models/process";
+import { ServiceRepository } from "services/services";
+import { loadUsersPanel } from "store/users/users-actions";
+
+export const toggleIsAdmin = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const { resources } = getState();
+ const data = getResource<UserResource>(uuid)(resources);
+ const isAdmin = data!.isAdmin;
+ const newActivity = await services.userService.update(uuid, { isAdmin: !isAdmin });
+ dispatch<any>(loadUsersPanel());
+ return newActivity;
+ };
+ import { InlinePulser } from "components/loading/inline-pulser";
const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
const navFunc = "groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
import { CollectionResource } from 'models/collection';
import { resourceIsFrozen } from 'common/frozen-resources';
import { ProjectResource } from 'models/project';
- import { NotFoundView } from 'views/not-found-panel/not-found-panel';
import { deselectAllOthers, toggleOne } from 'store/multiselect/multiselect-actions';
+import { ProjectDetailsCard } from 'views-components/project-details-card/project-details-card';
- type CssRules = 'root' | 'button';
+ type CssRules = 'root' | 'button' ;
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
export const ProjectPanel = withStyles(styles)(
connect(mapStateToProps)(
class extends React.Component<ProjectPanelProps> {
+
render() {
const { classes } = this.props;
-
- return this.props.project ?
- <div data-cy='project-panel' className={classes.root}>
- <ProjectDetailsCard />
- <DataExplorer
- id={PROJECT_PANEL_ID}
- onRowClick={this.handleRowClick}
- onRowDoubleClick={this.handleRowDoubleClick}
- onContextMenu={this.handleContextMenu}
- contextMenuColumn={true}
- defaultViewIcon={ProjectIcon}
- defaultViewMessages={DEFAULT_VIEW_MESSAGES}
- />
- </div>
- :
- <NotFoundView
- icon={ProjectIcon}
- messages={["Project not found"]}
+ return <div data-cy='project-panel' className={classes.root}>
++ <ProjectDetailsCard />
+ <DataExplorer
+ id={PROJECT_PANEL_ID}
+ onRowClick={this.handleRowClick}
+ onRowDoubleClick={this.handleRowDoubleClick}
+ onContextMenu={this.handleContextMenu}
+ contextMenuColumn={true}
+ defaultViewIcon={ProjectIcon}
+ defaultViewMessages={DEFAULT_VIEW_MESSAGES}
++ selectedResourceUuid={this.props.currentItemId}
/>
+ </div>
}
isCurrentItemChild = (resource: Resource) => {