Browse Source

app: Implémentation d’une recherche de test

master
Mattéo Delabre 1 year ago
parent
commit
8b77ed5435
Signed by: matteo <spam@delab.re> GPG Key ID: AE3FBD02DC583ABB
12 changed files with 188 additions and 41 deletions
  1. +2
    -1
      .babelrc
  2. +1
    -1
      app/index.html
  3. +2
    -2
      app/src/App.js
  4. +0
    -1
      app/src/Graph.js
  5. +13
    -0
      app/src/ResultsGraph.js
  6. +37
    -0
      app/src/SearchResults.js
  7. +11
    -2
      app/src/TermInput.js
  8. +81
    -27
      app/src/fetch.js
  9. +0
    -0
      app/src/index.js
  10. +1
    -1
      app/style/style.css
  11. +38
    -6
      package-lock.json
  12. +2
    -0
      package.json

+ 2
- 1
.babelrc View File

@@ -1,3 +1,4 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}

+ 1
- 1
app/index.html View File

@@ -9,6 +9,6 @@
</head>
<body>
<div id="root"></div>
<script src="index.js"></script>
<script src="src/index.js"></script>
</body>
</html>

app/App.js → app/src/App.js View File

@@ -1,6 +1,6 @@
import React, {useState} from 'react';
import TermInput from './TermInput.js';
import ResultsGraph from './ResultsGraph.js';
import SearchResults from './SearchResults.js';

const App = () =>
{
@@ -9,7 +9,7 @@ const App = () =>
return (
<div className="App">
<TermInput terms={terms} setTerms={setTerms} />
<ResultsGraph />
<SearchResults terms={terms} />
</div>
);
};

app/Graph.js → app/src/Graph.js View File

@@ -191,7 +191,6 @@ const Graph = ({nodes, edges, render}) =>

return () =>
{
layout.stop();
graphParent.current.removeEventListener('mousedown', mouseDown);
document.body.removeEventListener('mousemove', mouseMove);
document.body.removeEventListener('mouseup', mouseUp);

+ 13
- 0
app/src/ResultsGraph.js View File

@@ -0,0 +1,13 @@
import React from 'react';
import Graph from './Graph.js';


const ResultsGraph = () => (
<Graph
nodes={Object.keys(nodes)}
edges={edges}
render={id => nodes[id].name}
/>
);

export default ResultsGraph;

+ 37
- 0
app/src/SearchResults.js View File

@@ -0,0 +1,37 @@
import React, {useState, useEffect} from 'react';
import Graph from './Graph.js';
import {searchTerms} from './fetch.js';

const useResults = terms =>
{
const [results, setResults] = useState({
nodes: {},
edges: []
});

useEffect(() =>
{
const fetch = async () =>
{
setResults(await searchTerms(terms));
};

fetch();
}, [terms]);

return results;
};

const SearchResults = ({terms}) =>
{
const {nodes, edges} = useResults(terms);
return (
<Graph
nodes={Object.keys(nodes)}
edges={edges}
render={id => nodes[id].name}
/>
);
};

export default SearchResults;

app/TermInput.js → app/src/TermInput.js View File

@@ -1,6 +1,7 @@
import React, {useState} from 'react';

const enterKey = 13;
const backspaceKey = 8;

const TermInput = ({terms, setTerms}) =>
{
@@ -8,7 +9,7 @@ const TermInput = ({terms, setTerms}) =>

const handleKeyDown = ev =>
{
if (ev.keyCode === enterKey)
if (ev.keyCode === enterKey && value)
{
ev.preventDefault();

@@ -18,6 +19,14 @@ const TermInput = ({terms, setTerms}) =>
}

setValue('');
return;
}

if (ev.keyCode === backspaceKey && !value)
{
ev.preventDefault();
setTerms(terms.slice(0, -1));
return;
}
};

@@ -42,7 +51,7 @@ const TermInput = ({terms, setTerms}) =>
>{term}</span>
)}
<input
autofocus="true" type="text" className="TermInput_input"
autoFocus={true} type="text" className="TermInput_input"
placeholder="Rechercher un symptôme, un signe ou une maladie…"
value={value}
onChange={handleChange} onKeyDown={handleKeyDown} />

app/ResultsGraph.js → app/src/fetch.js View File

@@ -1,21 +1,4 @@
import React from 'react';
import Graph from './Graph.js';

// const config = {
// automaticRearrangeAfterDropNode: true,
// node: {
// color: 'lightgreen',
// size: 240,
// fontSize: 14,
// highlightStrokeColor: 'blue',
// labelProperty: 'name'
// },
// link: {
// highlightColor: 'lightblue'
// }
// };

