X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/e7a3f670ea3fa984fb704b204772ba98a5cf4451..e09a9e644ad7937bb3800ca8096f444f6ff4411b:/src/components/multiselect-toolbar/MultiselectToolbar.tsx diff --git a/src/components/multiselect-toolbar/MultiselectToolbar.tsx b/src/components/multiselect-toolbar/MultiselectToolbar.tsx index 8a7c0ab4..30d5bd79 100644 --- a/src/components/multiselect-toolbar/MultiselectToolbar.tsx +++ b/src/components/multiselect-toolbar/MultiselectToolbar.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -import React from "react"; +import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import { StyleRulesCallback, withStyles, WithStyles, Toolbar, Tooltip, IconButton } from "@material-ui/core"; import { ArvadosTheme } from "common/custom-theme"; @@ -13,44 +13,88 @@ import { ContextMenuResource } from "store/context-menu/context-menu-actions"; import { Resource, ResourceKind, extractUuidKind } from "models/resource"; import { getResource } from "store/resources/resources"; import { ResourcesState } from "store/resources/resources"; -import { MultiSelectMenuAction, MultiSelectMenuActionSet, MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions"; +import { MultiSelectMenuAction, MultiSelectMenuActionSet } from "views-components/multiselect-toolbar/ms-menu-actions"; +import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions"; import { ContextMenuAction } from "views-components/context-menu/context-menu-action-set"; -import { multiselectActionsFilters, TMultiselectActionsFilters, msResourceKind } from "./ms-toolbar-action-filters"; +import { multiselectActionsFilters, TMultiselectActionsFilters, msMenuResourceKind } from "./ms-toolbar-action-filters"; import { kindToActionSet, findActionByName } from "./ms-kind-action-differentiator"; import { msToggleTrashAction } from "views-components/multiselect-toolbar/ms-project-action-set"; import { copyToClipboardAction } from "store/open-in-new-tab/open-in-new-tab.actions"; import { ContainerRequestResource } from "models/container-request"; import { FavoritesState } from "store/favorites/favorites-reducer"; import { resourceIsFrozen } from "common/frozen-resources"; +import { getResourceWithEditableStatus } from "store/resources/resources"; +import { GroupResource } from "models/group"; +import { EditableResource } from "models/resource"; +import { User } from "models/user"; +import { GroupClass } from "models/group"; +import { isProcessCancelable } from "store/processes/process"; +import { CollectionResource } from "models/collection"; +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"; -type CssRules = "root" | "button"; +const WIDTH_TRANSITION = 150 + +type CssRules = "root" | "transition" | "button" | "iconContainer"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { display: "flex", + flexShrink: 0, flexDirection: "row", width: 0, + height: '2.7rem', padding: 0, margin: "1rem auto auto 0.5rem", - overflowY: 'scroll', - transition: "width 150ms", + overflowY: 'auto', + scrollBehavior: 'smooth', + '&::-webkit-scrollbar': { + width: 0, + height: 2 + }, + '&::-webkit-scrollbar-track': { + width: 0, + height: 2 + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#757575', + borderRadius: 2 + } + }, + transition: { + display: "flex", + flexDirection: "row", + width: 0, + height: '2.7rem', + padding: 0, + margin: "1rem auto auto 0.5rem", + overflow: 'hidden', + transition: `width ${WIDTH_TRANSITION}ms`, }, button: { width: "2.5rem", height: "2.5rem ", }, + iconContainer: { + height: '100%' + } }); export type MultiselectToolbarProps = { checkedList: TCheckedList; - selectedUuid: string | null + singleSelectedUuid: string | null iconProps: IconProps + user: User | null + disabledButtons: Set executeMulti: (action: ContextMenuAction, checkedList: TCheckedList, resources: ResourcesState) => void; }; type IconProps = { resources: ResourcesState; - favorites: FavoritesState + favorites: FavoritesState; + publicFavorites: PublicFavoritesState; } export const MultiselectToolbar = connect( @@ -58,49 +102,74 @@ export const MultiselectToolbar = connect( mapDispatchToProps )( withStyles(styles)((props: MultiselectToolbarProps & WithStyles) => { - const { classes, checkedList, selectedUuid: singleSelectedUuid, iconProps } = props; - const singleProjectKind = singleSelectedUuid ? resourceSubKind(singleSelectedUuid, iconProps.resources) : '' - const currentResourceKinds = singleProjectKind ? singleProjectKind :Array.from(selectedToKindSet(checkedList)); - + const { classes, checkedList, singleSelectedUuid, iconProps, user, disabledButtons } = props; + const singleResourceKind = singleSelectedUuid ? [resourceToMsResourceKind(singleSelectedUuid, iconProps.resources, user)] : null + const currentResourceKinds = singleResourceKind ? singleResourceKind : Array.from(selectedToKindSet(checkedList)); const currentPathIsTrash = window.location.pathname === "/trash"; - - + const [isTransitioning, setIsTransitioning] = useState(false); + const actions = - currentPathIsTrash && selectedToKindSet(checkedList).size - ? [msToggleTrashAction] - : selectActionsByKind([currentResourceKinds] as string[], multiselectActionsFilters) - .filter((action) => (singleSelectedUuid === null ? action.isForMulti : true)); + currentPathIsTrash && selectedToKindSet(checkedList).size + ? [msToggleTrashAction] + : selectActionsByKind(currentResourceKinds as string[], multiselectActionsFilters) + .filter((action) => (singleSelectedUuid === null ? action.isForMulti : true)); + + const handleTransition = () => { + setIsTransitioning(true) + setTimeout(() => { + setIsTransitioning(false) + }, WIDTH_TRANSITION); + } + + useEffect(()=>{ + handleTransition() + }, [checkedList]) return ( + className={isTransitioning ? classes.transition: classes.root} + style={{ width: `${(actions.length * 2.5) + 1}rem` }} + data-cy='multiselect-toolbar' + > {actions.length ? ( - actions.map((action, i) => - action.hasAlts ? ( - - props.executeMulti(action, checkedList, iconProps.resources)}> - {currentPathIsTrash || action.useAlts(singleSelectedUuid, iconProps) ? action.altIcon && action.altIcon({}) : action.icon({})} + actions.map((action, i) =>{ + const { hasAlts, useAlts, name, altName, icon, altIcon } = action; + return hasAlts ? ( + + + props.executeMulti(action, checkedList, iconProps.resources)} + > + {currentPathIsTrash || (useAlts && useAlts(singleSelectedUuid, iconProps)) ? altIcon && altIcon({}) : icon({})} - - ) : ( - - props.executeMulti(action, checkedList, iconProps.resources)}>{action.icon({})} - - ) - ) + + + ) : ( + + + props.executeMulti(action, checkedList, iconProps.resources)} + > + {action.icon({})} + + + + ); + }) ) : ( <> )} @@ -144,14 +213,64 @@ function filterActions(actionArray: MultiSelectMenuActionSet, filters: Set filters.has(action.name as string)); } -const resourceSubKind = (uuid: string, resources: ResourcesState) => { - const resource = getResource(uuid)(resources) as ContainerRequestResource | Resource; - switch (resource.kind) { +const resourceToMsResourceKind = (uuid: string, resources: ResourcesState, user: User | null, readonly = false): (msMenuResourceKind | ResourceKind) | undefined => { + if (!user) return; + const resource = getResourceWithEditableStatus(uuid, user.uuid)(resources); + const { isAdmin } = user; + const kind = extractUuidKind(uuid); + + const isFrozen = resourceIsFrozen(resource, resources); + const isEditable = (user.isAdmin || (resource || ({} as EditableResource)).isEditable) && !readonly && !isFrozen; + + switch (kind) { case ResourceKind.PROJECT: - if(resourceIsFrozen(resource, resources)) return [msResourceKind.PROJECT_FROZEN] - return [msResourceKind.PROJECT] + if (isFrozen) { + return isAdmin ? msMenuResourceKind.FROZEN_PROJECT_ADMIN : msMenuResourceKind.FROZEN_PROJECT; + } + + return isAdmin && !readonly + ? resource && resource.groupClass !== GroupClass.FILTER + ? msMenuResourceKind.PROJECT_ADMIN + : msMenuResourceKind.FILTER_GROUP_ADMIN + : isEditable + ? resource && resource.groupClass !== GroupClass.FILTER + ? msMenuResourceKind.PROJECT + : msMenuResourceKind.FILTER_GROUP + : msMenuResourceKind.READONLY_PROJECT; + case ResourceKind.COLLECTION: + const c = getResource(uuid)(resources); + if (c === undefined) { + return; + } + const isOldVersion = c.uuid !== c.currentVersionUuid; + const isTrashed = c.isTrashed; + return isOldVersion + ? msMenuResourceKind.OLD_VERSION_COLLECTION + : isTrashed && isEditable + ? msMenuResourceKind.TRASHED_COLLECTION + : isAdmin && isEditable + ? msMenuResourceKind.COLLECTION_ADMIN + : isEditable + ? msMenuResourceKind.COLLECTION + : msMenuResourceKind.READONLY_COLLECTION; + case ResourceKind.PROCESS: + return isAdmin && isEditable + ? resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process) + ? msMenuResourceKind.RUNNING_PROCESS_ADMIN + : msMenuResourceKind.PROCESS_ADMIN + : readonly + ? msMenuResourceKind.READONLY_PROCESS_RESOURCE + : resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process) + ? msMenuResourceKind.RUNNING_PROCESS_RESOURCE + : msMenuResourceKind.PROCESS_RESOURCE; + case ResourceKind.USER: + return msMenuResourceKind.ROOT_PROJECT; + case ResourceKind.LINK: + return msMenuResourceKind.LINK; + case ResourceKind.WORKFLOW: + return isEditable ? msMenuResourceKind.WORKFLOW : msMenuResourceKind.READONLY_WORKFLOW; default: - return resource.kind + return; } }; @@ -198,27 +317,19 @@ function selectActionsByKind(currentResourceKinds: Array, filterSet: TMu }); } -export const isExactlyOneSelected = (checkedList: TCheckedList) => { - let tally = 0; - let current = ''; - for (const uuid in checkedList) { - if (checkedList[uuid] === true) { - tally++; - current = uuid; - } - } - return tally === 1 ? current : null -}; //--------------------------------------------------// -function mapStateToProps({multiselect, resources, favorites}: RootState) { +function mapStateToProps({auth, multiselect, resources, favorites, publicFavorites}: RootState) { return { checkedList: multiselect.checkedList as TCheckedList, - selectedUuid: isExactlyOneSelected(multiselect.checkedList), + singleSelectedUuid: isExactlyOneSelected(multiselect.checkedList), + user: auth && auth.user ? auth.user : null, + disabledButtons: new Set(multiselect.disabledButtons), iconProps: { resources, - favorites + favorites, + publicFavorites } } }