refactor search
This commit is contained in:
parent
19dd45817c
commit
e09bcdd34b
56
src/components/Search/customSearchComponents.tsx
Normal file
56
src/components/Search/customSearchComponents.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import React from 'react';
|
||||
import CollectionIcon from 'components/icons/CollectionIcon';
|
||||
import DateIcon from 'components/icons/DateIcon';
|
||||
import ImageIcon from 'components/icons/ImageIcon';
|
||||
import LocationIcon from 'components/icons/LocationIcon';
|
||||
import VideoIcon from 'components/icons/VideoIcon';
|
||||
import { components } from 'react-select';
|
||||
import { SuggestionType } from 'types/search';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
|
||||
const { Option, Control } = components;
|
||||
|
||||
const getIconByType = (type: SuggestionType) => {
|
||||
switch (type) {
|
||||
case SuggestionType.DATE:
|
||||
return <DateIcon />;
|
||||
case SuggestionType.LOCATION:
|
||||
return <LocationIcon />;
|
||||
case SuggestionType.COLLECTION:
|
||||
return <CollectionIcon />;
|
||||
case SuggestionType.IMAGE:
|
||||
return <ImageIcon />;
|
||||
case SuggestionType.VIDEO:
|
||||
return <VideoIcon />;
|
||||
default:
|
||||
return <SearchIcon />;
|
||||
}
|
||||
};
|
||||
|
||||
export const OptionWithIcon = (props) => (
|
||||
<Option {...props}>
|
||||
<LabelWithIcon type={props.data.type} label={props.data.label} />
|
||||
</Option>
|
||||
);
|
||||
export const ControlWithIcon = (props) => (
|
||||
<Control {...props}>
|
||||
<span
|
||||
className="icon"
|
||||
style={{
|
||||
paddingLeft: '10px',
|
||||
paddingBottom: '4px',
|
||||
}}>
|
||||
{getIconByType(props.getValue()[0]?.type)}
|
||||
</span>
|
||||
{props.children}
|
||||
</Control>
|
||||
);
|
||||
|
||||
const LabelWithIcon = (props: { type: SuggestionType; label: string }) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span style={{ paddingRight: '10px', paddingBottom: '4px' }}>
|
||||
{getIconByType(props.type)}
|
||||
</span>
|
||||
<span>{props.label}</span>
|
||||
</div>
|
||||
);
|
|
@ -1,299 +1,40 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import { components } from 'react-select';
|
||||
import debounce from 'debounce-promise';
|
||||
import {
|
||||
getHolidaySuggestion,
|
||||
getYearSuggestion,
|
||||
parseHumanDate,
|
||||
searchCollection,
|
||||
searchFiles,
|
||||
searchLocation,
|
||||
} from 'services/searchService';
|
||||
import { getFormattedDate, isInsideBox } from 'utils/search';
|
||||
import constants from 'utils/strings/constants';
|
||||
import LocationIcon from '../icons/LocationIcon';
|
||||
import DateIcon from '../icons/DateIcon';
|
||||
import SearchIcon from '../icons/SearchIcon';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import React from 'react';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { Collection } from 'types/collection';
|
||||
import CollectionIcon from '../icons/CollectionIcon';
|
||||
|
||||
import ImageIcon from '../icons/ImageIcon';
|
||||
import VideoIcon from '../icons/VideoIcon';
|
||||
import { IconButton } from '../Container';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { Suggestion, SuggestionType, DateValue, Bbox } from 'types/search';
|
||||
import { Search, SearchStats } from 'types/gallery';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { SelectStyles } from './styles';
|
||||
import { AppContext } from 'pages/_app';
|
||||
|
||||
const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>`
|
||||
display: ${({ isOpen }) => (isOpen ? 'flex' : 'none')};
|
||||
width: 100%;
|
||||
@media (min-width: 625px) {
|
||||
display: flex;
|
||||
width: calc(100vw - 140px);
|
||||
margin: 0 70px;
|
||||
}
|
||||
align-items: center;
|
||||
min-height: 64px;
|
||||
transition: opacity 1s ease;
|
||||
opacity: ${(props) => (props.isDisabled ? 0 : 1)};
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const SearchButton = styled.div<{ isOpen: boolean }>`
|
||||
display: none;
|
||||
@media (max-width: 624px) {
|
||||
display: ${({ isOpen }) => (!isOpen ? 'flex' : 'none')};
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
min-height: 64px;
|
||||
}
|
||||
`;
|
||||
|
||||
const SearchInput = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 484px;
|
||||
margin: auto;
|
||||
`;
|
||||
import { Search } from 'types/gallery';
|
||||
import { SearchBarWrapper, SearchButtonWrapper } from './styledComponents';
|
||||
import SearchInput from './input';
|
||||
import { SelectionBar } from 'components/Navbar/SelectionBar';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
isFirstFetch: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
setSearch: (search: Search) => void;
|
||||
searchStats: SearchStats;
|
||||
collections: Collection[];
|
||||
setActiveCollection: (id: number) => void;
|
||||
files: EnteFile[];
|
||||
}
|
||||
export default function SearchBar(props: Props) {
|
||||
const [value, setValue] = useState<Suggestion>(null);
|
||||
const appContext = useContext(AppContext);
|
||||
const handleChange = (value) => {
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
useEffect(() => search(value), [value]);
|
||||
|
||||
// = =========================
|
||||
// Functionality
|
||||
// = =========================
|
||||
const getAutoCompleteSuggestions = async (searchPhrase: string) => {
|
||||
searchPhrase = searchPhrase.trim().toLowerCase();
|
||||
if (!searchPhrase?.length) {
|
||||
return [];
|
||||
}
|
||||
const options = [
|
||||
...getHolidaySuggestion(searchPhrase),
|
||||
...getYearSuggestion(searchPhrase),
|
||||
];
|
||||
|
||||
const searchedDates = parseHumanDate(searchPhrase);
|
||||
|
||||
options.push(
|
||||
...searchedDates.map((searchedDate) => ({
|
||||
type: SuggestionType.DATE,
|
||||
value: searchedDate,
|
||||
label: getFormattedDate(searchedDate),
|
||||
}))
|
||||
);
|
||||
|
||||
const collectionResults = searchCollection(
|
||||
searchPhrase,
|
||||
props.collections
|
||||
);
|
||||
options.push(
|
||||
...collectionResults.map(
|
||||
(searchResult) =>
|
||||
({
|
||||
type: SuggestionType.COLLECTION,
|
||||
value: searchResult.id,
|
||||
label: searchResult.name,
|
||||
} as Suggestion)
|
||||
)
|
||||
);
|
||||
const fileResults = searchFiles(searchPhrase, props.files);
|
||||
options.push(
|
||||
...fileResults.map((file) => ({
|
||||
type:
|
||||
file.type === FILE_TYPE.IMAGE
|
||||
? SuggestionType.IMAGE
|
||||
: SuggestionType.VIDEO,
|
||||
value: file.index,
|
||||
label: file.title,
|
||||
}))
|
||||
);
|
||||
|
||||
const locationResults = await searchLocation(searchPhrase);
|
||||
|
||||
const locationResultsHasFiles: boolean[] = new Array(
|
||||
locationResults.length
|
||||
).fill(false);
|
||||
props.files.map((file) => {
|
||||
for (const [index, location] of locationResults.entries()) {
|
||||
if (
|
||||
isInsideBox(
|
||||
{
|
||||
latitude: file.metadata.latitude,
|
||||
longitude: file.metadata.longitude,
|
||||
},
|
||||
location.bbox
|
||||
)
|
||||
) {
|
||||
locationResultsHasFiles[index] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
const filteredLocationWithFiles = locationResults.filter(
|
||||
(_, index) => locationResultsHasFiles[index]
|
||||
);
|
||||
options.push(
|
||||
...filteredLocationWithFiles.map(
|
||||
(searchResult) =>
|
||||
({
|
||||
type: SuggestionType.LOCATION,
|
||||
value: searchResult.bbox,
|
||||
label: searchResult.place,
|
||||
} as Suggestion)
|
||||
)
|
||||
);
|
||||
return options;
|
||||
};
|
||||
|
||||
const getOptions = debounce(getAutoCompleteSuggestions, 250);
|
||||
|
||||
const search = (selectedOption: Suggestion) => {
|
||||
if (!selectedOption) {
|
||||
return;
|
||||
}
|
||||
switch (selectedOption.type) {
|
||||
case SuggestionType.DATE:
|
||||
props.setSearch({
|
||||
date: selectedOption.value as DateValue,
|
||||
});
|
||||
props.setOpen(true);
|
||||
break;
|
||||
case SuggestionType.LOCATION:
|
||||
props.setSearch({
|
||||
location: selectedOption.value as Bbox,
|
||||
});
|
||||
props.setOpen(true);
|
||||
break;
|
||||
case SuggestionType.COLLECTION:
|
||||
props.setActiveCollection(selectedOption.value as number);
|
||||
setValue(null);
|
||||
break;
|
||||
case SuggestionType.IMAGE:
|
||||
case SuggestionType.VIDEO:
|
||||
props.setSearch({ fileIndex: selectedOption.value as number });
|
||||
setValue(null);
|
||||
break;
|
||||
}
|
||||
};
|
||||
const resetSearch = () => {
|
||||
if (props.isOpen) {
|
||||
appContext.startLoading();
|
||||
props.setSearch({});
|
||||
setTimeout(() => {
|
||||
appContext.finishLoading();
|
||||
}, 10);
|
||||
props.setOpen(false);
|
||||
setValue(null);
|
||||
}
|
||||
};
|
||||
|
||||
// = =========================
|
||||
// UI
|
||||
// = =========================
|
||||
|
||||
const getIconByType = (type: SuggestionType) => {
|
||||
switch (type) {
|
||||
case SuggestionType.DATE:
|
||||
return <DateIcon />;
|
||||
case SuggestionType.LOCATION:
|
||||
return <LocationIcon />;
|
||||
case SuggestionType.COLLECTION:
|
||||
return <CollectionIcon />;
|
||||
case SuggestionType.IMAGE:
|
||||
return <ImageIcon />;
|
||||
case SuggestionType.VIDEO:
|
||||
return <VideoIcon />;
|
||||
default:
|
||||
return <SearchIcon />;
|
||||
}
|
||||
};
|
||||
|
||||
const LabelWithIcon = (props: { type: SuggestionType; label: string }) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span style={{ paddingRight: '10px', paddingBottom: '4px' }}>
|
||||
{getIconByType(props.type)}
|
||||
</span>
|
||||
<span>{props.label}</span>
|
||||
</div>
|
||||
);
|
||||
const { Option, Control } = components;
|
||||
|
||||
const OptionWithIcon = (props) => (
|
||||
<Option {...props}>
|
||||
<LabelWithIcon type={props.data.type} label={props.data.label} />
|
||||
</Option>
|
||||
);
|
||||
const ControlWithIcon = (props) => (
|
||||
<Control {...props}>
|
||||
<span
|
||||
className="icon"
|
||||
style={{
|
||||
paddingLeft: '10px',
|
||||
paddingBottom: '4px',
|
||||
}}>
|
||||
{getIconByType(props.getValue()[0]?.type)}
|
||||
</span>
|
||||
{props.children}
|
||||
</Control>
|
||||
);
|
||||
export default function SearchBar({ isFirstFetch, ...props }: Props) {
|
||||
return (
|
||||
<>
|
||||
<Wrapper isDisabled={props.isFirstFetch} isOpen={props.isOpen}>
|
||||
<SearchInput>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
margin: '10px',
|
||||
}}>
|
||||
<AsyncSelect
|
||||
value={value}
|
||||
components={{
|
||||
Option: OptionWithIcon,
|
||||
Control: ControlWithIcon,
|
||||
}}
|
||||
placeholder={constants.SEARCH_HINT()}
|
||||
loadOptions={getOptions}
|
||||
onChange={handleChange}
|
||||
isClearable
|
||||
escapeClearsValue
|
||||
styles={SelectStyles}
|
||||
noOptionsMessage={() => null}
|
||||
/>
|
||||
</div>
|
||||
{props.isOpen && (
|
||||
<IconButton onClick={() => resetSearch()}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</SearchInput>
|
||||
</Wrapper>
|
||||
<SearchButton
|
||||
isOpen={props.isOpen}
|
||||
onClick={() => !props.isFirstFetch && props.setOpen(true)}>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
<SearchBarWrapper>
|
||||
<SearchInput {...props} />
|
||||
</SearchBarWrapper>
|
||||
<SearchButtonWrapper>
|
||||
<IconButton
|
||||
onClick={() => !isFirstFetch && props.setOpen(true)}>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</SearchButtonWrapper>
|
||||
{props.isOpen && (
|
||||
<SelectionBar>
|
||||
<SearchInput {...props} />
|
||||
</SelectionBar>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
105
src/components/Search/input.tsx
Normal file
105
src/components/Search/input.tsx
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { IconButton } from '@mui/material';
|
||||
import debounce from 'debounce-promise';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { getAutoCompleteSuggestions } from 'services/searchService';
|
||||
import { Bbox, DateValue, Suggestion, SuggestionType } from 'types/search';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { OptionWithIcon, ControlWithIcon } from './customSearchComponents';
|
||||
import { SearchInputWrapper } from './styledComponents';
|
||||
import { SelectStyles } from './styles';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { SetSearch } from 'types/gallery';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { Collection } from 'types/collection';
|
||||
|
||||
interface Iprops {
|
||||
isOpen: boolean;
|
||||
setSearch: SetSearch;
|
||||
setOpen: (value: boolean) => void;
|
||||
files: EnteFile[];
|
||||
collections: Collection[];
|
||||
setActiveCollection: (id: number) => void;
|
||||
}
|
||||
|
||||
export default function SearchInput(props: Iprops) {
|
||||
const [value, setValue] = useState<Suggestion>(null);
|
||||
const appContext = useContext(AppContext);
|
||||
const handleChange = (value: Suggestion) => {
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
useEffect(() => search(value), [value]);
|
||||
|
||||
const resetSearch = () => {
|
||||
if (props.isOpen) {
|
||||
appContext.startLoading();
|
||||
props.setSearch({});
|
||||
setTimeout(() => {
|
||||
appContext.finishLoading();
|
||||
}, 10);
|
||||
props.setOpen(false);
|
||||
setValue(null);
|
||||
}
|
||||
};
|
||||
|
||||
const getOptions = debounce(
|
||||
getAutoCompleteSuggestions(props.files, props.collections),
|
||||
250
|
||||
);
|
||||
|
||||
const search = (selectedOption: Suggestion) => {
|
||||
if (!selectedOption) {
|
||||
return;
|
||||
}
|
||||
switch (selectedOption.type) {
|
||||
case SuggestionType.DATE:
|
||||
props.setSearch({
|
||||
date: selectedOption.value as DateValue,
|
||||
});
|
||||
props.setOpen(true);
|
||||
break;
|
||||
case SuggestionType.LOCATION:
|
||||
props.setSearch({
|
||||
location: selectedOption.value as Bbox,
|
||||
});
|
||||
props.setOpen(true);
|
||||
break;
|
||||
case SuggestionType.COLLECTION:
|
||||
props.setActiveCollection(selectedOption.value as number);
|
||||
setValue(null);
|
||||
break;
|
||||
case SuggestionType.IMAGE:
|
||||
case SuggestionType.VIDEO:
|
||||
props.setSearch({ fileIndex: selectedOption.value as number });
|
||||
setValue(null);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchInputWrapper>
|
||||
<AsyncSelect
|
||||
value={value}
|
||||
components={{
|
||||
Option: OptionWithIcon,
|
||||
Control: ControlWithIcon,
|
||||
}}
|
||||
placeholder={constants.SEARCH_HINT()}
|
||||
loadOptions={getOptions}
|
||||
onChange={handleChange}
|
||||
isClearable
|
||||
escapeClearsValue
|
||||
styles={SelectStyles}
|
||||
noOptionsMessage={() => null}
|
||||
/>
|
||||
|
||||
{props.isOpen && (
|
||||
<IconButton onClick={() => resetSearch()} sx={{ ml: 1 }}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</SearchInputWrapper>
|
||||
);
|
||||
}
|
31
src/components/Search/styledComponents.tsx
Normal file
31
src/components/Search/styledComponents.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
CenteredFlex,
|
||||
FlexWrapper,
|
||||
FluidContainer,
|
||||
} from 'components/Container';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SearchBarWrapper = styled(CenteredFlex)`
|
||||
width: 100%;
|
||||
@media (max-width: 624px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchButtonWrapper = styled(FluidContainer)`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-height: 64px;
|
||||
padding: 0 20px;
|
||||
@media (min-width: 624px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchInputWrapper = styled(FlexWrapper)`
|
||||
width: 100%;
|
||||
max-width: 484px;
|
||||
margin: auto;
|
||||
`;
|
|
@ -1,4 +1,8 @@
|
|||
export const SelectStyles = {
|
||||
container: (style) => ({
|
||||
...style,
|
||||
flex: 1,
|
||||
}),
|
||||
control: (style, { isFocused }) => ({
|
||||
...style,
|
||||
backgroundColor: '#282828',
|
||||
|
|
|
@ -12,12 +12,95 @@ import {
|
|||
Suggestion,
|
||||
SuggestionType,
|
||||
} from 'types/search';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { getFormattedDate, isInsideBox } from 'utils/search';
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
|
||||
const DIGITS = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
|
||||
|
||||
export function parseHumanDate(humanDate: string): DateValue[] {
|
||||
export const getAutoCompleteSuggestions =
|
||||
(files: EnteFile[], collections: Collection[]) =>
|
||||
async (searchPhrase: string) => {
|
||||
searchPhrase = searchPhrase.trim().toLowerCase();
|
||||
if (!searchPhrase?.length) {
|
||||
return [];
|
||||
}
|
||||
const options = [
|
||||
...getHolidaySuggestion(searchPhrase),
|
||||
...getYearSuggestion(searchPhrase),
|
||||
];
|
||||
|
||||
const searchedDates = parseHumanDate(searchPhrase);
|
||||
|
||||
options.push(
|
||||
...searchedDates.map((searchedDate) => ({
|
||||
type: SuggestionType.DATE,
|
||||
value: searchedDate,
|
||||
label: getFormattedDate(searchedDate),
|
||||
}))
|
||||
);
|
||||
|
||||
const collectionResults = searchCollection(searchPhrase, collections);
|
||||
options.push(
|
||||
...collectionResults.map(
|
||||
(searchResult) =>
|
||||
({
|
||||
type: SuggestionType.COLLECTION,
|
||||
value: searchResult.id,
|
||||
label: searchResult.name,
|
||||
} as Suggestion)
|
||||
)
|
||||
);
|
||||
const fileResults = searchFiles(searchPhrase, files);
|
||||
options.push(
|
||||
...fileResults.map((file) => ({
|
||||
type:
|
||||
file.type === FILE_TYPE.IMAGE
|
||||
? SuggestionType.IMAGE
|
||||
: SuggestionType.VIDEO,
|
||||
value: file.index,
|
||||
label: file.title,
|
||||
}))
|
||||
);
|
||||
|
||||
const locationResults = await searchLocation(searchPhrase);
|
||||
|
||||
const locationResultsHasFiles: boolean[] = new Array(
|
||||
locationResults.length
|
||||
).fill(false);
|
||||
files.map((file) => {
|
||||
for (const [index, location] of locationResults.entries()) {
|
||||
if (
|
||||
isInsideBox(
|
||||
{
|
||||
latitude: file.metadata.latitude,
|
||||
longitude: file.metadata.longitude,
|
||||
},
|
||||
location.bbox
|
||||
)
|
||||
) {
|
||||
locationResultsHasFiles[index] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
const filteredLocationWithFiles = locationResults.filter(
|
||||
(_, index) => locationResultsHasFiles[index]
|
||||
);
|
||||
options.push(
|
||||
...filteredLocationWithFiles.map(
|
||||
(searchResult) =>
|
||||
({
|
||||
type: SuggestionType.LOCATION,
|
||||
value: searchResult.bbox,
|
||||
label: searchResult.place,
|
||||
} as Suggestion)
|
||||
)
|
||||
);
|
||||
return options;
|
||||
};
|
||||
|
||||
function parseHumanDate(humanDate: string): DateValue[] {
|
||||
const date = chrono.parseDate(humanDate);
|
||||
const date1 = chrono.parseDate(`${humanDate} 1`);
|
||||
if (date !== null) {
|
||||
|
@ -42,7 +125,7 @@ export function parseHumanDate(humanDate: string): DateValue[] {
|
|||
return [];
|
||||
}
|
||||
|
||||
export async function searchLocation(
|
||||
async function searchLocation(
|
||||
searchPhrase: string
|
||||
): Promise<LocationSearchResponse[]> {
|
||||
try {
|
||||
|
@ -63,7 +146,7 @@ export async function searchLocation(
|
|||
return [];
|
||||
}
|
||||
|
||||
export function getHolidaySuggestion(searchPhrase: string): Suggestion[] {
|
||||
function getHolidaySuggestion(searchPhrase: string): Suggestion[] {
|
||||
return [
|
||||
{
|
||||
label: 'Christmas',
|
||||
|
@ -90,7 +173,7 @@ export function getHolidaySuggestion(searchPhrase: string): Suggestion[] {
|
|||
);
|
||||
}
|
||||
|
||||
export function getYearSuggestion(searchPhrase: string): Suggestion[] {
|
||||
function getYearSuggestion(searchPhrase: string): Suggestion[] {
|
||||
if (searchPhrase.length === 4) {
|
||||
try {
|
||||
const year = parseInt(searchPhrase);
|
||||
|
@ -110,7 +193,7 @@ export function getYearSuggestion(searchPhrase: string): Suggestion[] {
|
|||
return [];
|
||||
}
|
||||
|
||||
export function searchCollection(
|
||||
function searchCollection(
|
||||
searchPhrase: string,
|
||||
collections: Collection[]
|
||||
): Collection[] {
|
||||
|
@ -119,7 +202,7 @@ export function searchCollection(
|
|||
);
|
||||
}
|
||||
|
||||
export function searchFiles(searchPhrase: string, files: EnteFile[]) {
|
||||
function searchFiles(searchPhrase: string, files: EnteFile[]) {
|
||||
return files
|
||||
.map((file, idx) => ({
|
||||
title: file.metadata.title,
|
||||
|
|
|
@ -11,6 +11,7 @@ export type SetFiles = React.Dispatch<React.SetStateAction<EnteFile[]>>;
|
|||
export type SetCollections = React.Dispatch<React.SetStateAction<Collection[]>>;
|
||||
export type SetLoading = React.Dispatch<React.SetStateAction<Boolean>>;
|
||||
export type SetSearchStats = React.Dispatch<React.SetStateAction<SearchStats>>;
|
||||
export type SetSearch = React.Dispatch<React.SetStateAction<Search>>;
|
||||
|
||||
export type Search = {
|
||||
date?: DateValue;
|
||||
|
|
Loading…
Reference in a new issue