feat: add AdvanceGroupPanelPermission controller

include fetch and set, now under DevContainer
pull/146/merge
moonrailgun 2 years ago
parent e85c39e570
commit 86ef2d5c20

@ -35,6 +35,8 @@ export function setWebFastifyFormConfig(config: typeof webFastifyFormConfig) {
const WebFastifyFormContainer: FastifyFormContainerComponent = React.memo(
(props) => {
const layout = props.layout;
const suffixElement = props.extraProps?.suffixElement;
const submitButtonRender = useMemo(() => {
return (
<Form.Item
@ -72,6 +74,7 @@ const WebFastifyFormContainer: FastifyFormContainerComponent = React.memo(
wrapperCol={layout === 'vertical' ? { xs: 24 } : { sm: 24, md: 16 }}
>
{props.children}
{suffixElement}
{submitButtonRender}
</Form>
);

@ -0,0 +1,15 @@
import { useCallback, useLayoutEffect, useState } from 'react';
export function useEditValue<T>(value: T, onChange: (val: T) => void) {
const [inner, setInner] = useState(value);
useLayoutEffect(() => {
setInner(value);
}, [value]);
const onSave = useCallback(() => {
onChange(inner);
}, [inner, onChange]);
return [inner, setInner, onSave] as const;
}

@ -0,0 +1,17 @@
import { useLayoutEffect, useState } from 'react';
import { useEvent } from './useEvent';
export function useLazyValue<T>(value: T, onChange: (val: T) => void) {
const [inner, setInner] = useState(value);
useLayoutEffect(() => {
setInner(value);
}, [value]);
const handleChange = useEvent((val: T) => {
setInner(val);
onChange(val);
});
return [inner, handleChange] as const;
}

@ -74,8 +74,10 @@ export { useAsyncFn } from './hooks/useAsyncFn';
export { useAsyncRefresh } from './hooks/useAsyncRefresh';
export { useAsyncRequest } from './hooks/useAsyncRequest';
export { useDebounce } from './hooks/useDebounce';
export { useEditValue } from './hooks/useEditValue';
export { useEvent } from './hooks/useEvent';
export { useInterval } from './hooks/useInterval';
export { useLazyValue } from './hooks/useLazyValue';
export { useMemoizedFn } from './hooks/useMemoizedFn';
export { useMountedState } from './hooks/useMountedState';
export { usePrevious } from './hooks/usePrevious';

@ -4,10 +4,11 @@ import React from 'react';
interface CollapseViewProps extends React.PropsWithChildren {
title: string;
className?: string;
style?: React.CSSProperties;
}
export const CollapseView: React.FC<CollapseViewProps> = React.memo((props) => {
return (
<Collapse className={props.className}>
<Collapse className={props.className} style={props.style}>
<Collapse.Panel header={props.title} key="main">
{props.children}
</Collapse.Panel>

@ -0,0 +1,120 @@
import { PermissionList } from '@/components/PermissionList';
import { Button } from 'antd';
import clsx from 'clsx';
import React, { PropsWithChildren, useState } from 'react';
import {
ALL_PERMISSION,
getDefaultPermissionList,
t,
useAppSelector,
useEvent,
useLazyValue,
} from 'tailchat-shared';
import _isEqual from 'lodash/isEqual';
interface AdvanceGroupPanelPermissionProps {
height?: number;
groupId: string;
panelId: string;
onChange: (
permissionMap: Record<string | typeof ALL_PERMISSION, string[]> | undefined
) => void;
}
export const AdvanceGroupPanelPermission: React.FC<AdvanceGroupPanelPermissionProps> =
React.memo((props) => {
const [selectedRoleId, setSelectedRoleId] = useState<
typeof ALL_PERMISSION | string
>(ALL_PERMISSION);
const roles = useAppSelector((state) => {
const groupInfo = state.group.groups[props.groupId];
return groupInfo.roles;
});
const permissionMap: Record<string | typeof ALL_PERMISSION, string[]> =
useAppSelector((state) => {
const groupInfo = state.group.groups[props.groupId];
const panelInfo = groupInfo.panels.find((p) => p.id === props.panelId);
if (!panelInfo) {
return { [ALL_PERMISSION]: getDefaultPermissionList() };
} else {
return {
[ALL_PERMISSION]:
panelInfo.fallbackPermissions ?? getDefaultPermissionList(),
...panelInfo.permissionMap,
};
}
}, _isEqual);
const [editPermissionMap, setEditPermissionMap] = useLazyValue(
permissionMap,
props.onChange
);
const handleUpdatePermissionMap = useEvent((permissions: string[]) => {
const newMap = { ...editPermissionMap, [selectedRoleId]: permissions };
setEditPermissionMap(newMap);
});
const handleSyncWithGroup = useEvent(() => {
setEditPermissionMap({
[ALL_PERMISSION]: getDefaultPermissionList(),
});
props.onChange(undefined);
});
return (
<div className="flex" style={{ width: 540 }}>
<div>
<RoleItem
active={selectedRoleId === ALL_PERMISSION}
onClick={() => setSelectedRoleId(ALL_PERMISSION)}
>
{t('所有人')}
</RoleItem>
{roles.map((r) => (
<RoleItem
key={r._id}
active={selectedRoleId === r._id}
onClick={() => setSelectedRoleId(r._id)}
>
{r.name}
</RoleItem>
))}
</div>
<div className="flex-1 overflow-auto" style={{ height: props.height }}>
<div className="text-right">
<Button onClick={handleSyncWithGroup}>{t('与群组配置同步')}</Button>
</div>
<PermissionList
value={editPermissionMap[selectedRoleId] ?? []}
onChange={handleUpdatePermissionMap}
/>
</div>
</div>
);
});
AdvanceGroupPanelPermission.displayName = 'AdvanceGroupPanelPermission';
const RoleItem: React.FC<
PropsWithChildren<{
active: boolean;
onClick?: () => void;
}>
> = React.memo((props) => {
return (
<div
className={clsx(
'px-2 py-1 rounded cursor-pointer mb-1 hover:bg-black hover:bg-opacity-20',
{
'bg-black bg-opacity-20': props.active,
}
)}
onClick={props.onClick}
>
{props.children}
</div>
);
});
RoleItem.displayName = 'RoleItem';

@ -6,12 +6,18 @@ import {
modifyGroupPanel,
showToasts,
useGroupPanelInfo,
useEvent,
ALL_PERMISSION,
DevContainer,
} from 'tailchat-shared';
import { ModalWrapper } from '../../Modal';
import { WebMetaForm } from 'tailchat-design';
import { buildDataFromValues, pickValuesFromGroupPanelInfo } from './helper';
import type { GroupPanelValues } from './types';
import { useGroupPanelFields } from './useGroupPanelFields';
import { AdvanceGroupPanelPermission } from './AdvanceGroupPanelPermission';
import { CollapseView } from '@/components/CollapseView';
import _omit from 'lodash/omit';
/**
*
@ -22,35 +28,70 @@ export const ModalModifyGroupPanel: React.FC<{
onSuccess?: () => void;
}> = React.memo((props) => {
const groupPanelInfo = useGroupPanelInfo(props.groupId, props.groupPanelId);
const [currentValues, setValues] = useState<Partial<GroupPanelValues>>({});
const [, handleSubmit] = useAsyncRequest(
async (values: GroupPanelValues) => {
await modifyGroupPanel(
props.groupId,
props.groupPanelId,
buildDataFromValues(values)
);
showToasts(t('修改成功'), 'success');
typeof props.onSuccess === 'function' && props.onSuccess();
},
[props.groupId, props.groupPanelId, props.onSuccess]
const [currentValues, setValues] = useState<GroupPanelValues>(
pickValuesFromGroupPanelInfo(groupPanelInfo)
);
const [, handleSubmit] = useAsyncRequest(async () => {
await modifyGroupPanel(
props.groupId,
props.groupPanelId,
buildDataFromValues(currentValues)
);
showToasts(t('修改成功'), 'success');
typeof props.onSuccess === 'function' && props.onSuccess();
}, [props.groupId, props.groupPanelId, props.onSuccess, currentValues]);
const { fields, schema } = useGroupPanelFields(props.groupId, currentValues);
const handleUpdateValues = useEvent((values: Partial<GroupPanelValues>) => {
setValues((state) => ({
...state,
...values,
}));
});
if (!groupPanelInfo) {
return <LoadingSpinner />;
}
return (
<ModalWrapper title={t('编辑群组面板')} style={{ maxWidth: 440 }}>
<ModalWrapper title={t('编辑群组面板')} style={{ maxWidth: 600 }}>
<WebMetaForm
schema={schema}
fields={fields.filter((f) => f.type !== 'type')} // 变更时不显示类型
initialValues={pickValuesFromGroupPanelInfo(groupPanelInfo)}
onChange={setValues}
onChange={handleUpdateValues}
onSubmit={handleSubmit}
extraProps={{
suffixElement: (
<DevContainer>
<CollapseView title={t('高级权限控制')} className="mb-2">
<AdvanceGroupPanelPermission
height={320}
groupId={props.groupId}
panelId={props.groupPanelId}
onChange={(permissionMap) => {
if (permissionMap) {
const fallbackPermissions = permissionMap[ALL_PERMISSION];
const others = { ...permissionMap };
handleUpdateValues({
fallbackPermissions,
permissionMap: _omit(others, [ALL_PERMISSION]),
});
} else {
handleUpdateValues({
fallbackPermissions: undefined,
permissionMap: undefined,
});
}
}}
/>
</CollapseView>
</DevContainer>
),
}}
/>
</ModalWrapper>
);

@ -6,7 +6,7 @@ import type { GroupPanelValues } from './types';
*
*/
export function buildDataFromValues(values: GroupPanelValues) {
const { name, type, ...meta } = values;
const { name, type, permissionMap, fallbackPermissions, ...meta } = values;
let panelType: number;
let provider: string | undefined = undefined;
let pluginPanelName: string | undefined = undefined;
@ -30,6 +30,8 @@ export function buildDataFromValues(values: GroupPanelValues) {
provider,
pluginPanelName,
meta,
permissionMap,
fallbackPermissions,
};
}
@ -37,8 +39,15 @@ export function buildDataFromValues(values: GroupPanelValues) {
*
*/
export function pickValuesFromGroupPanelInfo(
groupPanelInfo: GroupPanel
groupPanelInfo: GroupPanel | null
): GroupPanelValues {
if (groupPanelInfo === null) {
return {
name: '',
type: GroupPanelType.TEXT,
};
}
return {
...groupPanelInfo.meta,
name: groupPanelInfo.name,
@ -46,5 +55,7 @@ export function pickValuesFromGroupPanelInfo(
groupPanelInfo.type === GroupPanelType.PLUGIN
? String(groupPanelInfo.pluginPanelName)
: groupPanelInfo.type,
permissionMap: groupPanelInfo.permissionMap,
fallbackPermissions: groupPanelInfo.fallbackPermissions,
};
}

Loading…
Cancel
Save