270 lines
8.1 KiB
TypeScript
270 lines
8.1 KiB
TypeScript
import {
|
|
type ButtonProps,
|
|
Flex,
|
|
Menu,
|
|
MenuList,
|
|
Box,
|
|
Radio,
|
|
useOutsideClick,
|
|
HStack,
|
|
MenuButton,
|
|
Checkbox
|
|
} from '@chakra-ui/react';
|
|
import React, { useMemo, useRef, useState } from 'react';
|
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
import { type PermissionValueType } from '@fastgpt/global/support/permission/type';
|
|
import { useContextSelector } from 'use-context-selector';
|
|
import { Permission } from '@fastgpt/global/support/permission/controller';
|
|
import { CollaboratorContext } from './context';
|
|
import { useTranslation } from 'next-i18next';
|
|
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
|
|
|
export type PermissionSelectProps = {
|
|
value?: PermissionValueType;
|
|
onChange: (value: PermissionValueType) => void;
|
|
trigger?: 'hover' | 'click';
|
|
offset?: [number, number];
|
|
Button: React.ReactNode;
|
|
|
|
onDelete?: () => void;
|
|
} & Omit<ButtonProps, 'onChange' | 'value'>;
|
|
|
|
const MenuStyle = {
|
|
py: 2,
|
|
px: 3,
|
|
_hover: {
|
|
bg: 'myGray.50'
|
|
},
|
|
borderRadius: 'md',
|
|
cursor: 'pointer',
|
|
fontSize: 'sm'
|
|
};
|
|
|
|
function PermissionSelect({
|
|
value,
|
|
onChange,
|
|
trigger = 'click',
|
|
offset = [0, 5],
|
|
Button,
|
|
width = 'auto',
|
|
onDelete
|
|
}: PermissionSelectProps) {
|
|
const { t } = useTranslation();
|
|
const ref = useRef<HTMLButtonElement>(null);
|
|
const closeTimer = useRef<NodeJS.Timeout>();
|
|
|
|
const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v);
|
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
const permissionSelectList = useMemo(() => {
|
|
if (!permissionList) return { singleCheckBoxList: [], multipleCheckBoxList: [] };
|
|
|
|
const list = Object.entries(permissionList).map(([_, value]) => {
|
|
return {
|
|
name: value.name,
|
|
value: value.value,
|
|
description: value.description,
|
|
checkBoxType: value.checkBoxType
|
|
};
|
|
});
|
|
|
|
return {
|
|
singleCheckBoxList: list
|
|
.filter((item) => item.checkBoxType === 'single')
|
|
.filter((item) => {
|
|
if (permission.isOwner) return true;
|
|
if (item.value === permissionList['manage'].value) return false;
|
|
return true;
|
|
}),
|
|
multipleCheckBoxList: list.filter((item) => item.checkBoxType === 'multiple')
|
|
};
|
|
}, [permission.isOwner, permissionList]);
|
|
const selectedSingleValue = useMemo(() => {
|
|
if (!permissionList) return undefined;
|
|
|
|
const per = new Permission({ per: value, permissionList });
|
|
|
|
if (per.hasManagePer) return permissionList['manage'].value;
|
|
if (per.hasWritePer) return permissionList['write'].value;
|
|
|
|
return permissionList['read'].value;
|
|
}, [permissionList, value]);
|
|
const selectedMultipleValues = useMemo(() => {
|
|
const per = new Permission({ per: value, permissionList });
|
|
|
|
return permissionSelectList.multipleCheckBoxList
|
|
.filter((item) => {
|
|
return per.checkPer(item.value);
|
|
})
|
|
.map((item) => item.value);
|
|
}, [permissionList, permissionSelectList.multipleCheckBoxList, value]);
|
|
|
|
const onSelectPer = (perValue: PermissionValueType) => {
|
|
if (perValue === value) return;
|
|
const per = new Permission({ per: perValue, permissionList });
|
|
per.addPer(...selectedMultipleValues);
|
|
onChange(per.value);
|
|
};
|
|
|
|
useOutsideClick({
|
|
ref: ref,
|
|
handler: () => {
|
|
setIsOpen(false);
|
|
}
|
|
});
|
|
|
|
return selectedSingleValue !== undefined ? (
|
|
<Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'}>
|
|
<Box
|
|
w="fit-content"
|
|
onMouseEnter={() => {
|
|
if (trigger === 'hover') {
|
|
setIsOpen(true);
|
|
}
|
|
clearTimeout(closeTimer.current);
|
|
}}
|
|
onMouseLeave={() => {
|
|
if (trigger === 'hover') {
|
|
closeTimer.current = setTimeout(() => {
|
|
setIsOpen(false);
|
|
}, 100);
|
|
}
|
|
}}
|
|
>
|
|
<MenuButton
|
|
ref={ref}
|
|
position={'relative'}
|
|
onClickCapture={() => {
|
|
if (trigger === 'click') {
|
|
setIsOpen(!isOpen);
|
|
}
|
|
}}
|
|
>
|
|
{Button}
|
|
</MenuButton>
|
|
<MenuList
|
|
minW={isOpen ? `${width}px !important` : 0}
|
|
p="3"
|
|
border={'1px solid #fff'}
|
|
boxShadow={
|
|
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
|
|
}
|
|
zIndex={99}
|
|
overflowY={'auto'}
|
|
whiteSpace={'pre-wrap'}
|
|
>
|
|
{/* The list of single select permissions */}
|
|
{permissionSelectList.singleCheckBoxList.map((item) => {
|
|
const change = () => {
|
|
const per = new Permission({ per: value, permissionList });
|
|
per.removePer(selectedSingleValue);
|
|
per.addPer(item.value);
|
|
onSelectPer(per.value);
|
|
};
|
|
|
|
return (
|
|
<Flex
|
|
key={item.value}
|
|
{...(selectedSingleValue === item.value
|
|
? {
|
|
color: 'primary.600'
|
|
}
|
|
: {})}
|
|
{...MenuStyle}
|
|
onClick={change}
|
|
maxW={['70vw', '260px']}
|
|
>
|
|
<Radio isChecked={selectedSingleValue === item.value} />
|
|
<Box ml={4}>
|
|
<Box>{t(item.name as any)}</Box>
|
|
<Box color={'myGray.500'} fontSize={'mini'}>
|
|
{t(item.description as any)}
|
|
</Box>
|
|
</Box>
|
|
</Flex>
|
|
);
|
|
})}
|
|
|
|
{permissionSelectList.multipleCheckBoxList.length > 0 && (
|
|
<>
|
|
<MyDivider my={2} />
|
|
{permissionSelectList.multipleCheckBoxList.map((item) => {
|
|
const isChecked = selectedMultipleValues.includes(item.value);
|
|
const isDisabled = new Permission({
|
|
per: selectedSingleValue,
|
|
permissionList
|
|
}).checkPer(item.value);
|
|
const change = () => {
|
|
if (isDisabled) return;
|
|
const per = new Permission({ per: value, permissionList });
|
|
if (isChecked) {
|
|
per.removePer(item.value);
|
|
} else {
|
|
per.addPer(item.value);
|
|
}
|
|
onChange(per.value);
|
|
};
|
|
return (
|
|
<Flex
|
|
key={item.value}
|
|
{...(isChecked
|
|
? {
|
|
color: 'primary.600'
|
|
}
|
|
: {})}
|
|
{...MenuStyle}
|
|
maxW={['70vw', '260px']}
|
|
{...(isDisabled
|
|
? {
|
|
cursor: 'not-allowed'
|
|
}
|
|
: {
|
|
cursor: 'pointer'
|
|
})}
|
|
onClick={change}
|
|
>
|
|
<Checkbox
|
|
size="lg"
|
|
isChecked={isChecked}
|
|
// onChange={(e) => {
|
|
// e.stopPropagation();
|
|
// e.preventDefault();
|
|
// }}
|
|
isDisabled={isDisabled}
|
|
/>
|
|
<Box ml={4}>
|
|
<Box>{t(item.name as any)}</Box>
|
|
<Box color={'myGray.500'} fontSize={'mini'}>
|
|
{t(item.description as any)}
|
|
</Box>
|
|
</Box>
|
|
</Flex>
|
|
);
|
|
})}
|
|
</>
|
|
)}
|
|
|
|
{onDelete && (
|
|
<>
|
|
<MyDivider my={2} h={'2px'} borderColor={'myGray.200'} />
|
|
<HStack
|
|
{...MenuStyle}
|
|
onClick={() => {
|
|
onDelete();
|
|
setIsOpen(false);
|
|
}}
|
|
>
|
|
<MyIcon name="delete" w="20px" color="red.600" />
|
|
<Box color="red.600">{t('common:Remove')}</Box>
|
|
</HStack>
|
|
</>
|
|
)}
|
|
</MenuList>
|
|
</Box>
|
|
</Menu>
|
|
) : null;
|
|
}
|
|
|
|
export default React.memo(PermissionSelect);
|