import React, { useCallback, useEffect, useState } from 'react'; import { PageGrant, GroupType, getIdForRef, } from '@growi/core'; import { LoadingSpinner } from '@growi/ui/dist/components'; import { useTranslation } from 'next-i18next'; import { UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, Modal, ModalHeader, ModalBody, } from 'reactstrap'; import type { UserRelatedGroupsData } from '~/interfaces/page'; import { UserGroupPageGrantStatus } from '~/interfaces/page'; import { useCurrentUser } from '~/stores/context'; import { useCurrentPageId, useSWRxCurrentGrantData } from '~/stores/page'; import { useSelectedGrant } from '~/stores/ui'; const AVAILABLE_GRANTS = [ { grant: PageGrant.GRANT_PUBLIC, iconName: 'group', btnStyleClass: 'outline-info', label: 'Public', }, { grant: PageGrant.GRANT_RESTRICTED, iconName: 'link', btnStyleClass: 'outline-success', label: 'Anyone with the link', }, // { grant: 3, iconClass: '', label: 'Specified users only' }, { grant: PageGrant.GRANT_OWNER, iconName: 'lock', btnStyleClass: 'outline-danger', label: 'Only me', }, { grant: PageGrant.GRANT_USER_GROUP, iconName: 'more_horiz', btnStyleClass: 'outline-warning', label: 'Only inside the group', reselectLabel: 'Reselect the group', }, ]; type Props = { disabled?: boolean, openInModal?: boolean, } /** * Page grant select component */ export const GrantSelector = (props: Props): JSX.Element => { const { t } = useTranslation(); const { disabled, openInModal, } = props; const [isSelectGroupModalShown, setIsSelectGroupModalShown] = useState(false); const { data: currentUser } = useCurrentUser(); const shouldFetch = isSelectGroupModalShown; const { data: selectedGrant, mutate: mutateSelectedGrant } = useSelectedGrant(); const { data: currentPageId } = useCurrentPageId(); const { data: grantData } = useSWRxCurrentGrantData(currentPageId); const currentPageGrantData = grantData?.grantData.currentPageGrant; const groupGrantData = currentPageGrantData?.groupGrantData; const applyCurrentPageGrantToSelectedGrant = useCallback(() => { const currentPageGrant = grantData?.grantData.currentPageGrant; if (currentPageGrant == null) return; const userRelatedGrantedGroups = currentPageGrant.groupGrantData ?.userRelatedGroups.filter(group => group.status === UserGroupPageGrantStatus.isGranted)?.map((group) => { return { item: group.id, type: group.type }; }) ?? []; mutateSelectedGrant({ grant: currentPageGrant.grant, userRelatedGrantedGroups, }); }, [grantData?.grantData.currentPageGrant, mutateSelectedGrant]); // sync grant data useEffect(() => { applyCurrentPageGrantToSelectedGrant(); }, [applyCurrentPageGrantToSelectedGrant]); const showSelectGroupModal = useCallback(() => { setIsSelectGroupModalShown(true); }, []); /** * change event handler for grant selector */ const changeGrantHandler = useCallback((grant: PageGrant) => { // select group if (grant === 5) { if (selectedGrant?.grant !== 5) applyCurrentPageGrantToSelectedGrant(); showSelectGroupModal(); return; } mutateSelectedGrant({ grant, userRelatedGrantedGroups: undefined }); }, [mutateSelectedGrant, showSelectGroupModal, applyCurrentPageGrantToSelectedGrant, selectedGrant?.grant]); const groupListItemClickHandler = useCallback((clickedGroup: UserRelatedGroupsData) => { const userRelatedGrantedGroups = selectedGrant?.userRelatedGrantedGroups ?? []; let userRelatedGrantedGroupsCopy = [...userRelatedGrantedGroups]; if (userRelatedGrantedGroupsCopy.find(group => getIdForRef(group.item) === clickedGroup.id) == null) { const grantGroupInfo = { item: clickedGroup.id, type: clickedGroup.type }; userRelatedGrantedGroupsCopy.push(grantGroupInfo); } else { userRelatedGrantedGroupsCopy = userRelatedGrantedGroupsCopy.filter(group => getIdForRef(group.item) !== clickedGroup.id); } mutateSelectedGrant({ grant: 5, userRelatedGrantedGroups: userRelatedGrantedGroupsCopy }); }, [mutateSelectedGrant, selectedGrant?.userRelatedGrantedGroups]); /** * Render grant selector DOM. */ const renderGrantSelector = useCallback(() => { let dropdownToggleBtnColor; let dropdownToggleLabelElm; const userRelatedGrantedGroups = groupGrantData?.userRelatedGroups.filter((group) => { return selectedGrant?.userRelatedGrantedGroups?.some(grantedGroup => getIdForRef(grantedGroup.item) === group.id); }) ?? []; const nonUserRelatedGrantedGroups = groupGrantData?.nonUserRelatedGrantedGroups ?? []; const dropdownMenuElems = AVAILABLE_GRANTS.map((opt) => { const label = ((opt.grant === 5 && opt.reselectLabel != null) && userRelatedGrantedGroups.length > 0) ? opt.reselectLabel // when grantGroup is selected : opt.label; const labelElm = ( {opt.iconName} {t(label)} ); // set dropdownToggleBtnColor, dropdownToggleLabelElm if (opt.grant === 1 || opt.grant === selectedGrant?.grant) { dropdownToggleBtnColor = opt.btnStyleClass; dropdownToggleLabelElm = labelElm; } return changeGrantHandler(opt.grant)}>{labelElm}; }); // add specified group option if (selectedGrant?.grant === PageGrant.GRANT_USER_GROUP && (userRelatedGrantedGroups.length > 0 || nonUserRelatedGrantedGroups.length > 0)) { const grantedGroupNames = [...userRelatedGrantedGroups.map(group => group.name), ...nonUserRelatedGrantedGroups.map(group => group.name)]; const labelElm = ( account_tree {grantedGroupNames.length > 1 ? ( // substring for group name truncate {`${grantedGroupNames[0].substring(0, 30)}, ... `} +{grantedGroupNames.length - 1} ) : grantedGroupNames[0].substring(0, 30)} ); // set dropdownToggleLabelElm dropdownToggleLabelElm = labelElm; dropdownMenuElems.push({labelElm}); } return (
{dropdownToggleLabelElm} {dropdownMenuElems}
); }, [changeGrantHandler, disabled, groupGrantData, selectedGrant, t, openInModal]); /** * Render select grantgroup modal. */ const renderSelectGroupModalContent = useCallback(() => { if (!shouldFetch) { return <>; } // show spinner if (groupGrantData == null) { return (
); } const { userRelatedGroups, nonUserRelatedGrantedGroups } = groupGrantData; if (userRelatedGroups.length === 0) { return (

{t('user_group.belonging_to_no_group')}

{ currentUser?.admin && (

login{t('user_group.manage_user_groups')}

) }
); } return (
{ userRelatedGroups.map((group) => { const isGroupGranted = selectedGrant?.userRelatedGrantedGroups?.some(grantedGroup => getIdForRef(grantedGroup.item) === group.id); const cannotGrantGroup = group.status === UserGroupPageGrantStatus.cannotGrant; const activeClass = isGroupGranted ? 'active' : ''; return ( ); }) } { nonUserRelatedGrantedGroups.map((group) => { return ( ); }) }
); }, [currentUser?.admin, groupListItemClickHandler, shouldFetch, t, groupGrantData, selectedGrant?.userRelatedGrantedGroups]); const renderModalCloseButton = useCallback(() => { return ( ); }, [setIsSelectGroupModalShown]); return ( <> { renderGrantSelector() } {/* render modal */} { !disabled && currentUser != null && ( setIsSelectGroupModalShown(false)} centered > setIsSelectGroupModalShown(false)} className="fs-5 text-muted fw-bold pb-2" close={renderModalCloseButton()}> {t('user_group.select_group')} {renderSelectGroupModalContent()} ) } ); };