app: Généralise addQueryTerm/removeQueryTerm

This commit is contained in:
Mattéo Delabre 2019-12-04 21:46:57 -05:00
parent 650f76c028
commit 8e58ab8c91
Signed by: matteo
GPG Key ID: AE3FBD02DC583ABB
4 changed files with 108 additions and 65 deletions

View File

@ -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 napparaissent 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>
); );

View File

@ -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 lutilisateur. * @prop query Ensemble de termes recherchés par lutilisateur.
* @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 dinformations sur « ${term.name} »\n` `Cliquez pour plus dinformations sur « ${term.name} »\n`
+ '(Ctrl-clic pour lajouter à la requête)' + `(Ctrl-clic pour ${
isInQuery
? 'lenlever de'
: 'lajouter à'
} 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 dun terme déjà dans la requête // Ctrl-clic : Retrait dun terme déjà dans la requête
setQuery([ removeQueryTerm(queryIndex);
...query.slice(0, queryIndex),
...query.slice(queryIndex + 1)
]);
} }
else else
{ {
// Ctrl-clic : Ajout dun nouveau terme dans la requête // Ctrl-clic : Ajout dun 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;

View File

@ -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 lutilisateur. * @prop removeQueryTerm Fonction de rappel pour ôter un terme de la requête.
* @prop availableTerms Termes pouvant être ajoutés par lutilisateur.
*/ */
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 lentrée courante en lajoutant 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);
{
// Najoute 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 lappui sur une touche dans le champ de saisie. * Gère lappui 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 nont 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,
}; };

View File

@ -32,9 +32,6 @@ export const useAsync = (initial, func, ...args) =>
* *
* si la liste nest pas vide, focus [0, taille de la liste]. * si la liste nest pas vide, focus [0, taille de la liste].
* sinon, focus = 0. * sinon, focus = 0.
*
* @return Objet contenant la liste, lindice 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))
{
// Najoute 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)
]);
},
};
};