Skip to content

Commit

Permalink
feat: add AdvanceGroupPanelPermission controller
Browse files Browse the repository at this point in the history
include fetch and set, now under DevContainer
  • Loading branch information
moonrailgun committed Sep 10, 2023
1 parent e85c39e commit 86ef2d5
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 18 deletions.
3 changes: 3 additions & 0 deletions client/packages/design/components/WebMetaForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -72,6 +74,7 @@ const WebFastifyFormContainer: FastifyFormContainerComponent = React.memo(
wrapperCol={layout === 'vertical' ? { xs: 24 } : { sm: 24, md: 16 }}
>
{props.children}
{suffixElement}
{submitButtonRender}
</Form>
);
Expand Down
15 changes: 15 additions & 0 deletions client/shared/hooks/useEditValue.ts
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions client/shared/hooks/useLazyValue.ts
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 2 additions & 0 deletions client/shared/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion client/web/src/components/CollapseView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
71 changes: 56 additions & 15 deletions client/web/src/components/modals/GroupPanel/ModifyGroupPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
* 修改群组面板
Expand All @@ -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>
);
Expand Down
15 changes: 13 additions & 2 deletions client/web/src/components/modals/GroupPanel/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,21 +30,32 @@ export function buildDataFromValues(values: GroupPanelValues) {
provider,
pluginPanelName,
meta,
permissionMap,
fallbackPermissions,
};
}

/**
* 从群组面板信息中获取面板属性修改相关信息
*/
export function pickValuesFromGroupPanelInfo(
groupPanelInfo: GroupPanel
groupPanelInfo: GroupPanel | null
): GroupPanelValues {
if (groupPanelInfo === null) {
return {
name: '',
type: GroupPanelType.TEXT,
};
}

return {
...groupPanelInfo.meta,
name: groupPanelInfo.name,
type:
groupPanelInfo.type === GroupPanelType.PLUGIN
? String(groupPanelInfo.pluginPanelName)
: groupPanelInfo.type,
permissionMap: groupPanelInfo.permissionMap,
fallbackPermissions: groupPanelInfo.fallbackPermissions,
};
}

0 comments on commit 86ef2d5

Please sign in to comment.