Merge pull request #70 from ente-io/search-auto-complete

Search auto complete
This commit is contained in:
Abhinav-grd 2021-05-24 18:01:48 +05:30 committed by GitHub
commit 88f06bb655
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 404 additions and 109 deletions

View file

@ -19,6 +19,7 @@
"bootstrap": "^4.5.2",
"chrono-node": "^2.2.6",
"comlink": "^4.3.0",
"debounce-promise": "^3.1.2",
"exif-js": "^2.3.0",
"formik": "^2.1.5",
"heic2any": "^0.0.3",
@ -34,6 +35,7 @@
"react-burger-menu": "^3.0.4",
"react-dom": "16.13.1",
"react-dropzone": "^11.2.4",
"react-select": "^4.3.1",
"react-top-loading-bar": "^2.0.1",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.6",
@ -45,11 +47,13 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^9.5.3",
"@types/debounce-promise": "^3.1.3",
"@types/libsodium-wrappers": "^0.7.8",
"@types/localforage": "^0.0.34",
"@types/node": "^14.6.4",
"@types/photoswipe": "^4.1.1",
"@types/react": "^16.9.49",
"@types/react-select": "^4.0.15",
"@types/react-window": "^1.8.2",
"@types/react-window-infinite-loader": "^1.0.3",
"@types/styled-components": "^5.1.3",

View file

@ -0,0 +1,20 @@
import React from 'react';
export default function DateIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox={props.viewBox}
width={props.width}
>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"></path>
</svg>
);
}
DateIcon.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
};

View file

@ -0,0 +1,20 @@
import React from 'react';
export default function DateIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox={props.viewBox}
width={props.width}
>
<path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm-4.5-7a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5z"></path>
</svg>
);
}
DateIcon.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
};

View file

@ -0,0 +1,22 @@
import React from 'react';
import styled from 'styled-components';
export default function LocationIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox={props.viewBox}
width={props.width}
>
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zM7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 2.88-2.88 7.19-5 9.88C9.92 16.21 7 11.85 7 9z"></path>
<circle cx="12" cy="9" r="2.5"></circle>
</svg>
);
}
LocationIcon.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
};

View file

