🔥 Plus de react-select pour la recherche de règles

pull/763/head
Maël 2019-10-30 16:54:57 +01:00 committed by Mael
parent 50d691b4af
commit 5dbb93eeca
5 changed files with 129 additions and 64 deletions

View File

@ -176,6 +176,7 @@
"webpack-dev-middleware": "^3.4.0",
"webpack-hot-middleware": "^2.24.2",
"workbox-webpack-plugin": "^3.6.1",
"worker-loader": "^2.0.0",
"yaml-jest": "^1.0.5",
"yaml-loader": "^0.5.0"
},

View File

@ -1,50 +1,87 @@
import withSitePaths from 'Components/utils/withSitePaths'
import { encodeRuleName } from 'Engine/rules'
import Fuse from 'fuse.js'
import { compose, pick, sortBy } from 'ramda'
import React, { useMemo, useRef, useState } from 'react'
import { encodeRuleName, parentName } from 'Engine/rules.js'
import { compose, pick, sortBy, take } from 'ramda'
import React, { useEffect, useState } from 'react'
import Highlighter from 'react-highlight-words'
import { useTranslation } from 'react-i18next'
import { Link, Redirect } from 'react-router-dom'
import Select from 'react-select'
import 'react-select/dist/react-select.css'
import Worker from 'worker-loader!./SearchBar.worker.js'
import { capitalise0 } from '../utils'
function SearchBar({
const worker = new Worker()
let SearchBar = ({
rules,
showDefaultList,
finally: finallyCallback,
sitePaths
}) {
const [inputValue, setInputValue] = useState(null)
}) => {
const [input, setInput] = useState('')
const [selectedOption, setSelectedOption] = useState(null)
const inputElementRef = useRef()
// This operation is expensive, we don't want to do it everytime we re-render, so we cache its result
const fuse = useMemo(() => {
const list = rules.map(
pick(['title', 'espace', 'description', 'name', 'dottedName'])
)
const options = {
keys: [
{ name: 'name', weight: 0.3 },
{ name: 'title', weight: 0.3 },
{ name: 'espace', weight: 0.2 },
{ name: 'description', weight: 0.2 }
]
}
return new Fuse(list, options)
}, [rules])
const [results, setResults] = useState([])
const { i18n } = useTranslation()
const renderOption = ({ title, dottedName }) => (
<span>
<Highlighter searchWords={[inputValue]} textToHighlight={title} />
<span style={{ opacity: 0.6, fontSize: '75%', marginLeft: '.6em' }}>
<Highlighter searchWords={[inputValue]} textToHighlight={dottedName} />
</span>
</span>
)
const filterOptions = (options, filter) => fuse.search(filter)
useEffect(() => {
worker.postMessage({
rules: rules.map(
pick(['title', 'espace', 'description', 'name', 'dottedName'])
)
})
worker.onmessage = ({ data: results }) => setResults(results)
}, [])
let renderOptions = rules => {
let options =
(rules && sortBy(rule => rule.dottedName, rules)) || take(5)(results)
return <ul>{options.map(option => renderOption(option))}</ul>
}
let renderOption = option => {
let { title, dottedName, name } = option
return (
<li
key={dottedName}
css={`
padding: 0.4rem;
border-radius: 0.3rem;
:hover {
background: var(--colour);
color: var(--textColour);
}
:hover a {
color: var(--textColour);
}
`}
onClick={() => setSelectedOption(option)}>
<div
style={{
fontWeight: '300',
fontSize: '85%',
lineHeight: '.9em'
}}>
<Highlighter
searchWords={[input]}
textToHighlight={
parentName(dottedName)
? parentName(dottedName)
.split(' . ')
.map(capitalise0)
.join(' - ')
: ''
}
/>
</div>
<Link
to={sitePaths.documentation.index + '/' + encodeRuleName(dottedName)}>
<Highlighter
searchWords={[input]}
textToHighlight={title || capitalise0(name)}
/>
</Link>
</li>
)
}
if (selectedOption != null) {
finallyCallback && finallyCallback()
@ -61,37 +98,22 @@ function SearchBar({
return (
<>
<Select
value={selectedOption && selectedOption.dottedName}
onChange={setSelectedOption}
onInputChange={setInputValue}
valueKey="dottedName"
labelKey="title"
options={rules}
filterOptions={filterOptions}
optionRenderer={renderOption}
<input
type="text"
value={input}
placeholder={i18n.t('Entrez des mots clefs ici')}
noResultsText={i18n.t('noresults', {
onChange={e => {
let input = e.target.value
setInput(input)
if (input.length > 2) worker.postMessage({ input })
}}
/>
{input.length > 2 &&
!results.length &&
i18n.t('noresults', {
defaultValue: "Nous n'avons rien trouvé…"
})}
ref={inputElementRef}
/>
{showDefaultList && !inputValue && (
<ul>
{sortBy(rule => rule.title, rules).map(rule => (
<li key={rule.dottedName}>
<Link
to={
sitePaths.documentation.index +
'/' +
encodeRuleName(rule.dottedName)
}>
{rule.title || capitalise0(rule.name)}
</Link>
</li>
))}
</ul>
)}
{showDefaultList && !input ? renderOptions(rules) : renderOptions()}
</>
)
}

View File

@ -0,0 +1,33 @@
import Fuse from 'fuse.js'
let searchWeights = [
{
name: 'name',
weight: 0.3
},
{
name: 'title',
weight: 0.3
},
{
name: 'espace',
weight: 0.2
},
{
name: 'description',
weight: 0.2
}
]
let fuse = null
onmessage = function(event) {
if (event.data.rules)
fuse = new Fuse(event.data.rules, {
keys: searchWeights
})
if (event.data.input) {
let results = fuse.search(event.data.input)
postMessage( results )
}
}

View File

@ -27,7 +27,8 @@ module.exports.default = {
publicodes: './source/sites/publi.codes/entry.js'
},
output: {
path: path.resolve('./dist/')
path: path.resolve('./dist/'),
globalObject: 'self'
},
plugins: [
new EnvironmentPlugin({

View File

@ -6736,7 +6736,7 @@ loader-runner@^2.4.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
loader-utils@1.2.3, loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
@ -11354,6 +11354,14 @@ worker-farm@^1.7.0:
dependencies:
errno "~0.1.7"
worker-loader@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"
integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==
dependencies:
loader-utils "^1.0.0"
schema-utils "^0.4.0"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"