diff --git a/frontend/package.json b/frontend/package.json
index 4d519b14be..f8ac855c38 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -35,7 +35,8 @@
"fmt": "prettier src --write --loglevel warn",
"fmt:check": "prettier src --check",
"e2e": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=admin,AUTH_PASSWORD=unleash4all",
- "e2e:heroku": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=example@example.com"
+ "e2e:heroku": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=example@example.com",
+ "isready": "yarn lint && yarn fmt && yarn prepare"
},
"devDependencies": {
"@emotion/react": "11.9.3",
diff --git a/frontend/src/assets/icons/24_Negator off.svg b/frontend/src/assets/icons/24_Negator off.svg
new file mode 100644
index 0000000000..a3dababecf
--- /dev/null
+++ b/frontend/src/assets/icons/24_Negator off.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/icons/24_Negator.svg b/frontend/src/assets/icons/24_Negator.svg
new file mode 100644
index 0000000000..84e1638591
--- /dev/null
+++ b/frontend/src/assets/icons/24_Negator.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/icons/24_Text format off.svg b/frontend/src/assets/icons/24_Text format off.svg
new file mode 100644
index 0000000000..c4e0102f8a
--- /dev/null
+++ b/frontend/src/assets/icons/24_Text format off.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/icons/24_Text format.svg b/frontend/src/assets/icons/24_Text format.svg
new file mode 100644
index 0000000000..ea8ae72501
--- /dev/null
+++ b/frontend/src/assets/icons/24_Text format.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/component/addons/AddonForm/AddonForm.tsx b/frontend/src/component/addons/AddonForm/AddonForm.tsx
index 0909ae38d6..f7bf2a49e2 100644
--- a/frontend/src/component/addons/AddonForm/AddonForm.tsx
+++ b/frontend/src/component/addons/AddonForm/AddonForm.tsx
@@ -6,13 +6,7 @@ import React, {
useState,
VFC,
} from 'react';
-import {
- Button,
- Divider,
- FormControlLabel,
- Switch,
- TextField,
-} from '@mui/material';
+import { Button, Divider, FormControlLabel, Switch } from '@mui/material';
import produce from 'immer';
import { trim } from 'component/common/util';
import { IAddon, IAddonProvider } from 'interfaces/addons';
diff --git a/frontend/src/component/addons/AddonForm/AddonMultiSelector/AddonMultiSelector.tsx b/frontend/src/component/addons/AddonForm/AddonMultiSelector/AddonMultiSelector.tsx
index 0d657c2b7a..6019204c23 100644
--- a/frontend/src/component/addons/AddonForm/AddonMultiSelector/AddonMultiSelector.tsx
+++ b/frontend/src/component/addons/AddonForm/AddonMultiSelector/AddonMultiSelector.tsx
@@ -7,14 +7,7 @@ import {
AutocompleteRenderOptionState,
} from '@mui/material/Autocomplete';
import { styled } from '@mui/system';
-import {
- Autocomplete,
- Box,
- capitalize,
- Checkbox,
- Paper,
- TextField,
-} from '@mui/material';
+import { capitalize, Checkbox, Paper, TextField } from '@mui/material';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx
index dcd4f2d6b8..f032f3e930 100644
--- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx
+++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx
@@ -1,14 +1,9 @@
-import { Popover, Badge, styled, Tooltip } from '@mui/material';
-import { IGroup, IGroupUser, Role } from 'interfaces/group';
-import { Link } from 'react-router-dom';
+import { Badge, Popover, styled } from '@mui/material';
+import { IGroupUser, Role } from 'interfaces/group';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Badge as StyledBadge } from 'component/common/Badge/Badge';
-
-import { RemoveGroup } from 'component/admin/groups/RemoveGroup/RemoveGroup';
-import { useState } from 'react';
import StarIcon from '@mui/icons-material/Star';
-import { IUser } from '../../../../../../../interfaces/user';
const StyledPopover = styled(Popover)(({ theme }) => ({
pointerEvents: 'none',
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts
index 61e799332e..45fc481d0b 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts
@@ -17,8 +17,8 @@ export const useStyles = makeStyles()(theme => ({
fill: '#fff',
},
accordion: {
- border: `1px solid ${theme.palette.grey[300]}`,
- borderRadius: '5px',
+ border: `1px solid ${theme.palette.grey[400]}`,
+ borderRadius: '8px',
backgroundColor: '#fff',
boxShadow: 'none',
margin: 0,
@@ -46,6 +46,11 @@ export const useStyles = makeStyles()(theme => ({
position: 'relative',
},
},
+ headerValuesContainerWrapper: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ },
headerValuesContainer: {
display: 'flex',
flexDirection: 'column',
@@ -81,12 +86,6 @@ export const useStyles = makeStyles()(theme => ({
top: '-10px',
},
},
- help: {
- fill: theme.palette.grey[600],
- [theme.breakpoints.down(860)]: {
- display: 'none',
- },
- },
headerText: {
maxWidth: '400px',
fontSize: theme.fontSizes.smallBody,
@@ -105,8 +104,9 @@ export const useStyles = makeStyles()(theme => ({
[theme.breakpoints.down(770)]: {
marginTop: '1rem',
},
+ display: 'inline-flex',
},
- headerSelect: { marginRight: '2rem', width: '200px' },
+ headerSelect: { marginRight: '1rem', width: '200px' },
chip: {
margin: '0 0.5rem 0.5rem 0',
},
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx
index c6abd4e958..ffb54cc4a1 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.tsx
@@ -33,6 +33,7 @@ export const ConstraintAccordion = ({
constraint={constraint}
onCancel={onCancel}
onSave={onSave!}
+ onDelete={onDelete}
compact={compact}
/>
}
@@ -41,7 +42,6 @@ export const ConstraintAccordion = ({
constraint={constraint}
onEdit={onEdit}
onDelete={onDelete}
- compact={compact}
/>
}
/>
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx
index 0f7051ae2f..73b30eb261 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEdit.tsx
@@ -19,6 +19,7 @@ interface IConstraintAccordionEditProps {
onCancel: () => void;
onSave: (constraint: IConstraint) => void;
compact: boolean;
+ onDelete?: () => void;
}
export const CANCEL = 'cancel';
@@ -48,6 +49,7 @@ export const ConstraintAccordionEdit = ({
compact,
onCancel,
onSave,
+ onDelete,
}: IConstraintAccordionEditProps) => {
const [localConstraint, setLocalConstraint] = useState(
cleanConstraint(constraint)
@@ -203,6 +205,9 @@ export const ConstraintAccordionEdit = ({
setOperator={setOperator}
action={action}
compact={compact}
+ setInvertedOperator={setInvertedOperator}
+ setCaseInsensitive={setCaseInsensitive}
+ onDelete={onDelete}
/>
@@ -214,10 +219,8 @@ export const ConstraintAccordionEdit = ({
localConstraint={localConstraint}
setValues={setValues}
setValue={setValue}
- setCaseInsensitive={setCaseInsensitive}
triggerTransition={triggerTransition}
setAction={setAction}
- setInvertedOperator={setInvertedOperator}
onSubmit={onSubmit}
>
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/CaseInsensitive/CaseInsensitive.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/CaseInsensitive/CaseInsensitive.tsx
deleted file mode 100644
index f0c2a71711..0000000000
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/CaseInsensitive/CaseInsensitive.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { FormControlLabel, Switch } from '@mui/material';
-import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
-
-interface ICaseInsensitiveProps {
- caseInsensitive: boolean;
- setCaseInsensitive: (caseInsensitive: boolean) => void;
-}
-
-export const CaseInsensitive = ({
- caseInsensitive,
- setCaseInsensitive,
-}: ICaseInsensitiveProps) => {
- return (
- <>
-
- Should the constraint be case insensitive?
-
- setCaseInsensitive(!caseInsensitive)}
- color="primary"
- />
- }
- label="Case insensitive"
- />
- >
- );
-};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.styles.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.styles.ts
index 017450106d..82c2efa82a 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.styles.ts
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.styles.ts
@@ -3,6 +3,7 @@ import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
inputContainer: {
padding: '1rem',
+ backgroundColor: theme.palette.neutral.light,
},
buttonContainer: {
display: 'flex',
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx
index 57efd2da2b..ebfcb4d3a9 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx
@@ -1,8 +1,7 @@
-import { Button, FormControlLabel, Switch } from '@mui/material';
+import { Button } from '@mui/material';
import { IConstraint } from 'interfaces/strategy';
import { CANCEL } from '../ConstraintAccordionEdit';
-import { ConstraintFormHeader } from './ConstraintFormHeader/ConstraintFormHeader';
import { useStyles } from './ConstraintAccordionEditBody.styles';
import React from 'react';
import { newOperators } from 'constants/operators';
@@ -16,21 +15,12 @@ interface IConstraintAccordionBody {
triggerTransition: () => void;
setValue: (value: string) => void;
setAction: React.Dispatch>;
- setCaseInsensitive: () => void;
- setInvertedOperator: () => void;
onSubmit: () => void;
}
export const ConstraintAccordionEditBody: React.FC<
IConstraintAccordionBody
-> = ({
- localConstraint,
- children,
- triggerTransition,
- setInvertedOperator,
- setAction,
- onSubmit,
-}) => {
+> = ({ localConstraint, children, triggerTransition, setAction, onSubmit }) => {
const { classes: styles } = useStyles();
return (
@@ -40,10 +30,6 @@ export const ConstraintAccordionEditBody: React.FC<
condition={oneOf(newOperators, localConstraint.operator)}
show={}
/>
-
{children}
@@ -71,33 +57,3 @@ export const ConstraintAccordionEditBody: React.FC<
>
);
};
-
-interface IInvertedOperatorProps {
- inverted: boolean;
- setInvertedOperator: () => void;
-}
-
-const InvertedOperator = ({
- inverted,
- setInvertedOperator,
-}: IInvertedOperatorProps) => {
- return (
- <>
-
- Should the operator be negated? (this will make the operator do
- the opposite)
-
- setInvertedOperator()}
- color="primary"
- />
- }
- label="Negated"
- />
- >
- );
-};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx
index 406c14a249..91c9e2b190 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/FreeTextInput/FreeTextInput.tsx
@@ -110,8 +110,8 @@ export const FreeTextInput = ({
}
- elseShow={Editing
}
- />
-
-
-
-
-
+
);
};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/CaseSensitiveButton/CaseSensitiveButton.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/CaseSensitiveButton/CaseSensitiveButton.tsx
new file mode 100644
index 0000000000..16edba2e5a
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/CaseSensitiveButton/CaseSensitiveButton.tsx
@@ -0,0 +1,46 @@
+import { Tooltip } from '@mui/material';
+import { ReactComponent as CaseSensitive } from 'assets/icons/24_Text format.svg';
+import { ReactComponent as CaseSensitiveOff } from 'assets/icons/24_Text format off.svg';
+import React from 'react';
+import {
+ StyledToggleButtonOff,
+ StyledToggleButtonOn,
+} from '../StyledToggleButton';
+import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
+import { IConstraint } from '../../../../../../interfaces/strategy';
+
+interface CaseSensitiveButtonProps {
+ localConstraint: IConstraint;
+ setCaseInsensitive: () => void;
+}
+
+export const CaseSensitiveButton = ({
+ localConstraint,
+ setCaseInsensitive,
+}: CaseSensitiveButtonProps) => {
+ return (
+
+
+
+
+
+ }
+ elseShow={
+
+
+
+
+
+ }
+ />
+ );
+};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/InvertedOperatorButton/InvertedOperatorButton.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/InvertedOperatorButton/InvertedOperatorButton.tsx
new file mode 100644
index 0000000000..b05667b4d0
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/InvertedOperatorButton/InvertedOperatorButton.tsx
@@ -0,0 +1,46 @@
+import { Tooltip } from '@mui/material';
+import { ReactComponent as NegatedIcon } from '../../../../../../assets/icons/24_Negator.svg';
+import { ReactComponent as NegatedIconOff } from '../../../../../../assets/icons/24_Negator off.svg';
+import React from 'react';
+import { IConstraint } from '../../../../../../interfaces/strategy';
+import {
+ StyledToggleButtonOff,
+ StyledToggleButtonOn,
+} from '../StyledToggleButton';
+import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
+
+interface InvertedOperatorButtonProps {
+ localConstraint: IConstraint;
+ setInvertedOperator: () => void;
+}
+
+export const InvertedOperatorButton = ({
+ localConstraint,
+ setInvertedOperator,
+}: InvertedOperatorButtonProps) => {
+ return (
+
+
+
+
+
+ }
+ elseShow={
+
+
+
+
+
+ }
+ />
+ );
+};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/StyledToggleButton.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/StyledToggleButton.tsx
new file mode 100644
index 0000000000..6e6e60110b
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/StyledToggleButton/StyledToggleButton.tsx
@@ -0,0 +1,30 @@
+import { styled } from '@mui/system';
+import { IconButton } from '@mui/material';
+
+export const StyledToggleButtonOff = styled(IconButton)(({ theme }) => ({
+ width: '28px',
+ minWidth: '28px',
+ maxWidth: '28px',
+ backgroundColor: theme.palette.tertiary.background,
+ borderRadius: theme.shape.borderRadius,
+ padding: '0 1px 0',
+ marginRight: '1rem',
+ '&:hover': {
+ background: theme.palette.tertiary.contrast[300],
+ },
+}));
+
+export const StyledToggleButtonOn = styled(IconButton)(({ theme }) => ({
+ width: '28px',
+ minWidth: '28px',
+ maxWidth: '28px',
+ color: theme.palette.primary.contrastText,
+ backgroundColor: theme.palette.primary.main,
+ borderRadius: theme.shape.borderRadius,
+ marginRight: '1rem',
+ padding: '0 1px 0',
+ '&:hover': {
+ color: theme.palette.primary.contrastText,
+ backgroundColor: theme.palette.primary.main,
+ },
+}));
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions.tsx
new file mode 100644
index 0000000000..810a3b9e63
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import { IconButton, Tooltip } from '@mui/material';
+import { Delete, Edit } from '@mui/icons-material';
+import { useStyles } from '../ConstraintAccordion.styles';
+import { ConditionallyRender } from '../../ConditionallyRender/ConditionallyRender';
+
+interface ConstraintAccordionHeaderActionsProps {
+ onDelete?: () => void;
+ onEdit?: () => void;
+ disableEdit?: boolean;
+ disableDelete?: boolean;
+}
+
+export const ConstraintAccordionHeaderActions = ({
+ onEdit,
+ onDelete,
+ disableDelete = false,
+ disableEdit = false,
+}: ConstraintAccordionHeaderActionsProps) => {
+ const { classes: styles } = useStyles();
+ const onEditClick =
+ onEdit &&
+ ((event: React.SyntheticEvent) => {
+ event.stopPropagation();
+ onEdit();
+ });
+
+ const onDeleteClick =
+ onDelete &&
+ ((event: React.SyntheticEvent) => {
+ event.stopPropagation();
+ onDelete();
+ });
+
+ return (
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+ );
+};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.styles.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.styles.ts
index 43067815e7..830852c474 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.styles.ts
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.styles.ts
@@ -6,4 +6,17 @@ export const useStyles = makeStyles()(theme => ({
display: 'grid',
gap: '1rem',
},
+ help: {
+ fill: theme.palette.grey[600],
+ [theme.breakpoints.down(860)]: {
+ display: 'none',
+ },
+ marginLeft: '0.75rem',
+ },
+ addCustomLabel: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'start',
+ margin: '0.75rem 0',
+ },
}));
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx
index 2f33d8563b..03672fb694 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList.tsx
@@ -8,7 +8,8 @@ import { objectId } from 'utils/objectId';
import { useStyles } from './ConstraintAccordionList.styles';
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { Button } from '@mui/material';
+import { Button, Tooltip } from '@mui/material';
+import { Help } from '@mui/icons-material';
interface IConstraintAccordionListProps {
constraints: IConstraint[];
@@ -102,11 +103,25 @@ export const ConstraintAccordionList = forwardRef<
condition={Boolean(showCreateButton && onAdd)}
show={
+
+
Add any number of custom constraints
+
+
+
+
+
+
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx
index a56da12348..033f5b6c18 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx
@@ -1,5 +1,4 @@
import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
-import { ExpandMore } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy';
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
@@ -12,41 +11,52 @@ import {
} from 'constants/operators';
import { useStyles } from '../ConstraintAccordion.styles';
+import { useState } from 'react';
interface IConstraintAccordionViewProps {
constraint: IConstraint;
onDelete?: () => void;
onEdit?: () => void;
- compact: boolean;
}
export const ConstraintAccordionView = ({
- compact,
constraint,
onEdit,
onDelete,
}: IConstraintAccordionViewProps) => {
const { classes: styles } = useStyles();
+ const [expandable, setExpandable] = useState(true);
+ const [expanded, setExpanded] = useState(false);
const singleValue = oneOf(
[...semVerOperators, ...numOperators, ...dateOperators],
constraint.operator
);
+ const handleClick = () => {
+ if (expandable) {
+ setExpanded(!expanded);
+ }
+ };
+
return (
}
+ expandIcon={null}
+ onClick={handleClick}
>
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx
index 8521bdadf4..e9eddc7f85 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx
@@ -1,149 +1,43 @@
-import { Chip, IconButton, Tooltip, styled } from '@mui/material';
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
-import { Delete, Edit } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy';
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import React from 'react';
-import { formatConstraintValue } from 'utils/formatConstraintValue';
-import { useLocationSettings } from 'hooks/useLocationSettings';
-import { ConstraintOperator } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator';
-import classnames from 'classnames';
-
-const StyledHeaderText = styled('span')(({ theme }) => ({
- display: '-webkit-box',
- WebkitLineClamp: 3,
- WebkitBoxOrient: 'vertical',
- overflow: 'hidden',
- maxWidth: '100px',
- minWidth: '100px',
- marginRight: '10px',
- wordBreak: 'break-word',
- fontSize: theme.fontSizes.smallBody,
- [theme.breakpoints.down(710)]: {
- textAlign: 'center',
- padding: theme.spacing(1, 0),
- marginRight: 'inherit',
- maxWidth: 'inherit',
- },
-}));
-
-const StyledValuesSpan = styled('span')(({ theme }) => ({
- display: '-webkit-box',
- WebkitLineClamp: 2,
- WebkitBoxOrient: 'vertical',
- overflow: 'hidden',
- wordBreak: 'break-word',
- fontSize: theme.fontSizes.smallBody,
- [theme.breakpoints.down(710)]: {
- margin: theme.spacing(1, 0),
- textAlign: 'center',
- },
-}));
-
-const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
- [theme.breakpoints.down(710)]: {
- margin: theme.spacing(1, 0),
- },
-}));
+import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo';
+import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
interface IConstraintAccordionViewHeaderProps {
- compact: boolean;
constraint: IConstraint;
onDelete?: () => void;
onEdit?: () => void;
singleValue: boolean;
+ expanded: boolean;
+ allowExpand: (shouldExpand: boolean) => void;
}
export const ConstraintAccordionViewHeader = ({
- compact,
constraint,
onEdit,
onDelete,
singleValue,
+ allowExpand,
+ expanded,
}: IConstraintAccordionViewHeaderProps) => {
const { classes: styles } = useStyles();
- const { locationSettings } = useLocationSettings();
-
- const onEditClick =
- onEdit &&
- ((event: React.SyntheticEvent) => {
- event.stopPropagation();
- onEdit();
- });
-
- const onDeleteClick =
- onDelete &&
- ((event: React.SyntheticEvent) => {
- event.stopPropagation();
- onDelete();
- });
return (
-
-
-
- {constraint.contextName}
-
-
-
-
-
-
- }
- elseShow={
-
-
- {constraint?.values
- ?.map(value => value)
- .join(', ')}
-
-
- Expand to view all ({constraint?.values?.length}
- )
-
-
- }
- />
-
-
- (
-
-
-
-
-
- )}
- />
- (
-
-
-
-
-
- )}
- />
-
+
+
);
};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo.tsx
new file mode 100644
index 0000000000..3e2fc4e282
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo.tsx
@@ -0,0 +1,65 @@
+import { styled, Tooltip } from '@mui/material';
+import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
+import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
+import { ContraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue';
+import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues';
+import React from 'react';
+import { IConstraint } from '../../../../../../interfaces/strategy';
+import { useStyles } from '../../../ConstraintAccordion.styles';
+
+const StyledHeaderText = styled('span')(({ theme }) => ({
+ display: '-webkit-box',
+ WebkitLineClamp: 3,
+ WebkitBoxOrient: 'vertical',
+ overflow: 'hidden',
+ maxWidth: '100px',
+ minWidth: '100px',
+ marginRight: '10px',
+ wordBreak: 'break-word',
+ fontSize: theme.fontSizes.smallBody,
+ [theme.breakpoints.down(710)]: {
+ textAlign: 'center',
+ padding: theme.spacing(1, 0),
+ marginRight: 'inherit',
+ maxWidth: 'inherit',
+ },
+}));
+
+interface ConstraintAccordionViewHeaderMetaInfoProps {
+ constraint: IConstraint;
+ singleValue: boolean;
+ expanded: boolean;
+ allowExpand: (shouldExpand: boolean) => void;
+}
+
+export const ConstraintAccordionViewHeaderInfo = ({
+ constraint,
+ singleValue,
+ allowExpand,
+ expanded,
+}: ConstraintAccordionViewHeaderMetaInfoProps) => {
+ const { classes: styles } = useStyles();
+ return (
+
+
+ {constraint.contextName}
+
+
+
+ }
+ elseShow={
+
+ }
+ />
+
+ );
+};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintViewHeaderOperator/ConstraintViewHeaderOperator.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintViewHeaderOperator/ConstraintViewHeaderOperator.tsx
new file mode 100644
index 0000000000..9cd51b9269
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintViewHeaderOperator/ConstraintViewHeaderOperator.tsx
@@ -0,0 +1,36 @@
+import { IConstraint } from '../../../../../../interfaces/strategy';
+import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
+import { Tooltip } from '@mui/material';
+import { ReactComponent as NegatedIcon } from '../../../../../../assets/icons/24_Negator.svg';
+import { ConstraintOperator } from '../../../ConstraintOperator/ConstraintOperator';
+import React from 'react';
+import { useStyles } from '../../../ConstraintAccordion.styles';
+import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
+
+interface ConstraintViewHeaderOperatorProps {
+ constraint: IConstraint;
+}
+
+export const ConstraintViewHeaderOperator = ({
+ constraint,
+}: ConstraintViewHeaderOperatorProps) => {
+ const { classes: styles } = useStyles();
+
+ return (
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+ );
+};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues.tsx
new file mode 100644
index 0000000000..2ddb041d2b
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues.tsx
@@ -0,0 +1,102 @@
+import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
+import { oneOf } from '../../../../../../utils/oneOf';
+import { stringOperators } from '../../../../../../constants/operators';
+import { styled, Tooltip } from '@mui/material';
+import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg';
+import React, { useEffect, useRef, useState } from 'react';
+import classnames from 'classnames';
+import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
+import { IConstraint } from '../../../../../../interfaces/strategy';
+import { useStyles } from '../../../ConstraintAccordion.styles';
+import { getTextWidth } from '../../../helpers';
+
+const StyledValuesSpan = styled('span')(({ theme }) => ({
+ display: '-webkit-box',
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: 'vertical',
+ overflow: 'hidden',
+ wordBreak: 'break-word',
+ fontSize: theme.fontSizes.smallBody,
+ [theme.breakpoints.down(710)]: {
+ margin: theme.spacing(1, 0),
+ textAlign: 'center',
+ },
+}));
+
+interface ConstraintSingleValueProps {
+ constraint: IConstraint;
+ expanded: boolean;
+ allowExpand: (shouldExpand: boolean) => void;
+}
+
+export const ConstraintAccordionViewHeaderMultipleValues = ({
+ constraint,
+ expanded,
+ allowExpand,
+}: ConstraintSingleValueProps) => {
+ const { classes: styles } = useStyles();
+
+ const elementRef = useRef(null);
+
+ const [width, setWidth] = useState(0);
+ const [textWidth, setTextWidth] = useState(0);
+ const [expandable, setExpandable] = useState(false);
+
+ useEffect(() => {
+ if (elementRef && elementRef.current != null) {
+ setTextWidth(
+ Math.round(getTextWidth(elementRef.current.innerText) / 2) // 2 lines
+ );
+ setWidth(elementRef.current.clientWidth);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (textWidth && width) {
+ setExpandable(textWidth > width);
+ }
+ }, [textWidth, width]);
+
+ useEffect(() => {
+ allowExpand(expandable);
+ }, [expandable, allowExpand]);
+
+ return (
+
+
+
+
+
+
+ }
+ />
+
+
+ {constraint?.values?.map(value => value).join(', ')}
+
+
+ {!expanded
+ ? `View all (
+ ${constraint?.values?.length})`
+ : 'View less'}
+
+ }
+ />
+
+
+ );
+};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue.tsx
new file mode 100644
index 0000000000..00818af583
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue.tsx
@@ -0,0 +1,49 @@
+import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
+import { oneOf } from '../../../../../../utils/oneOf';
+import { stringOperators } from '../../../../../../constants/operators';
+import { Chip, styled, Tooltip } from '@mui/material';
+import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg';
+import { formatConstraintValue } from '../../../../../../utils/formatConstraintValue';
+import React from 'react';
+import { useStyles } from '../../../ConstraintAccordion.styles';
+import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
+import { IConstraint } from '../../../../../../interfaces/strategy';
+import { useLocationSettings } from '../../../../../../hooks/useLocationSettings';
+
+const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
+ [theme.breakpoints.down(710)]: {
+ margin: theme.spacing(1, 0),
+ },
+}));
+
+interface ConstraintSingleValueProps {
+ constraint: IConstraint;
+}
+
+export const ContraintAccordionViewHeaderSingleValue = ({
+ constraint,
+}: ConstraintSingleValueProps) => {
+ const { locationSettings } = useLocationSettings();
+ const { classes: styles } = useStyles();
+
+ return (
+
+
+
+ {' '}
+
+
+ }
+ />
+
+
+ );
+};
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/StyledIconWrapper/StyledIconWrapper.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/StyledIconWrapper/StyledIconWrapper.tsx
new file mode 100644
index 0000000000..dd6568c427
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/StyledIconWrapper/StyledIconWrapper.tsx
@@ -0,0 +1,16 @@
+import { styled } from '@mui/material';
+
+export const StyledIconWrapper = styled('div')<{
+ marginright?: string;
+}>(({ theme, marginright }) => ({
+ backgroundColor: theme.palette.grey[200],
+ width: 28,
+ height: 48,
+ display: 'inline-flex',
+ justifyContent: 'center',
+ padding: '10px 0',
+ color: theme.palette.primary.main,
+ marginRight: marginright ? marginright : '1rem',
+ marginTop: 'auto',
+ marginBottom: 'auto',
+}));
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.tsx
index 9cd3a5cf61..b21c6159a1 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.tsx
@@ -1,6 +1,7 @@
import { IConstraint } from 'interfaces/strategy';
import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription';
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles';
+import React from 'react';
interface IConstraintOperatorProps {
constraint: IConstraint;
@@ -14,15 +15,8 @@ export const ConstraintOperator = ({
const operatorName = constraint.operator;
const operatorText = formatOperatorDescription(constraint.operator);
- const notLabel = constraint.inverted && (
-
- NOT
-
- );
-
return (
- {notLabel}
{operatorName}
{operatorText}
diff --git a/frontend/src/component/common/ConstraintAccordion/helpers.ts b/frontend/src/component/common/ConstraintAccordion/helpers.ts
new file mode 100644
index 0000000000..aa7da24837
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/helpers.ts
@@ -0,0 +1,13 @@
+export function getTextWidth(text: string | null) {
+ if (text != null) {
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+
+ if (context != null) {
+ context.font = getComputedStyle(document.body).font;
+
+ return context.measureText(text).width;
+ }
+ }
+ return 0;
+}
diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx
index a066c7219d..c364bfecb4 100644
--- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx
+++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx
@@ -6,7 +6,6 @@ import {
Typography,
useTheme,
} from '@mui/material';
-import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { GuidanceIndicator } from 'component/common/GuidanceIndicator/GuidanceIndicator';
diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultsTable.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultsTable.tsx
index 5f7137c5fb..2e695ce979 100644
--- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultsTable.tsx
+++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultsTable.tsx
@@ -22,7 +22,6 @@ import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell';
import { PlaygroundFeatureSchema } from 'hooks/api/actions/usePlayground/playground.model';
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
import useLoading from 'hooks/useLoading';
-import { GuidanceIndicator } from 'component/common/GuidanceIndicator/GuidanceIndicator';
import { VariantCell } from './VariantCell/VariantCell';
const defaultSort: SortingRule = { id: 'name' };
diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
index fd75a7224a..6fc76c2828 100644
--- a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
+++ b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
@@ -32,7 +32,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { IUser } from 'interfaces/user';
import { IGroup } from 'interfaces/group';
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
-import { mapGroupUsers } from '../../../../hooks/api/getters/useGroup/useGroup';
const StyledAvatar = styled(Avatar)(({ theme }) => ({
width: theme.spacing(4),
diff --git a/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts b/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts
index 9d67f266eb..445d74201f 100644
--- a/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts
+++ b/frontend/src/hooks/api/getters/useProjectAccess/useProjectAccess.ts
@@ -1,11 +1,10 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
-import { useState, useEffect, useMemo } from 'react';
+import { useState, useEffect } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import { IProjectRole } from 'interfaces/role';
import { IGroup } from 'interfaces/group';
import { IUser } from 'interfaces/user';
-import { useGroups } from '../useGroups/useGroups';
import { mapGroupUsers } from '../useGroup/useGroup';
export enum ENTITY_TYPE {
diff --git a/frontend/src/themes/theme.ts b/frontend/src/themes/theme.ts
index 0e9b54d8ba..8f23875c5e 100644
--- a/frontend/src/themes/theme.ts
+++ b/frontend/src/themes/theme.ts
@@ -95,6 +95,8 @@ export default createTheme({
light: colors.grey[200],
main: colors.grey[400],
dark: colors.grey[600],
+ background: 'white',
+ contrast: colors.grey[300],
},
divider: colors.grey[300],
dividerAlternative: colors.grey[400],
diff --git a/frontend/src/themes/themeTypes.ts b/frontend/src/themes/themeTypes.ts
index cb59be8a6e..9e61141b28 100644
--- a/frontend/src/themes/themeTypes.ts
+++ b/frontend/src/themes/themeTypes.ts
@@ -88,6 +88,8 @@ declare module '@mui/material/styles' {
main: string;
light: string;
dark: string;
+ background: string;
+ contrast: string;
};
}