diff --git a/src/modules/RA/Scene/assets/List.svg b/src/modules/RA/Scene/assets/List.svg new file mode 100644 index 000000000..7b0fe2133 --- /dev/null +++ b/src/modules/RA/Scene/assets/List.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/RA/Scene/components/GeolayerSelect.js b/src/modules/RA/Scene/components/GeolayerSelect.js index ff09e71f1..2cd9f912d 100644 --- a/src/modules/RA/Scene/components/GeolayerSelect.js +++ b/src/modules/RA/Scene/components/GeolayerSelect.js @@ -5,6 +5,7 @@ import { LinearProgress, withDataProvider, GET_LIST } from 'react-admin'; import debounce from 'lodash.debounce'; import uniqBy from 'lodash.uniqby'; +import Box from '@material-ui/core/Box'; import FormControl from '@material-ui/core/FormControl'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; @@ -120,7 +121,11 @@ const GeolayerSelect = ({ dataProvider, onChange, excludeIds = [], includeIds = * Display progress bar until we have geolayers */ if (!geolayers) { - return ; + return ( + + + + ); } if (!geolayers.length) { diff --git a/src/modules/RA/Scene/components/TreeInput.js b/src/modules/RA/Scene/components/TreeInput.js index ed1c98fb1..e08e800a0 100644 --- a/src/modules/RA/Scene/components/TreeInput.js +++ b/src/modules/RA/Scene/components/TreeInput.js @@ -35,6 +35,7 @@ const generateNodeProps = (treeData, setTreeData, includeIds) => className: classnames({ treeGroup: node.group, treeGroupExclusive: node.exclusive, + treeGroupByVariable: node.byVariable, }), buttons: [], }; @@ -91,7 +92,7 @@ const TreeInput = ({ input: { value, onChange }, ...props }) => { */ const addGroup = () => onChange([ ...value, - { label: 'New group name', group: true }, + { label: 'New group name', group: true, children: [], expanded: true }, ]); if (!value) { diff --git a/src/modules/RA/Scene/components/TreeInput.scss b/src/modules/RA/Scene/components/TreeInput.scss index 81952e100..c664e18cc 100644 --- a/src/modules/RA/Scene/components/TreeInput.scss +++ b/src/modules/RA/Scene/components/TreeInput.scss @@ -31,4 +31,10 @@ background-image: url(../assets/RadioButton.svg); } } + + &ByVariable { + .rst__moveHandle { + background-image: url(../assets/List.svg); + } + } } diff --git a/src/modules/RA/Scene/components/TreeNodeLabelInput.js b/src/modules/RA/Scene/components/TreeNodeLabelInput.js index 6b1cb5e0a..6ecadd7b2 100644 --- a/src/modules/RA/Scene/components/TreeNodeLabelInput.js +++ b/src/modules/RA/Scene/components/TreeNodeLabelInput.js @@ -1,6 +1,7 @@ import React from 'react'; -import { changeNodeAtPath } from 'react-sortable-tree'; +import { changeNodeAtPath, getNodeAtPath } from 'react-sortable-tree'; +import Box from '@material-ui/core/Box'; import TextField from '@material-ui/core/TextField'; @@ -16,9 +17,31 @@ const NodeLabel = ({ treeData, setTreeData, path, node }) => { newNode: { ...node, label }, })); + + const parentVariables = React.useMemo( + () => getNodeAtPath({ + treeData, + path: path.slice(0, -1), + getNodeKey: ({ treeIndex }) => treeIndex, + })?.node?.variables, + [path, treeData], + ); + /* Only groups have editable labels */ if (!node.group) { - return node.label; + return ( + <> + {node.label} + + {Boolean(parentVariables?.length) && ( + + {parentVariables.map(({ id, label }) => + // eslint-disable-next-line no-irregular-whitespace + `${label} : ${node.variables?.[id]}`).join(', ')} + + )} + + ); } return ; diff --git a/src/modules/RA/Scene/components/TreeNodeToolbar.js b/src/modules/RA/Scene/components/TreeNodeToolbar.js index ff8ada1c2..7a4b94b53 100644 --- a/src/modules/RA/Scene/components/TreeNodeToolbar.js +++ b/src/modules/RA/Scene/components/TreeNodeToolbar.js @@ -4,11 +4,17 @@ import { addNodeUnderParent, removeNodeAtPath, changeNodeAtPath, + getNodeAtPath, getFlatDataFromTree, } from 'react-sortable-tree'; +import { v4 as uuid } from 'uuid'; + +import Box from '@material-ui/core/Box'; import Button from '@material-ui/core/Button'; +import Chip from '@material-ui/core/Chip'; import AddIcon from '@material-ui/icons/Add'; +import EditIcon from '@material-ui/icons/Edit'; import DeleteIcon from '@material-ui/icons/Delete'; import IconButton from '@material-ui/core/IconButton'; import FormLabel from '@material-ui/core/FormLabel'; @@ -16,7 +22,10 @@ import FormControl from '@material-ui/core/FormControl'; import Menu from '@material-ui/core/Menu'; import MenuItem from '@material-ui/core/MenuItem'; import Modal from '@material-ui/core/Modal'; -import Switch from '@material-ui/core/Switch'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Radio from '@material-ui/core/Radio'; +import RadioGroup from '@material-ui/core/RadioGroup'; +import TextField from '@material-ui/core/TextField'; import MoreVertIcon from '@material-ui/icons/MoreVert'; import GeolayerSelect from './GeolayerSelect'; @@ -54,6 +63,18 @@ const TreeNodeToolbar = ({ treeData, setTreeData, path, node, includeIds }) => { const [newLayerProps, setNewLayerProps] = React.useState({}); const [groupNewSettings, setGroupNewSettings] = React.useState({}); + React.useEffect( + () => { + if (displaySettingsModal.node) { + setGroupNewSettings({ + exclusive: Boolean(displaySettingsModal.node.exclusive), + byVariable: Boolean(displaySettingsModal.node.byVariable), + variables: displaySettingsModal.node.variables || [], + }); + } + }, + [displaySettingsModal], + ); const handleClick = ({ currentTarget }) => setAnchorEl(currentTarget); const closeMenu = () => setAnchorEl(null); @@ -91,6 +112,19 @@ const TreeNodeToolbar = ({ treeData, setTreeData, path, node, includeIds }) => { */ const editItem = newProps => { closeMenu(); + + // Remove dropped variables from children + const removedVariables = node.variables?.filter( + variable => !newProps.variables.includes(variable), + ); + + const newChildren = JSON.parse(JSON.stringify(node.children)); + newChildren?.forEach(child => { + removedVariables?.forEach(removedVariable => { + delete child[removedVariable.id]; // eslint-disable-line no-param-reassign + }); + }); + setTreeData(changeNodeAtPath({ treeData, path, @@ -98,6 +132,7 @@ const TreeNodeToolbar = ({ treeData, setTreeData, path, node, includeIds }) => { newNode: { ...node, ...newProps, + children: newChildren, }, })); }; @@ -110,14 +145,26 @@ const TreeNodeToolbar = ({ treeData, setTreeData, path, node, includeIds }) => { setDisplayLayerModal(true); }; + const openEditLayerModal = editNode => { + closeMenu(); + setNewLayerProps(editNode); + setDisplayLayerModal(editNode); + }; + /** * Close modal for new layer node creation */ - const closeNewLayerModal = (doCreate = false) => () => { - if (doCreate && newLayerProps.geolayer) { + const closeLayerModal = (save = false, edit = false) => () => { + if (save && !edit && newLayerProps.geolayer) { newSubItem(newLayerProps)(); + } else if (save && edit) { + setTreeData(changeNodeAtPath({ + treeData, + path, + getNodeKey: ({ treeIndex }) => treeIndex, + newNode: { ...node, ...newLayerProps }, + })); } - /* Close modal */ setDisplayLayerModal(false); @@ -130,7 +177,11 @@ const TreeNodeToolbar = ({ treeData, setTreeData, path, node, includeIds }) => { */ const openSettingsModal = () => { closeMenu(); - setDisplaySettingsModal(true); + setDisplaySettingsModal(getNodeAtPath({ + treeData, + path, + getNodeKey: ({ treeIndex }) => treeIndex, + })); }; /** @@ -148,9 +199,14 @@ const TreeNodeToolbar = ({ treeData, setTreeData, path, node, includeIds }) => { setDisplaySettingsModal(false); }; - const isGroupExclusive = typeof groupNewSettings.exclusive !== 'undefined' - ? groupNewSettings.exclusive - : node.exclusive; + const getRadioValue = React.useCallback( + () => { + if (groupNewSettings.byVariable) { return 'byVariable'; } + return groupNewSettings.exclusive ? 'exclusive' : 'inclusive'; + }, + [groupNewSettings.byVariable, groupNewSettings.exclusive], + ); + const radioValue = getRadioValue(); /** * Array of all nodes in treeData @@ -169,56 +225,197 @@ const TreeNodeToolbar = ({ treeData, setTreeData, path, node, includeIds }) => { new Set(), )).filter(Boolean); + const parentNode = getNodeAtPath({ + treeData, + path: path.slice(0, -1), + getNodeKey: ({ treeIndex }) => treeIndex, + })?.node; + const variables = + node.variables?.length + ? node.variables + : (parentNode?.variables || []); + + const newVariableFieldRef = React.useRef(); + const handleVariableAdd = React.useCallback( + () => { + const label = newVariableFieldRef?.current?.value?.trim(); + if (!label) { + return; + } + + setGroupNewSettings(({ variables: vars = [], ...prevSettings }) => ({ + ...prevSettings, + variables: [ + ...vars, + { id: uuid(), label }, + ], + })); + newVariableFieldRef.current.value = ''; + }, + [], + ); + return ( <> {isGroup && } {isGroup && } - {!isGroup && } + {!isGroup && ( + openEditLayerModal(node)}> + + + )} + {!isGroup && } {isGroup && Ajouter une couche} - {isGroup && Ajouter un sous-groupe} + {isGroup && !node.byVariable && ( + Ajouter un sous-groupe + )} {isGroup && Paramètres} Supprimer - +
-
- + +
+ )} + + {Boolean(displayLayerModal.geolayer) && ( + { + const label = event?.target?.value; + setNewLayerProps(prevProps => ({ ...prevProps, label })); + }} /> -
+ )} + + {(node.byVariable || Boolean(displayLayerModal.geolayer)) && ( + + {variables?.map(({ id, label }) => ( + { + const fieldValue = event?.target?.value; + setNewLayerProps( + prevProps => ({ + ...prevProps, + variables: { ...prevProps.variables, [id]: fieldValue }, + }), + ); + }} + /> + ))} + + )}
- - + +
- +
- - Mode de sélection des couches -
- Inclusif - - setGroupNewSettings({ ...groupNewSettings, exclusive })} - /> - Exclusif -
+ + Mode de sélection des couches + { + switch (choice) { + case 'inclusive': + setGroupNewSettings( + { ...groupNewSettings, exclusive: false, byVariable: false }, + ); + break; + case 'exclusive': + setGroupNewSettings( + { ...groupNewSettings, exclusive: true, byVariable: false }, + ); + break; + case 'byVariable': + setGroupNewSettings( + { ...groupNewSettings, exclusive: true, byVariable: true }, + ); + break; + default: + } + }} + > + } label="Inclusif" /> + } label="Exclusif" /> + } label="Par variables" /> + + + + + Ajouter + + ), + }} + /> + + + + {groupNewSettings.variables?.map(({ id, label }) => ( + { + setGroupNewSettings(({ variables: prevVariables = [], ...prevsettings }) => ({ + ...prevsettings, + variables: prevVariables.filter(({ id: cId }) => cId !== id), + })); + }} + /> + ))} + + +
- +