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