From cbedd4539c750fd647ee13dee15c7cb0556029c2 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 22 Sep 2020 21:41:35 -0700 Subject: [PATCH] Performance cleanup; check main box when all children are checked; closes #2379 --- .../components/elements/TitledGreyBox.tsx | 5 +- .../server/users/EditSubuserModal.tsx | 178 ++++++++++-------- 2 files changed, 107 insertions(+), 76 deletions(-) diff --git a/resources/scripts/components/elements/TitledGreyBox.tsx b/resources/scripts/components/elements/TitledGreyBox.tsx index 3e83f8689..ef4b888bc 100644 --- a/resources/scripts/components/elements/TitledGreyBox.tsx +++ b/resources/scripts/components/elements/TitledGreyBox.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import React, { memo } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import tw from 'twin.macro'; +import isEqual from 'react-fast-compare'; interface Props { icon?: IconProp; @@ -27,4 +28,4 @@ const TitledGreyBox = ({ icon, title, children, className }: Props) => ( ); -export default TitledGreyBox; +export default memo(TitledGreyBox, isEqual); diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx index 9396d579e..482a6b86b 100644 --- a/resources/scripts/components/server/users/EditSubuserModal.tsx +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useEffect, useRef } from 'react'; +import React, { forwardRef, memo, useCallback, useEffect, useRef } from 'react'; import { Subuser } from '@/state/server/subusers'; import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { array, object, string } from 'yup'; @@ -11,7 +11,6 @@ import Checkbox from '@/components/elements/Checkbox'; import styled from 'styled-components/macro'; import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser'; import { ServerContext } from '@/state/server'; -import { httpErrorToHuman } from '@/api/http'; import FlashMessageRender from '@/components/FlashMessageRender'; import Can from '@/components/elements/Can'; import { usePermissions } from '@/plugins/usePermissions'; @@ -20,6 +19,7 @@ import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import Label from '@/components/elements/Label'; import Input from '@/components/elements/Input'; +import isEqual from 'react-fast-compare'; type Props = { subuser?: Subuser; @@ -31,7 +31,7 @@ interface Values { } const PermissionLabel = styled.label` - ${tw`flex items-center border border-transparent rounded md:p-2`}; + ${tw`flex items-center border border-transparent rounded md:p-2 transition-colors duration-75`}; text-transform: none; &:not(.disabled) { @@ -41,6 +41,10 @@ const PermissionLabel = styled.label` ${tw`border-neutral-500 bg-neutral-800`}; } } + + &:not(:first-of-type) { + ${tw`mt-4 sm:mt-2`}; + } &.disabled { ${tw`opacity-50`}; @@ -51,8 +55,58 @@ const PermissionLabel = styled.label` } `; +interface TitleProps { + isEditable: boolean; + permission: string; + permissions: string[]; + children: React.ReactNode; + className?: string; +} + +const PermissionTitledBox = memo(({ isEditable, permission, permissions, className, children }: TitleProps) => { + const { values, setFieldValue } = useFormikContext(); + + const onCheckboxClicked = useCallback((e: React.ChangeEvent) => { + console.log(e.currentTarget.checked, [ + ...values.permissions, + ...permissions.filter(p => !values.permissions.includes(p)), + ]); + + if (e.currentTarget.checked) { + setFieldValue('permissions', [ + ...values.permissions, + ...permissions.filter(p => !values.permissions.includes(p)), + ]); + } else { + setFieldValue('permissions', [ + ...values.permissions.filter(p => !permissions.includes(p)), + ]); + } + }, [ permissions, values.permissions ]); + + return ( + +

{permission}

+ {isEditable && + values.permissions.includes(p))} + onChange={onCheckboxClicked} + /> + } + + } + className={className} + > + {children} +
+ ); +}, isEqual); + const EditSubuserModal = forwardRef(({ subuser, ...props }, ref) => { - const { values, isSubmitting, setFieldValue } = useFormikContext(); + const { isSubmitting } = useFormikContext(); const [ canEditUser ] = usePermissions(subuser ? [ 'user.update' ] : [ 'user.create' ]); const permissions = useStoreState(state => state.permissions.data); @@ -104,73 +158,48 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr }
- {Object.keys(permissions).filter(key => key !== 'websocket').map((key, index) => ( - -

{key}

- {canEditUser && - { - if (e.currentTarget.checked) { - setFieldValue('permissions', [ - ...values.permissions, - ...Object.keys(permissions[key].keys) - .map(pkey => `${key}.${pkey}`) - .filter(permission => values.permissions.indexOf(permission) === -1), - ]); - } else { - setFieldValue('permissions', [ - ...values.permissions.filter( - permission => Object.keys(permissions[key].keys) - .map(pkey => `${key}.${pkey}`) - .indexOf(permission) < 0, - ), - ]); + {Object.keys(permissions).filter(key => key !== 'websocket').map((key, index) => { + const group = Object.keys(permissions[key].keys).map(pkey => `${key}.${pkey}`); + + return ( + 0 ? tw`mt-4` : undefined} + > +

+ {permissions[key].description} +

+ {Object.keys(permissions[key].keys).map(pkey => ( + +
+ +
+
+ + {permissions[key].keys[pkey].length > 0 && +

+ {permissions[key].keys[pkey]} +

} - }} - /> - } -
- } - css={index > 0 ? tw`mt-4` : undefined} - > -

- {permissions[key].description} -

- {Object.keys(permissions[key].keys).map((pkey, index) => ( - 0 ? tw`mt-4 sm:mt-2` : undefined, - ]} - className={(!canEditUser || editablePermissions.indexOf(`${key}.${pkey}`) < 0) ? 'disabled' : undefined} - > -
- -
-
- - {permissions[key].keys[pkey].length > 0 && -

- {permissions[key].keys[pkey]} -

- } -
-
- ))} -
- ))} +
+ + ))} + + ); + })}
@@ -187,8 +216,7 @@ export default ({ subuser, ...props }: Props) => { const ref = useRef(null); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser); - - const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('user:edit'); @@ -200,7 +228,7 @@ export default ({ subuser, ...props }: Props) => { .catch(error => { console.error(error); setSubmitting(false); - addError({ key: 'user:edit', message: httpErrorToHuman(error) }); + clearAndAddHttpError({ key: 'user:edit', error }); if (ref.current) { ref.current.scrollIntoView(); @@ -209,7 +237,9 @@ export default ({ subuser, ...props }: Props) => { }; useEffect(() => { - clearFlashes('user:edit'); + return () => { + clearFlashes('user:edit'); + }; }, []); return (