diff --git a/app/src/components/App.js b/app/src/components/App.js
index aa03cff..3e19803 100644
--- a/app/src/components/App.js
+++ b/app/src/components/App.js
@@ -1,7 +1,7 @@
-import React, {useState} from 'react';
+import React from 'react';
import TermInput from './TermInput.js';
import DiseaseGraph from './DiseaseGraph.js';
-import {useAsync} from '../util.js';
+import {useAsync, useQuery} from '../util.js';
import {
diseasesBySymptoms,
exploreSymptoms
@@ -9,22 +9,35 @@ import {
const App = () =>
{
- const [query, setQuery] = useState([]);
+ const {
+ query,
+ addTerm: addQueryTerm,
+ removeTerm: removeQueryTerm
+ } = useQuery();
+ // Récupération des résultats de la recherche
const diseases = useAsync([], diseasesBySymptoms, query);
const terms = useAsync([], exploreSymptoms, diseases);
+ // Tous les termes compatibles avec la recherche qui n’apparaissent pas
+ // dans la recherche
+ const availableTerms = terms.filter(({id}) =>
+ !query.some(({id: termId}) => termId === id)
+ );
+
return (
);
diff --git a/app/src/components/DiseaseGraph.js b/app/src/components/DiseaseGraph.js
index 1427c18..2978bb4 100644
--- a/app/src/components/DiseaseGraph.js
+++ b/app/src/components/DiseaseGraph.js
@@ -10,10 +10,15 @@ import {symptomsSubgraph} from '../data/mock';
*
* @prop terms Termes à afficher.
* @prop query Ensemble de termes recherchés par l’utilisateur.
- * @prop setQuery Fonction de rappel pour ajouter de nouveaux termes de
- * recherche.
+ * @prop addQueryTerm Fonction de rappel pour ajouter un terme à la requête.
+ * @prop removeQueryTerm Fonction de rappel pour ôter un terme de la requête.
*/
-const DiseaseGraph = ({terms, query, setQuery}) =>
+const DiseaseGraph = ({
+ terms,
+ query,
+ addQueryTerm,
+ removeQueryTerm,
+}) =>
{
const {nodes, edges} = useAsync({
nodes: {},
@@ -40,7 +45,11 @@ const DiseaseGraph = ({terms, query, setQuery}) =>
].join(' ')}
title={
`Cliquez pour plus d’informations sur « ${term.name} »\n`
- + '(Ctrl-clic pour l’ajouter à la requête)'
+ + `(Ctrl-clic pour ${
+ isInQuery
+ ? 'l’enlever de'
+ : 'l’ajouter à'
+ } la requête)`
}
>
{nodes[id].name}
@@ -67,15 +76,12 @@ const DiseaseGraph = ({terms, query, setQuery}) =>
else if (queryIndex >= 0)
{
// Ctrl-clic : Retrait d’un terme déjà dans la requête
- setQuery([
- ...query.slice(0, queryIndex),
- ...query.slice(queryIndex + 1)
- ]);
+ removeQueryTerm(queryIndex);
}
else
{
// Ctrl-clic : Ajout d’un nouveau terme dans la requête
- setQuery(query.concat([term]));
+ addQueryTerm(term);
}
};
@@ -93,7 +99,8 @@ const DiseaseGraph = ({terms, query, setQuery}) =>
DiseaseGraph.propTypes = {
terms: PropTypes.arrayOf(Term).isRequired,
query: PropTypes.arrayOf(Term).isRequired,
- setQuery: PropTypes.func.isRequired,
+ addQueryTerm: PropTypes.func.isRequired,
+ removeQueryTerm: PropTypes.func.isRequired,
};
export default DiseaseGraph;
diff --git a/app/src/components/TermInput.js b/app/src/components/TermInput.js
index 35eb5f2..10a5f7a 100644
--- a/app/src/components/TermInput.js
+++ b/app/src/components/TermInput.js
@@ -20,10 +20,16 @@ const keys = Object.assign(Object.create(null), {
* Zone de saisie des termes de recherche.
*
* @prop query Ensemble de termes déjà dans la requête.
- * @prop setQuery Fonction de rappel utilisée pour modifier la requête.
- * @param availableTerms Termes pouvant être ajoutés par l’utilisateur.
+ * @prop addQueryTerm Fonction de rappel pour ajouter un terme à la requête.
+ * @prop removeQueryTerm Fonction de rappel pour ôter un terme de la requête.
+ * @prop availableTerms Termes pouvant être ajoutés par l’utilisateur.
*/
-const TermInput = ({query, setQuery, availableTerms}) =>
+const TermInput = ({
+ query,
+ addQueryTerm,
+ removeQueryTerm,
+ availableTerms,
+}) =>
{
// Valeur actuellement saisie dans le champ de recherche de termes
const [value, setValue] = useState('');
@@ -40,19 +46,13 @@ const TermInput = ({query, setQuery, availableTerms}) =>
const inputRef = useRef(null);
/**
- * Ajoute un terme à la requête.
+ * Valide l’entrée courante en l’ajoutant comme terme de la requête.
*
- * @param term Terme à ajouter.
+ * @param id Identifiant du terme à ajouter dans la requête.
*/
- const addQueryTerm = term =>
+ const finalizeInput = id =>
{
- if (query.some(({id}) => id === term.id))
- {
- // N’ajoute pas les termes déjà sélectionnés
- return;
- }
-
- setQuery(query.concat([term]));
+ addQueryTerm(id);
setValue('');
setSuggestions([]);
@@ -62,19 +62,6 @@ const TermInput = ({query, setQuery, availableTerms}) =>
}
};
- /**
- * Supprime un terme de la requête.
- *
- * @param index Indice du terme à supprimer dans la liste.
- */
- const removeQueryTerm = index =>
- {
- setQuery([
- ...query.slice(0, index),
- ...query.slice(index + 1)
- ]);
- };
-
/**
* Gère l’appui sur une touche dans le champ de saisie.
*
@@ -89,19 +76,15 @@ const TermInput = ({query, setQuery, availableTerms}) =>
if (focusedSuggestion < suggestions.length)
{
- addQueryTerm(suggestions[focusedSuggestion]);
+ finalizeInput(suggestions[focusedSuggestion]);
}
}
- else if (
- ev.keyCode === keys.backspace
- && !value
- && query.length !== 0
- )
+ else if (ev.keyCode === keys.backspace && !value)
{
// Touche Retour alors que le champ de saisie est vide :
// retrait du dernier terme de la requête
ev.preventDefault();
- setQuery(query.slice(0, -1));
+ removeQueryTerm(query.length - 1);
}
else if (ev.keyCode === keys.up)
{
@@ -134,17 +117,8 @@ const TermInput = ({query, setQuery, availableTerms}) =>
}
else
{
- setSuggestions(fuzzy.filterTerms(
- // Filtre les termes correspondant à la saisie
- nextValue,
-
- // Sélectionne les termes qui n’ont pas déjà été choisis
- availableTerms.filter(({id: suggestionId}) =>
- !query.some(({id: termId}) =>
- termId === suggestionId
- )
- )
- ));
+ // Filtre les termes correspondant à la saisie
+ setSuggestions(fuzzy.filterTerms(nextValue, availableTerms));
}
};
@@ -180,7 +154,7 @@ const TermInput = ({query, setQuery, availableTerms}) =>
: ''
].join(' ')}
onMouseEnter={setFocusedSuggestion.bind(null, index)}
- onClick={addQueryTerm.bind(null, suggestion)}
+ onClick={finalizeInput.bind(null, suggestion)}
title="Cliquez pour ajouter ce terme à la recherche"
>
{suggestion.name}
@@ -206,7 +180,8 @@ const TermInput = ({query, setQuery, availableTerms}) =>
TermInput.propTypes = {
query: PropTypes.arrayOf(Term).isRequired,
- setQuery: PropTypes.func.isRequired,
+ addQueryTerm: PropTypes.func.isRequired,
+ removeQueryTerm: PropTypes.func.isRequired,
availableTerms: PropTypes.arrayOf(Term).isRequired,
};
diff --git a/app/src/util.js b/app/src/util.js
index 9c831ad..1fb875e 100644
--- a/app/src/util.js
+++ b/app/src/util.js
@@ -32,9 +32,6 @@ export const useAsync = (initial, func, ...args) =>
*
* — si la liste n’est pas vide, focus ∈ [0, taille de la liste].
* — sinon, focus = 0.
- *
- * @return Objet contenant la liste, l’indice de l’élément ayant le focus ainsi
- * que des fonctions de modification.
*/
export const useFocusableList = () =>
{
@@ -42,7 +39,14 @@ export const useFocusableList = () =>
const [focus, setFocus] = useState(0);
return {
+ /**
+ * Contenu courant de la liste.
+ */
list,
+
+ /**
+ * Indice de l’élément ayant présentement le focus.
+ */
focus,
/**
@@ -88,3 +92,47 @@ export const useFocusableList = () =>
}
};
};
+
+/**
+ * Crée une liste de termes de recherche.
+ */
+export const useQuery = () =>
+{
+ const [query, setQuery] = useState([]);
+
+ return {
+ /**
+ * Liste des termes de la requête.
+ */
+ query,
+
+ /**
+ * Ajoute un terme à la requête.
+ *
+ * @param term Terme à ajouter.
+ */
+ addTerm(term)
+ {
+ if (query.some(({id}) => id === term.id))
+ {
+ // N’ajoute pas les termes déjà sélectionnés
+ return;
+ }
+
+ setQuery(query.concat([term]));
+ },
+
+ /**
+ * Supprime un terme de la requête.
+ *
+ * @param index Indice du terme à supprimer dans la liste.
+ */
+ removeTerm(index)
+ {
+ setQuery([
+ ...query.slice(0, index),
+ ...query.slice(index + 1)
+ ]);
+ },
+ };
+};