From c06c51362632593233ceffb48f874c690eb92393 Mon Sep 17 00:00:00 2001
From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com>
Date: Tue, 2 Jul 2024 14:52:00 +0200
Subject: [PATCH 1/4] feat(format): add a poc for a routine builder
---
.../fields/sourceValue/SourceValueRoutine.js | 156 +++++++++++++++---
1 file changed, 130 insertions(+), 26 deletions(-)
diff --git a/src/app/js/fields/sourceValue/SourceValueRoutine.js b/src/app/js/fields/sourceValue/SourceValueRoutine.js
index 4dfa3b1f4..453e0c6d3 100644
--- a/src/app/js/fields/sourceValue/SourceValueRoutine.js
+++ b/src/app/js/fields/sourceValue/SourceValueRoutine.js
@@ -1,19 +1,60 @@
import React from 'react';
-import compose from 'recompose/compose';
+import { compose } from 'recompose';
import ListAltIcon from '@mui/icons-material/ListAlt';
import PropTypes from 'prop-types';
import RoutineCatalog from '../wizard/RoutineCatalog';
import translate from 'redux-polyglot/translate';
import { polyglot as polyglotPropTypes } from '../../propTypes';
import { Box, Button, TextField } from '@mui/material';
+import { fromFields } from '../../sharedSelectors';
+import { loadField } from '../index';
+import { connect } from 'react-redux';
+import { getFieldForSpecificScope } from '../../../../common/scope';
+import SearchAutocomplete from '../../admin/Search/SearchAutocomplete';
const SourceValueRoutine = ({
+ fields,
updateDefaultValueTransformers,
value,
p: polyglot,
}) => {
const [openRoutineCatalog, setOpenRoutineCatalog] = React.useState(false);
const [valueInput, setValueInput] = React.useState(value || '');
+ const [routine, setRoutine] = React.useState('');
+ const [routineArgs, setRoutineArgs] = React.useState([]);
+ const [routineFields, setRoutineFields] = React.useState([]);
+ const [first, setFirst] = React.useState(true);
+
+ const fieldsResource = React.useMemo(
+ () => getFieldForSpecificScope(fields, 'collection'),
+ [fields],
+ );
+
+ React.useEffect(() => {
+ if (typeof value === 'string') {
+ setRoutine(value.split('/').slice(0, 4).join('/'));
+ const args = value.split('/').slice(4);
+ setRoutineArgs(args);
+ setRoutineFields(
+ fieldsResource.filter((field) => {
+ return args.includes(field.name);
+ }),
+ );
+ }
+ }, [value]);
+
+ React.useEffect(() => {
+ if (!first) {
+ handleChange({
+ target: {
+ value: [routine, ...routineArgs].join('/'),
+ },
+ });
+ } else {
+ setFirst(false);
+ }
+ }, [routine, routineArgs]);
+
const handleChange = (event) => {
setValueInput(event.target.value);
const transformers = [
@@ -32,40 +73,103 @@ const SourceValueRoutine = ({
updateDefaultValueTransformers(transformers);
};
+ const handleRoutineFieldsChange = (event, newValue) => {
+ setRoutineFields(newValue);
+ setRoutineArgs(newValue.map((field) => field.name));
+ };
+
return (
-
-
-
-
+
+
+
+
+
+
+
+
+ setOpenRoutineCatalog(false)}
+ onChange={handleChange}
+ currentValue={value}
+ />
+
+
+
+
+
+
+
+
- setOpenRoutineCatalog(false)}
- onChange={handleChange}
- currentValue={value}
- />
);
};
+const mapStateToProps = (state) => {
+ return {
+ // sort by label asc
+ fields: fromFields
+ .getFields(state)
+ .sort((a, b) => a.label.localeCompare(b.label)),
+ };
+};
+
+const mapDispatchToProps = {
+ loadField,
+};
+
SourceValueRoutine.propTypes = {
+ fields: PropTypes.arrayOf(PropTypes.object).isRequired,
p: polyglotPropTypes.isRequired,
updateDefaultValueTransformers: PropTypes.func.isRequired,
value: PropTypes.string,
};
-export default compose(translate)(SourceValueRoutine);
+export default compose(
+ translate,
+ connect(mapStateToProps, mapDispatchToProps),
+)(SourceValueRoutine);
From 609953fd368067b9a7fb97426c7758fba581b1cd Mon Sep 17 00:00:00 2001
From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com>
Date: Wed, 3 Jul 2024 10:23:58 +0200
Subject: [PATCH 2/4] feat(format): add a routine selector, remove last field
---
src/app/custom/translations.tsv | 1 +
src/app/js/fields/FieldRepresentation.js | 2 +-
.../fields/sourceValue/SourceValueRoutine.js | 67 +++----
.../wizard/RoutineCatalogAutocomplete.js | 175 ++++++++++++++++++
4 files changed, 200 insertions(+), 45 deletions(-)
create mode 100644 src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv
index 4c3f8bfee..4c8983141 100644
--- a/src/app/custom/translations.tsv
+++ b/src/app/custom/translations.tsv
@@ -1098,3 +1098,4 @@
"ejs_variable_list" "Data from a routine is displayed using an HTML template based on EJS syntax. You can use these variables to access data and utils:" "L’affichage des données en provenance d’une routine est réalisé à l’aide d’un template HTML utilisant la syntaxe EJS. Vous pouvez utiliser ces variables pour accéder aux données et aux utilitaires :"
"ejs_data" "Variable containing the routine data" "Variable contenant les données de la routine"
"ejs_lodash" "Variable containing the Lodash function" "Variable contenant les fonctions de Lodash"
+"routine_args" "Routine fields" "Champs de la routine"
diff --git a/src/app/js/fields/FieldRepresentation.js b/src/app/js/fields/FieldRepresentation.js
index 2a2973180..eb25f602e 100644
--- a/src/app/js/fields/FieldRepresentation.js
+++ b/src/app/js/fields/FieldRepresentation.js
@@ -67,7 +67,7 @@ function FieldRepresentation({ field, shortMode = false, p: polyglot }) {
}
FieldRepresentation.propTypes = {
- field: PropTypes.isRequired,
+ field: PropTypes.object.isRequired,
shortMode: PropTypes.bool,
p: polyglotPropTypes.isRequired,
};
diff --git a/src/app/js/fields/sourceValue/SourceValueRoutine.js b/src/app/js/fields/sourceValue/SourceValueRoutine.js
index 453e0c6d3..eea0bcb3d 100644
--- a/src/app/js/fields/sourceValue/SourceValueRoutine.js
+++ b/src/app/js/fields/sourceValue/SourceValueRoutine.js
@@ -11,6 +11,7 @@ import { loadField } from '../index';
import { connect } from 'react-redux';
import { getFieldForSpecificScope } from '../../../../common/scope';
import SearchAutocomplete from '../../admin/Search/SearchAutocomplete';
+import RoutineCatalogAutocomplete from '../wizard/RoutineCatalogAutocomplete';
const SourceValueRoutine = ({
fields,
@@ -19,7 +20,6 @@ const SourceValueRoutine = ({
p: polyglot,
}) => {
const [openRoutineCatalog, setOpenRoutineCatalog] = React.useState(false);
- const [valueInput, setValueInput] = React.useState(value || '');
const [routine, setRoutine] = React.useState('');
const [routineArgs, setRoutineArgs] = React.useState([]);
const [routineFields, setRoutineFields] = React.useState([]);
@@ -45,32 +45,29 @@ const SourceValueRoutine = ({
React.useEffect(() => {
if (!first) {
- handleChange({
- target: {
- value: [routine, ...routineArgs].join('/'),
+ const finalRoutine = [routine, ...routineArgs].join('/');
+ const transformers = [
+ {
+ operation: 'ROUTINE',
+ args: [
+ {
+ name: 'value',
+ type: 'string',
+ value: finalRoutine,
+ },
+ ],
},
- });
+ ];
+ updateDefaultValueTransformers(transformers);
} else {
setFirst(false);
}
}, [routine, routineArgs]);
- const handleChange = (event) => {
- setValueInput(event.target.value);
- const transformers = [
- {
- operation: 'ROUTINE',
- args: [
- {
- name: 'value',
- type: 'string',
- value: event.target.value,
- },
- ],
- },
- ];
-
- updateDefaultValueTransformers(transformers);
+ const handleRoutineChange = (event) => {
+ setRoutine(event.target.value);
+ setRoutineFields([]);
+ setRoutineArgs([]);
};
const handleRoutineFieldsChange = (event, newValue) => {
@@ -86,13 +83,10 @@ const SourceValueRoutine = ({
display="flex"
alignItems="center"
>
-
@@ -109,7 +103,7 @@ const SourceValueRoutine = ({
setOpenRoutineCatalog(false)}
- onChange={handleChange}
+ onChange={handleRoutineChange}
currentValue={value}
/>
@@ -122,7 +116,7 @@ const SourceValueRoutine = ({
>
-
-
-
-
);
};
diff --git a/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js b/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
new file mode 100644
index 000000000..048f09508
--- /dev/null
+++ b/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
@@ -0,0 +1,175 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import PropTypes from 'prop-types';
+import translate from 'redux-polyglot/translate';
+import compose from 'recompose/compose';
+import { polyglot as polyglotPropTypes } from '../../propTypes';
+
+import { Typography, Box, Link, Tooltip } from '@mui/material';
+
+import TextField from '@mui/material/TextField';
+import Autocomplete from '@mui/material/Autocomplete';
+import { styled, lighten, darken } from '@mui/system';
+
+import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
+import ThumbUpIcon from '@mui/icons-material/ThumbUp';
+
+import routines from '../../../custom/routines/routines-catalog.json';
+import routinesPrecomputed from '../../../custom/routines/routines-precomputed-catalog.json';
+
+const GroupHeader = styled('div')(({ theme }) => ({
+ position: 'sticky',
+ top: '-8px',
+ padding: '4px 10px',
+ color: theme.palette.primary.main,
+ backgroundColor:
+ theme.palette.mode === 'light'
+ ? lighten(theme.palette.primary.light, 0.85)
+ : darken(theme.palette.primary.main, 0.8),
+}));
+
+const GroupItems = styled('ul')({
+ padding: 0,
+});
+
+const RoutineOption = ({ key, option, polyglot, ...props }) => {
+ return (
+
+
+
+
+ {option.title}
+
+
+
+
+
+ {polyglot.t(`${option.id}_description`)}
+
+
+
+
+ {option.recommendedWith && (
+
+
+
+
+ {option.recommendedWith.toString()}
+
+
+
+ )}
+ {option.doc && (
+
+ e.stopPropagation()}
+ >
+
+
+
+ )}
+
+
+
+ );
+};
+
+RoutineOption.propTypes = {
+ key: PropTypes.string.isRequired,
+ option: PropTypes.shape({
+ title: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
+ doc: PropTypes.string,
+ url: PropTypes.string,
+ recommendedWith: PropTypes.array,
+ }).isRequired,
+ polyglot: polyglotPropTypes.isRequired,
+};
+
+const RoutineCatalog = ({
+ p: polyglot,
+ label,
+ onChange,
+ currentValue,
+ precomputed = false,
+}) => {
+ const [value, setValue] = useState(null);
+
+ /**
+ * @type {Array<{id: string, title: string, url: string, doc: string, recommendedWith: string[]}>}
+ */
+ const catalog = useMemo(() => {
+ let routineCatalog = precomputed ? routinesPrecomputed : routines;
+ const formatedRoutineCatalog = routineCatalog.map((routine) => {
+ const title = polyglot.t(`${routine.id}_title`);
+ const firstLetter = title[0].toUpperCase();
+ const formatedFirstLetter = /[0-9]/.test(firstLetter)
+ ? '0-9'
+ : firstLetter;
+ return {
+ ...routine,
+ title: polyglot.t(`${routine.id}_title`),
+ firstLetter: formatedFirstLetter,
+ };
+ });
+ return formatedRoutineCatalog.sort(
+ (a, b) => -b.firstLetter.localeCompare(a.firstLetter),
+ );
+ }, [precomputed]);
+
+ useEffect(() => {
+ setValue(catalog.find((routine) => routine.url.includes(currentValue)));
+ }, [currentValue]);
+
+ const handleChange = (event, newValue) => {
+ setValue(newValue);
+ onChange({
+ target: {
+ value: newValue.url,
+ },
+ });
+ };
+
+ return (
+ option.firstLetter}
+ getOptionLabel={(option) => option.title}
+ renderOption={(props, option) => (
+
+ )}
+ renderInput={(params) => }
+ renderGroup={(params) => (
+
+ {params.group}
+ {params.children}
+
+ )}
+ />
+ );
+};
+
+RoutineCatalog.propTypes = {
+ label: PropTypes.string.isRequired,
+ p: polyglotPropTypes.isRequired,
+ onChange: PropTypes.func.isRequired,
+ currentValue: PropTypes.string,
+ precomputed: PropTypes.bool,
+};
+
+export default compose(translate)(RoutineCatalog);
From 0b7b6b4c5a3c619d57335fa7da4e8f48bcfc5405 Mon Sep 17 00:00:00 2001
From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com>
Date: Wed, 3 Jul 2024 14:11:42 +0200
Subject: [PATCH 3/4] feat(format): add routine selector in precomputed data
---
.../sourceValue/SourceValuePrecomputed.js | 13 +++++++-----
.../fields/sourceValue/SourceValueRoutine.js | 4 ++--
.../wizard/RoutineCatalogAutocomplete.js | 21 +++++++++++++++----
3 files changed, 27 insertions(+), 11 deletions(-)
diff --git a/src/app/js/fields/sourceValue/SourceValuePrecomputed.js b/src/app/js/fields/sourceValue/SourceValuePrecomputed.js
index bf93b53ec..fcb95a18f 100644
--- a/src/app/js/fields/sourceValue/SourceValuePrecomputed.js
+++ b/src/app/js/fields/sourceValue/SourceValuePrecomputed.js
@@ -9,6 +9,7 @@ import { fromPrecomputed } from '../../admin/selectors';
import { polyglot as polyglotPropTypes } from '../../propTypes';
import { Autocomplete, Box, Button, TextField } from '@mui/material';
import { toast } from 'react-toastify';
+import RoutineCatalogAutocomplete from '../wizard/RoutineCatalogAutocomplete';
const SourceValuePrecomputed = ({
precomputedData,
@@ -97,14 +98,15 @@ const SourceValuePrecomputed = ({
)}
onChange={handleChangePrecomputed}
/>
+
-
+
+
setOpenRoutineCatalog(false)}
diff --git a/src/app/js/fields/sourceValue/SourceValueRoutine.js b/src/app/js/fields/sourceValue/SourceValueRoutine.js
index eea0bcb3d..cbb3f8fec 100644
--- a/src/app/js/fields/sourceValue/SourceValueRoutine.js
+++ b/src/app/js/fields/sourceValue/SourceValueRoutine.js
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import RoutineCatalog from '../wizard/RoutineCatalog';
import translate from 'redux-polyglot/translate';
import { polyglot as polyglotPropTypes } from '../../propTypes';
-import { Box, Button, TextField } from '@mui/material';
+import { Box, Button } from '@mui/material';
import { fromFields } from '../../sharedSelectors';
import { loadField } from '../index';
import { connect } from 'react-redux';
@@ -86,7 +86,7 @@ const SourceValueRoutine = ({
diff --git a/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js b/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
index 048f09508..6cf6dc8f5 100644
--- a/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
+++ b/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
@@ -63,7 +63,7 @@ const RoutineOption = ({ key, option, polyglot, ...props }) => {
- {option.recommendedWith.toString()}
+ {option.recommendedWith.join(', ')}
@@ -130,8 +130,15 @@ const RoutineCatalog = ({
}, [precomputed]);
useEffect(() => {
- setValue(catalog.find((routine) => routine.url.includes(currentValue)));
- }, [currentValue]);
+ setValue(
+ catalog.find(
+ (routine) =>
+ typeof currentValue === 'string' &&
+ currentValue.startsWith('/') &&
+ routine.url.includes(currentValue),
+ ),
+ );
+ }, [currentValue, catalog]);
const handleChange = (event, newValue) => {
setValue(newValue);
@@ -144,8 +151,14 @@ const RoutineCatalog = ({
return (
{
+ if (!option1 || !option2) {
+ return false;
+ }
+ return option1.id === option2.id && option1.url === option2.url;
+ }}
fullWidth
options={catalog}
groupBy={(option) => option.firstLetter}
From 5541ec57239c5bf3bcc12bd77926cae3b5e1f221 Mon Sep 17 00:00:00 2001
From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com>
Date: Thu, 4 Jul 2024 07:21:00 +0200
Subject: [PATCH 4/4] fix(format): apply minor fix
---
.../js/fields/wizard/RoutineCatalogAutocomplete.js | 12 +++++++-----
src/app/js/propTypes.js | 1 +
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js b/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
index 6cf6dc8f5..c5dc87afa 100644
--- a/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
+++ b/src/app/js/fields/wizard/RoutineCatalogAutocomplete.js
@@ -124,9 +124,11 @@ const RoutineCatalog = ({
firstLetter: formatedFirstLetter,
};
});
- return formatedRoutineCatalog.sort(
- (a, b) => -b.firstLetter.localeCompare(a.firstLetter),
- );
+ const sorter = new Intl.Collator(polyglot.currentLocale, {
+ numeric: true,
+ ignorePunctuation: true,
+ });
+ return formatedRoutineCatalog.sort(sorter.compare);
}, [precomputed]);
useEffect(() => {
@@ -136,7 +138,7 @@ const RoutineCatalog = ({
typeof currentValue === 'string' &&
currentValue.startsWith('/') &&
routine.url.includes(currentValue),
- ),
+ ) ?? null,
);
}, [currentValue, catalog]);
@@ -151,7 +153,7 @@ const RoutineCatalog = ({
return (
{
if (!option1 || !option2) {
diff --git a/src/app/js/propTypes.js b/src/app/js/propTypes.js
index 2d0c4b489..4cde985ba 100644
--- a/src/app/js/propTypes.js
+++ b/src/app/js/propTypes.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { PROPOSED, VALIDATED, REJECTED } from '../../common/propositionStatus';
export const polyglot = PropTypes.shape({
+ currentLocale: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
});