@ -1,18 +1,25 @@
import { Formik } from 'formik';
import { SetCollections, SetFiles } from 'pages/gallery';
import React, { useEffect, useState, useRef } from 'react';
import { Form } from 'react-bootstrap';
import styled from 'styled-components';
import constants from 'utils/strings/constants';
import * as Yup from 'yup';
import AsyncSelect from 'react-select/async';
import { components } from 'react-select';
import debounce from 'debounce-promise';
import { File, getLocalFiles } from 'services/fileService';
import {
Collection,
getLocalCollections,
getNonEmptyCollections,
} from 'services/collectionService';
import { parseHumanDate, searchLocation } from 'services/searchService';
import { getFilesWithCreationDay, getFilesInsideBbox } from 'utils/search';
import { Bbox, parseHumanDate, searchLocation } from 'services/searchService';
import {
getFilesWithCreationDay,
getFilesInsideBbox,
getFormattedDate,
} from 'utils/search';
import constants from 'utils/strings/constants';
import LocationIcon from './LocationIcon';
import DateIcon from './DateIcon';
import CrossIcon from './CrossIcon';
const Wrapper = styled.div<{ open: boolean }>`
background-color: #111;
@ -32,8 +39,14 @@ const Wrapper = styled.div<{ open: boolean }>`
display: ${(props) => (props.open ? 'flex' : 'none')};
`;
interface formValues {
searchPhrase: string;
enum SuggestionType {
DATE,
LOCATION,
}
interface Suggestion {
type: SuggestionType;
label: string;
value: Bbox | Date;
}
interface Props {
isOpen: boolean;
@ -63,36 +76,61 @@ export default function SearchBar(props: Props) {
}
}, [props.isOpen]);
const searchFiles = async (values: formValues) => {
props.loadingBar.current?.continuousStart();
const getAutoCompleteSuggestion = async (searchPhrase: string) => {
let option = new Array<Suggestion>();
if (!searchPhrase?.length) {
return option;
}
const searchedDate = parseHumanDate(searchPhrase);
if (searchedDate != null) {
option.push({
type: SuggestionType.DATE,
value: searchedDate,
label: getFormattedDate(searchedDate),
});
}
const searchResults = await searchLocation(searchPhrase);
option.push(
...searchResults.map(
(searchResult) =>
({
type: SuggestionType.LOCATION,
value: searchResult.bbox,
label: searchResult.placeName,
} as Suggestion)
)
);
return option;
};
const getOptions = debounce(getAutoCompleteSuggestion, 100);
const filterFiles = (selectedOption: Suggestion) => {
if (!selectedOption) {
return;
}
let resultFiles: File[] = [];
const searchedDate = parseHumanDate(values.searchPhrase);
if (searchedDate != null) {
const filesWithSameDate = getFilesWithCreationDay(
allFiles,
searchedDate
);
resultFiles.push(...filesWithSameDate);
} else {
const bbox = await searchLocation(values.searchPhrase);
if (bbox) {
switch (selectedOption.type) {
case SuggestionType.DATE:
const searchedDate = selectedOption.value as Date;
const filesWithSameDate = getFilesWithCreationDay(
allFiles,
searchedDate
);
resultFiles = filesWithSameDate;
break;
case SuggestionType.LOCATION:
const bbox = selectedOption.value as Bbox;
const filesTakenAtLocation = getFilesInsideBbox(allFiles, bbox);
resultFiles.push(...filesTakenAtLocation);
}
resultFiles = filesTakenAtLocation;
}
props.setFiles(resultFiles);
props.setCollections(
getNonEmptyCollections(allCollections, resultFiles)
);
await new Promise((resolve) =>
setTimeout(
() => resolve(props.loadingBar.current?.complete()),
5000
)
);
};
const closeSearchBar = ({ resetForm }) => {
props.setOpen(false);
@ -100,68 +138,110 @@ export default function SearchBar(props: Props) {
props.setCollections(allCollections);
resetForm();
};
const getIconByType = (type: SuggestionType) =>
type === SuggestionType.DATE ? <DateIcon /> : <LocationIcon />;
const LabelWithIcon = (props: { type: SuggestionType; label: string }) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<span style={{ marginRight: '10px' }}>
{getIconByType(props.type)}
</span>
<span>{props.label}</span>
</div>
);
const { Option, SingleValue } = components;
const SingleValueWithIcon = (props) => (
<SingleValue {...props}>
<LabelWithIcon type={props.data.type} label={props.data.label} />
</SingleValue>
);
const OptionWithIcon = (props) => (
<Option {...props}>
<LabelWithIcon type={props.data.type} label={props.data.label} />
</Option>
);
const customStyles = {
control: (provided, { isFocused }) => ({
...provided,
backgroundColor: '#282828',
color: '#d1d1d1',
borderColor: isFocused && '#2dc262',
boxShadow: isFocused && '0 0 3px #2dc262',
':hover': {
borderColor: '#2dc262',
},
}),
input: (provided) => ({
...provided,
color: '#d1d1d1',
}),
menu: (provided) => ({
...provided,
marginTop: '10px',
backgroundColor: '#282828',
}),
option: (provided, { isFocused }) => ({
...provided,
backgroundColor: isFocused && '#343434',
}),
dropdownIndicator: (provided) => ({
...provided,
display: 'none',
}),
indicatorSeparator: (provided) => ({
...provided,
display: 'none',
}),
clearIndicator: (provided) => ({
...provided,
':hover': { color: '#d2d2d2' },
}),
singleValue: (provided, state) => ({
...provided,
backgroundColor: '#282828',
color: '#d1d1d1',
}),
};
return (
<Wrapper open={props.isOpen}>
<Formik<formValues>
initialValues={{ searchPhrase: '' }}
onSubmit={searchFiles}
validationSchema={Yup.object().shape({
searchPhrase: Yup.string().required(constants.REQUIRED),
})}
validateOnChange={false}
validateOnBlur={false}
>
{({
values,
touched,
errors,
handleChange,
handleSubmit,
resetForm,
}) => (
<>
<div
style={{
flex: 1,
maxWidth: '600px',
margin: '10px',
}}
>
<Form noValidate onSubmit={handleSubmit}>
<Form.Control
type={'search'}
placeholder={constants.SEARCH_HINT}
value={values.searchPhrase}
onChange={handleChange('searchPhrase')}
ref={searchBarRef}
isInvalid={Boolean(
touched.searchPhrase &&
errors.searchPhrase
)}
/>
</Form>
</div>
<div
style={{
margin: '0',
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
}}
onClick={() => closeSearchBar({ resetForm })}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height={25}
viewBox={`0 0 25 25`}
width={25}
>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"></path>
</svg>
</div>
</>
)}
</Formik>
<>
<div
style={{
flex: 1,
maxWidth: '600px',
margin: '10px',
}}
>
<AsyncSelect
components={{
Option: OptionWithIcon,
SingleValue: SingleValueWithIcon,
}}
ref={searchBarRef}
placeholder={constants.SEARCH_HINT}
loadOptions={getOptions}
onChange={filterFiles}
isClearable
escapeClearsValue
styles={customStyles}
noOptionsMessage={() => null}
/>
</div>
<div
style={{
margin: '0',
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
}}
onClick={() => closeSearchBar({ resetForm: () => null })}
>
<CrossIcon />
</div>
</>
</Wrapper>
);
}

View file

@ -1,24 +1,23 @@
import React, { Props } from 'react';
import React from 'react';
import styled from 'styled-components';
const Wrapper = styled.button<{ open: boolean }>`
const Wrapper = styled.button<{ isOpen: boolean }>`
border: none;
background-color: #404040;
position: fixed;
z-index: 1;
bottom: 50px;
display: ${(props) => (!props.isOpen ? 'block' : 'none')};
right: 50px;
width: 60px;
height: 60px;
border-radius: 50%;
color: #fff;
`;
export default function SearchButton(props) {
if (props.open) {
return <div />;
}
return (
<Wrapper open={props.open} onClick={props.onClick}>
<Wrapper isOpen={props.isOpen} onClick={props.onClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}

View file

@ -114,7 +114,7 @@ export default function Gallery() {
});
const loadingBar = useRef(null);
const [searchView, setSearchView] = useState(false);
const [searchMode, setSearchMode] = useState(false);
useEffect(() => {
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
if (!key) {
@ -258,6 +258,7 @@ export default function Gallery() {
const updateFiles = (files: File[]) => {
setFiles(files);
setSinceTime(new Date().getTime());
selectCollection(null);
};
return (
<FullScreenDropZone
@ -292,8 +293,8 @@ export default function Gallery() {
attributes={dialogMessage}
/>
<SearchBar
isOpen={searchView}
setOpen={setSearchView}
isOpen={searchMode}
setOpen={setSearchMode}
loadingBar={loadingBar}
setFiles={updateFiles}
setCollections={setCollections}
@ -355,9 +356,9 @@ export default function Gallery() {
isFirstLoad={isFirstLoad}
openFileUploader={openFileUploader}
loadingBar={loadingBar}
searchMode={searchView}
searchMode={searchMode}
/>
{files.length < 30 && (
{files.length < 30 && !searchMode && (
<Alert
variant="success"
style={{
@ -383,8 +384,8 @@ export default function Gallery() {
/>
) : (
<SearchButton
open={searchView}
onClick={() => setSearchView(true)}
isOpen={searchMode}
onClick={() => setSearchMode(true)}
/>
)}
</FullScreenDropZone>

View file

@ -1,6 +1,12 @@
import HTTPService from './HTTPService';
import * as chrono from 'chrono-node';
const KM_IN_DEGREE = 0.01;
export type Bbox = [number, number, number, number];
export interface LocationSearchResponse {
placeName: string;
bbox: Bbox;
}
export const getMapboxToken = () => {
return process.env.NEXT_PUBLIC_MAPBOX_TOKEN;
};
@ -11,12 +17,25 @@ export function parseHumanDate(humanDate: string) {
export async function searchLocation(
location: string
): Promise<[number, number, number, number]> {
): Promise<LocationSearchResponse[]> {
const resp = await HTTPService.get(
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURI(
location
)}.json`,
{ access_token: getMapboxToken(), limit: 1 }
{ access_token: getMapboxToken(), limit: 4 }
);
return resp.data.features.length > 0 && resp.data.features[0].bbox;
return resp.data.features.length == 0
? new Array<LocationSearchResponse>()
: resp.data.features.map((feature) => ({
placeName: feature.place_name,
bbox:
feature.bbox ??
([
feature.center[0] - KM_IN_DEGREE,
feature.center[1] - KM_IN_DEGREE,
feature.center[0] + KM_IN_DEGREE,
feature.center[1] + KM_IN_DEGREE,
] as Bbox),
}));
}

View file

@ -33,3 +33,9 @@ export function getFilesWithCreationDay(files: File[], searchedDate: Date) {
isSearchedDate(new Date(file.metadata.creationTime / 1000))
);
}
export function getFormattedDate(date: Date) {
return new Intl.DateTimeFormat('en-IN', {
month: 'long',
day: 'numeric',
}).format(date);
}

134
yarn.lock
View file

@ -1654,6 +1654,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.0", "@babel/runtime@^7.13.10":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.12.13", "@babel/template@^7.7.4":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
@ -1705,6 +1712,22 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@emotion/cache@^11.4.0":
version "11.4.0"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.4.0.tgz#293fc9d9a7a38b9aad8e9337e5014366c3b09ac0"
integrity sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g==
dependencies:
"@emotion/memoize" "^0.7.4"
"@emotion/sheet" "^1.0.0"
"@emotion/utils" "^1.0.0"
"@emotion/weak-memoize" "^0.2.5"
stylis "^4.0.3"
"@emotion/hash@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
"@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
@ -1717,16 +1740,60 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
"@emotion/memoize@^0.7.4":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
"@emotion/react@^11.1.1":
version "11.4.0"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.4.0.tgz#2465ad7b073a691409b88dfd96dc17097ddad9b7"
integrity sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg==
dependencies:
"@babel/runtime" "^7.13.10"
"@emotion/cache" "^11.4.0"
"@emotion/serialize" "^1.0.2"
"@emotion/sheet" "^1.0.1"
"@emotion/utils" "^1.0.0"
"@emotion/weak-memoize" "^0.2.5"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.0.0", "@emotion/serialize@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965"
integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==
dependencies:
"@emotion/hash" "^0.8.0"
"@emotion/memoize" "^0.7.4"
"@emotion/unitless" "^0.7.5"
"@emotion/utils" "^1.0.0"
csstype "^3.0.2"
"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698"
integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g==
"@emotion/stylis@^0.8.4":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
"@emotion/unitless@^0.7.4":
"@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@emotion/utils@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af"
integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==
"@emotion/weak-memoize@^0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@jimp/bmp@^0.16.1":
version "0.16.1"
resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.16.1.tgz#6e2da655b2ba22e721df0795423f34e92ef13768"
@ -2193,6 +2260,11 @@
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.11.tgz#2521cc86f69d15c5b90664e4829d84566052c1cf"
integrity sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==
"@types/debounce-promise@^3.1.3":
version "3.1.3"
resolved "https://registry.yarnpkg.com/@types/debounce-promise/-/debounce-promise-3.1.3.tgz#dd0d6b96ee61da0dd4c413e3ea03a425ffa36b3f"
integrity sha512-mjcCf//DAUQ6YLQMhqYJAv/+a4BsE1GQFmy1el5K62wLJJmQwGi3TsnshhOFynPpuBF9Gh2Vvb+5ImPi47KaZw==
"@types/hoist-non-react-statics@*":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
@ -2245,7 +2317,24 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react-transition-group@^4.4.0":
"@types/react-dom@*":
version "17.0.5"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.5.tgz#df44eed5b8d9e0b13bb0cd38e0ea6572a1231227"
integrity sha512-ikqukEhH4H9gr4iJCmQVNzTB307kROe3XFfHAOTxOXPOw7lAoEXnM5KWTkzeANGL5Ce6ABfiMl/zJBYNi7ObmQ==
dependencies:
"@types/react" "*"
"@types/react-select@^4.0.15":
version "4.0.15"
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-4.0.15.tgz#2e6a1cff22c4bbae6c95b8dbee5b5097c12eae54"
integrity sha512-GPyBFYGMVFCtF4eg9riodEco+s2mflR10Nd5csx69+bcdvX6Uo9H/jgrIqovBU9yxBppB9DS66OwD6xxgVqOYQ==
dependencies:
"@emotion/serialize" "^1.0.0"
"@types/react" "*"
"@types/react-dom" "*"
"@types/react-transition-group" "*"
"@types/react-transition-group@*", "@types/react-transition-group@^4.4.0":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1"
integrity sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==
@ -3671,6 +3760,11 @@ debounce-fn@^4.0.0:
dependencies:
mimic-fn "^3.0.0"
debounce-promise@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/debounce-promise/-/debounce-promise-3.1.2.tgz#320fb8c7d15a344455cd33cee5ab63530b6dc7c5"
integrity sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -4627,7 +4721,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0:
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -5348,6 +5442,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
memoize-one@^5.0.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
memory-fs@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@ -6288,7 +6387,7 @@ prop-types-extra@^1.1.0:
react-is "^16.3.2"
warning "^4.0.0"
prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -6491,6 +6590,13 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-input-autosize@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
integrity sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==
dependencies:
prop-types "^15.5.8"
react-is@16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -6527,12 +6633,25 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
react-select@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81"
integrity sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==
dependencies:
"@babel/runtime" "^7.12.0"
"@emotion/cache" "^11.4.0"
"@emotion/react" "^11.1.1"
memoize-one "^5.0.0"
prop-types "^15.6.0"
react-input-autosize "^3.0.0"
react-transition-group "^4.3.0"
react-top-loading-bar@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-top-loading-bar/-/react-top-loading-bar-2.0.1.tgz#c8805ad9c1068766fdd3cadd414e67cfdf1878e9"
integrity sha512-wkRlK9Rte4TU817GDcjlsCoDOxrrnvsNvK609FKyio0EIrmmqjQDz5DB8HbN88CHNZBy5Lh/OBALc03ioWFPuQ==
react-transition-group@^4.4.1:
react-transition-group@^4.3.0, react-transition-group@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
@ -7268,6 +7387,11 @@ stylis@3.5.4:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==
stylis@^4.0.3:
version "4.0.10"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240"
integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"