const nodes = {
const mockNodes = {
Q2840: {
id: 'Q2840',
name: 'Grippe',
@@ -127,7 +110,7 @@ const nodes = {
}
};

const edges = [
const mockEdges = [
['Q2840', 'Q38933'],
['Q2840', 'Q1115038'],
['Q2840', 'Q474959'],
@@ -165,12 +148,83 @@ const edges = [
['Q133780', 'Q160796']
];

const ResultsGraph = () => (
<Graph
nodes={Object.keys(nodes)}
edges={edges}
render={id => nodes[id].name}
/>
);
/**
* Vérifie si une liste d’arêtes contient une arête donnée, dans un sens ou
* dans l’autre.
*
* @param list Liste d’arêtes.
* @param from Premier nœud de l’arête.
* @param to Second nœud de l’arête.
* @return Vrai si et seulement si l’arête existe.
*/
const includesEdge = (list, from, to) =>
list.some(([source, target]) => (
(source === from && target === to)
|| (source === to && target === from)
));

export const searchTerms = terms => new Promise((res, rej) =>
{
// Fait attendre artificiellement pour simuler une requête
setTimeout(() =>
{
const nodes = {};
const edges = [];

// Récupération des identifiants correspondant aux termes de la requête
const termIds = terms.map(term =>
Object.keys(mockNodes)
.find(id => mockNodes[id].name === term)
);

// Si l’un des termes est inconnu, aucun résultat
if (!termIds.includes(undefined))
{
// Sélection des termes de la requête
for (let termId of termIds)
{
nodes[termId] = mockNodes[termId];
}

// Sélection des nœuds liés à tous les éléments de la requête
const resultIds = [];

for (let [id, data] of Object.entries(mockNodes))
{
if (termIds.every(
termId => includesEdge(mockEdges, id, termId)
))
{
nodes[id] = data;
resultIds.push(id);
}
}

// Sélection des voisins des résultats de la requête
for (let [id, data] of Object.entries(mockNodes))
{
for (let resultId of resultIds)
{
if (includesEdge(mockEdges, resultId, id))
{
nodes[id] = data;
}
}
}

// Sélection des arêtes liant les nœuds sélectionnés
for (let [from, to] of mockEdges)
{
if (
(from in nodes && to in nodes)
&& !includesEdge(edges, from, to)
)
{
edges.push([from, to]);
}
}
}

export default ResultsGraph;
res({nodes, edges});
}, 500);
});

app/index.js → app/src/index.js View File


+ 1
- 1
app/style/style.css View File

@@ -104,7 +104,7 @@ input
color: var(--color-light);

border-radius: 2px;
padding: calc(.2 * var(--base-size));
padding: calc(.25 * var(--base-size)) calc(.5 * var(--base-size));
margin-left: calc(.3 * var(--base-size));
cursor: pointer;



+ 38
- 6
package-lock.json View File

@@ -730,6 +730,40 @@
"@babel/helper-plugin-utils": "^7.0.0"
}
},
"@babel/plugin-transform-runtime": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.4.tgz",
"integrity": "sha512-O8kSkS5fP74Ad/8pfsCMGa8sBRdLxYoSReaARRNSz3FbFQj3z/QUvoUmJ28gn9BO93YfnXc3j+Xyaqe8cKDNBQ==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.7.4",
"@babel/helper-plugin-utils": "^7.0.0",
"resolve": "^1.8.1",
"semver": "^5.5.1"
},
"dependencies": {
"@babel/helper-module-imports": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz",
"integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==",
"dev": true,
"requires": {
"@babel/types": "^7.7.4"
}
},
"@babel/types": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz",
"integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==",
"dev": true,
"requires": {
"esutils": "^2.0.2",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
}
}
},
"@babel/plugin-transform-shorthand-properties": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
@@ -860,10 +894,9 @@
}
},
"@babel/runtime": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz",
"integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==",
"dev": true,
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.4.tgz",
"integrity": "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw==",
"requires": {
"regenerator-runtime": "^0.13.2"
}
@@ -5954,8 +5987,7 @@
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
"dev": true
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
},
"regenerator-transform": {
"version": "0.14.1",


+ 2
- 0
package.json View File

@@ -5,11 +5,13 @@
"main": "index.js",
"devDependencies": {
"@babel/core": "^7.7.2",
"@babel/plugin-transform-runtime": "^7.7.4",
"@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.7.0",
"parcel-bundler": "^1.12.4"
},
"dependencies": {
"@babel/runtime": "^7.7.4",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"springy": "^2.8.0"


Loading…
Cancel
Save