app: Généralise addQueryTerm/removeQueryTerm
This commit is contained in:
parent
650f76c028
commit
8e58ab8c91
|
@ -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 (
|
||||
<div className="App">
|
||||
<TermInput
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
availableTerms={terms}
|
||||
addQueryTerm={addQueryTerm}
|
||||
removeQueryTerm={removeQueryTerm}
|
||||
availableTerms={availableTerms}
|
||||
/>
|
||||
<DiseaseGraph
|
||||
terms={terms}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
addQueryTerm={addQueryTerm}
|
||||
removeQueryTerm={removeQueryTerm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
]);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue