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 TermInput from './TermInput.js';
|
||||||
import DiseaseGraph from './DiseaseGraph.js';
|
import DiseaseGraph from './DiseaseGraph.js';
|
||||||
import {useAsync} from '../util.js';
|
import {useAsync, useQuery} from '../util.js';
|
||||||
import {
|
import {
|
||||||
diseasesBySymptoms,
|
diseasesBySymptoms,
|
||||||
exploreSymptoms
|
exploreSymptoms
|
||||||
|
@ -9,22 +9,35 @@ import {
|
||||||
|
|
||||||
const App = () =>
|
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 diseases = useAsync([], diseasesBySymptoms, query);
|
||||||
const terms = useAsync([], exploreSymptoms, diseases);
|
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 (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<TermInput
|
<TermInput
|
||||||
query={query}
|
query={query}
|
||||||
setQuery={setQuery}
|
addQueryTerm={addQueryTerm}
|
||||||
availableTerms={terms}
|
removeQueryTerm={removeQueryTerm}
|
||||||
|
availableTerms={availableTerms}
|
||||||
/>
|
/>
|
||||||
<DiseaseGraph
|
<DiseaseGraph
|
||||||
terms={terms}
|
terms={terms}
|
||||||
query={query}
|
query={query}
|
||||||
setQuery={setQuery}
|
addQueryTerm={addQueryTerm}
|
||||||
|
removeQueryTerm={removeQueryTerm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,10 +10,15 @@ import {symptomsSubgraph} from '../data/mock';
|
||||||
*
|
*
|
||||||
* @prop terms Termes à afficher.
|
* @prop terms Termes à afficher.
|
||||||
* @prop query Ensemble de termes recherchés par l’utilisateur.
|
* @prop query Ensemble de termes recherchés par l’utilisateur.
|
||||||
* @prop setQuery Fonction de rappel pour ajouter de nouveaux termes de
|
* @prop addQueryTerm Fonction de rappel pour ajouter un terme à la requête.
|
||||||
* recherche.
|
* @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({
|
const {nodes, edges} = useAsync({
|
||||||
nodes: {},
|
nodes: {},
|
||||||
|
@ -40,7 +45,11 @@ const DiseaseGraph = ({terms, query, setQuery}) =>
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
title={
|
title={
|
||||||
`Cliquez pour plus d’informations sur « ${term.name} »\n`
|
`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}
|
{nodes[id].name}
|
||||||
|
@ -67,15 +76,12 @@ const DiseaseGraph = ({terms, query, setQuery}) =>
|
||||||
else if (queryIndex >= 0)
|
else if (queryIndex >= 0)
|
||||||
{
|
{
|
||||||
// Ctrl-clic : Retrait d’un terme déjà dans la requête
|
// Ctrl-clic : Retrait d’un terme déjà dans la requête
|
||||||
setQuery([
|
removeQueryTerm(queryIndex);
|
||||||
...query.slice(0, queryIndex),
|
|
||||||
...query.slice(queryIndex + 1)
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Ctrl-clic : Ajout d’un nouveau terme dans la requête
|
// 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 = {
|
DiseaseGraph.propTypes = {
|
||||||
terms: PropTypes.arrayOf(Term).isRequired,
|
terms: PropTypes.arrayOf(Term).isRequired,
|
||||||
query: PropTypes.arrayOf(Term).isRequired,
|
query: PropTypes.arrayOf(Term).isRequired,
|
||||||
setQuery: PropTypes.func.isRequired,
|
addQueryTerm: PropTypes.func.isRequired,
|
||||||
|
removeQueryTerm: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DiseaseGraph;
|
export default DiseaseGraph;
|
||||||
|
|
|
@ -20,10 +20,16 @@ const keys = Object.assign(Object.create(null), {
|
||||||
* Zone de saisie des termes de recherche.
|
* Zone de saisie des termes de recherche.
|
||||||
*
|
*
|
||||||
* @prop query Ensemble de termes déjà dans la requête.
|
* @prop query Ensemble de termes déjà dans la requête.
|
||||||
* @prop setQuery Fonction de rappel utilisée pour modifier la requête.
|
* @prop addQueryTerm Fonction de rappel pour ajouter un terme à la requête.
|
||||||
* @param availableTerms Termes pouvant être ajoutés par l’utilisateur.
|
* @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
|
// Valeur actuellement saisie dans le champ de recherche de termes
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
@ -40,19 +46,13 @@ const TermInput = ({query, setQuery, availableTerms}) =>
|
||||||
const inputRef = useRef(null);
|
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))
|
addQueryTerm(id);
|
||||||
{
|
|
||||||
// N’ajoute pas les termes déjà sélectionnés
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setQuery(query.concat([term]));
|
|
||||||
setValue('');
|
setValue('');
|
||||||
setSuggestions([]);
|
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.
|
* 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)
|
if (focusedSuggestion < suggestions.length)
|
||||||
{
|
{
|
||||||
addQueryTerm(suggestions[focusedSuggestion]);
|
finalizeInput(suggestions[focusedSuggestion]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (
|
else if (ev.keyCode === keys.backspace && !value)
|
||||||
ev.keyCode === keys.backspace
|
|
||||||
&& !value
|
|
||||||
&& query.length !== 0
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
// Touche Retour alors que le champ de saisie est vide :
|
// Touche Retour alors que le champ de saisie est vide :
|
||||||
// retrait du dernier terme de la requête
|
// retrait du dernier terme de la requête
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
setQuery(query.slice(0, -1));
|
removeQueryTerm(query.length - 1);
|
||||||
}
|
}
|
||||||
else if (ev.keyCode === keys.up)
|
else if (ev.keyCode === keys.up)
|
||||||
{
|
{
|
||||||
|
@ -134,17 +117,8 @@ const TermInput = ({query, setQuery, availableTerms}) =>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setSuggestions(fuzzy.filterTerms(
|
|
||||||
// Filtre les termes correspondant à la saisie
|
// Filtre les termes correspondant à la saisie
|
||||||
nextValue,
|
setSuggestions(fuzzy.filterTerms(nextValue, availableTerms));
|
||||||
|
|
||||||
// Sélectionne les termes qui n’ont pas déjà été choisis
|
|
||||||
availableTerms.filter(({id: suggestionId}) =>
|
|
||||||
!query.some(({id: termId}) =>
|
|
||||||
termId === suggestionId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,7 +154,7 @@ const TermInput = ({query, setQuery, availableTerms}) =>
|
||||||
: ''
|
: ''
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
onMouseEnter={setFocusedSuggestion.bind(null, index)}
|
onMouseEnter={setFocusedSuggestion.bind(null, index)}
|
||||||
onClick={addQueryTerm.bind(null, suggestion)}
|
onClick={finalizeInput.bind(null, suggestion)}
|
||||||
title="Cliquez pour ajouter ce terme à la recherche"
|
title="Cliquez pour ajouter ce terme à la recherche"
|
||||||
>
|
>
|
||||||
{suggestion.name}
|
{suggestion.name}
|
||||||
|
@ -206,7 +180,8 @@ const TermInput = ({query, setQuery, availableTerms}) =>
|
||||||
|
|
||||||
TermInput.propTypes = {
|
TermInput.propTypes = {
|
||||||
query: PropTypes.arrayOf(Term).isRequired,
|
query: PropTypes.arrayOf(Term).isRequired,
|
||||||
setQuery: PropTypes.func.isRequired,
|
addQueryTerm: PropTypes.func.isRequired,
|
||||||
|
removeQueryTerm: PropTypes.func.isRequired,
|
||||||
availableTerms: PropTypes.arrayOf(Term).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].
|
* — si la liste n’est pas vide, focus ∈ [0, taille de la liste].
|
||||||
* — sinon, focus = 0.
|
* — 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 = () =>
|
export const useFocusableList = () =>
|
||||||
{
|
{
|
||||||
|
@ -42,7 +39,14 @@ export const useFocusableList = () =>
|
||||||
const [focus, setFocus] = useState(0);
|
const [focus, setFocus] = useState(0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Contenu courant de la liste.
|
||||||
|
*/
|
||||||
list,
|
list,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indice de l’élément ayant présentement le focus.
|
||||||
|
*/
|
||||||
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