setup data pipeline

This commit is contained in:
Abhinav 2022-06-01 15:00:21 +05:30
parent 85c61e84c7
commit 4e0e1e4e19
11 changed files with 177 additions and 83 deletions

View file

@ -6,7 +6,7 @@ import { EnteFile } from 'types/file';
export default function CollectionCard(props: { export default function CollectionCard(props: {
children?: any; children?: any;
latestFile?: EnteFile; latestFile: EnteFile;
onClick: () => void; onClick: () => void;
collectionTile: any; collectionTile: any;
}) { }) {

View file

@ -86,3 +86,9 @@ export const CollectionSelectorTile = styled(AllCollectionTile)`
width: 192px; width: 192px;
margin: 10px; margin: 10px;
`; `;
export const ResultPreviewTile = styled(AllCollectionTile)`
width: 48px;
height: 48px;
border-radius: 4px;
`;

View file

@ -7,7 +7,6 @@ import DownloadManager from 'services/downloadManager';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe'; import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe';
import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
import { formatDateRelative } from 'utils/file'; import { formatDateRelative } from 'utils/file';
import { import {
ALL_SECTION, ALL_SECTION,
@ -17,7 +16,7 @@ import {
import { isSharedFile } from 'utils/file'; import { isSharedFile } from 'utils/file';
import { isPlaybackPossible } from 'utils/photoFrame'; import { isPlaybackPossible } from 'utils/photoFrame';
import { PhotoList } from './PhotoList'; import { PhotoList } from './PhotoList';
import { SetFiles, SelectedState, Search, SetSearchStats } from 'types/gallery'; import { SetFiles, SelectedState, SetSearchStats } from 'types/gallery';
import { FILE_TYPE } from 'constants/file'; import { FILE_TYPE } from 'constants/file';
import PublicCollectionDownloadManager from 'services/publicCollectionDownloadManager'; import PublicCollectionDownloadManager from 'services/publicCollectionDownloadManager';
import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery'; import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
@ -26,6 +25,8 @@ import EmptyScreen from './EmptyScreen';
import { AppContext } from 'pages/_app'; import { AppContext } from 'pages/_app';
import { DeduplicateContext } from 'pages/deduplicate'; import { DeduplicateContext } from 'pages/deduplicate';
import { IsArchived } from 'utils/magicMetadata'; import { IsArchived } from 'utils/magicMetadata';
import { isSameDayAnyYear, isInsideBox } from 'utils/search';
import { Search } from 'types/search';
const Container = styled.div` const Container = styled.div`
display: block; display: block;
@ -99,7 +100,7 @@ const PhotoFrame = ({
const [rangeStart, setRangeStart] = useState(null); const [rangeStart, setRangeStart] = useState(null);
const [currentHover, setCurrentHover] = useState(null); const [currentHover, setCurrentHover] = useState(null);
const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false); const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false);
const filteredDataRef = useRef([]); const filteredDataRef = useRef<EnteFile[]>([]);
const filteredData = filteredDataRef?.current ?? []; const filteredData = filteredDataRef?.current ?? [];
const router = useRouter(); const router = useRouter();
const [isSourceLoaded, setIsSourceLoaded] = useState(false); const [isSourceLoaded, setIsSourceLoaded] = useState(false);
@ -141,9 +142,9 @@ const PhotoFrame = ({
timeTaken: (Date.now() - startTime) / 1000, timeTaken: (Date.now() - startTime) / 1000,
}); });
} }
if (search?.fileIndex || search?.fileIndex === 0) { if (search?.file || search?.file === 0) {
const filteredDataIdx = filteredData.findIndex( const filteredDataIdx = filteredData.findIndex(
(data) => data.dataIndex === search.fileIndex (data) => data.id === search.file
); );
if (filteredDataIdx || filteredDataIdx === 0) { if (filteredDataIdx || filteredDataIdx === 0) {
onThumbnailClick(filteredDataIdx)(); onThumbnailClick(filteredDataIdx)();

View file

@ -5,11 +5,12 @@ import ImageIcon from 'components/icons/ImageIcon';
import LocationIcon from 'components/icons/LocationIcon'; import LocationIcon from 'components/icons/LocationIcon';
import VideoIcon from 'components/icons/VideoIcon'; import VideoIcon from 'components/icons/VideoIcon';
import { components } from 'react-select'; import { components } from 'react-select';
import { SuggestionType } from 'types/search'; import { SearchOption, SuggestionType } from 'types/search';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import { Box, Divider, Stack, Typography } from '@mui/material'; import { Box, Divider, Stack, Typography } from '@mui/material';
import { FreeFlowText, SpaceBetweenFlex } from 'components/Container'; import { FreeFlowText, SpaceBetweenFlex } from 'components/Container';
import { PreviewResultImages } from './styledComponents'; import CollectionCard from 'components/Collections/CollectionCard';
import { ResultPreviewTile } from 'components/Collections/styledComponents';
const { Option, Control } = components; const { Option, Control } = components;
@ -32,7 +33,7 @@ const getIconByType = (type: SuggestionType) => {
export const OptionWithIcon = (props) => ( export const OptionWithIcon = (props) => (
<Option {...props}> <Option {...props}>
<LabelWithIcon type={props.data.type} label={props.data.label} /> <LabelWithIcon data={props.data} />
</Option> </Option>
); );
export const ControlWithIcon = (props) => ( export const ControlWithIcon = (props) => (
@ -49,7 +50,7 @@ export const ControlWithIcon = (props) => (
</Control> </Control>
); );
const LabelWithIcon = (props: { type: SuggestionType; label: string }) => ( const LabelWithIcon = ({ data }: { data: SearchOption }) => (
<> <>
<Box className="main" px={2} py={1}> <Box className="main" px={2} py={1}>
<Typography <Typography
@ -63,15 +64,22 @@ const LabelWithIcon = (props: { type: SuggestionType; label: string }) => (
<SpaceBetweenFlex> <SpaceBetweenFlex>
<Box mr={1}> <Box mr={1}>
<FreeFlowText> <FreeFlowText>
<Typography>{props.label}</Typography> <Typography>{data.label}</Typography>
</FreeFlowText> </FreeFlowText>
<Typography color="text.secondary"> 22 Photos</Typography> <Typography color="text.secondary">
{data.fileCount} Photos
</Typography>
</Box> </Box>
<Stack direction={'row'} spacing={1}> <Stack direction={'row'} spacing={1}>
<PreviewResultImages src="https://images.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500" /> {data.previewFiles.map((file) => (
<PreviewResultImages src="https://images.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500" /> <CollectionCard
<PreviewResultImages src="https://images.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500" /> key={file.id}
latestFile={file}
onClick={() => null}
collectionTile={ResultPreviewTile}
/>
))}
</Stack> </Stack>
</SpaceBetweenFlex> </SpaceBetweenFlex>
</Box> </Box>

View file

@ -30,6 +30,16 @@ export default function SearchInput(props: Iprops) {
setValue(value); setValue(value);
}; };
const [defaultOption, setDefaultOption] = useState<Suggestion[]>(null);
useEffect(() => {
const main = async () => {
const d = await getOptions('photo');
setDefaultOption(d);
};
main();
}, []);
useEffect(() => search(value), [value]); useEffect(() => search(value), [value]);
const resetSearch = () => { const resetSearch = () => {
@ -72,7 +82,7 @@ export default function SearchInput(props: Iprops) {
break; break;
case SuggestionType.IMAGE: case SuggestionType.IMAGE:
case SuggestionType.VIDEO: case SuggestionType.VIDEO:
props.setSearch({ fileIndex: selectedOption.value as number }); props.setSearch({ file: selectedOption.value as number });
setValue(null); setValue(null);
break; break;
} }
@ -92,7 +102,9 @@ export default function SearchInput(props: Iprops) {
isClearable isClearable
escapeClearsValue escapeClearsValue
styles={SelectStyles} styles={SelectStyles}
noOptionsMessage={() => null} // noOptionsMessage={() => null}
defaultOptions={defaultOption}
menuIsOpen
/> />
{props.isOpen && ( {props.isOpen && (

View file

@ -27,8 +27,3 @@ export const SearchInputWrapper = styled(FlexWrapper)`
max-width: 484px; max-width: 484px;
margin: auto; margin: auto;
`; `;
export const PreviewResultImages = styled.img`
width: 48px;
height: 48px;
border-radius: 4px;
`;

View file

@ -34,6 +34,9 @@ export const SelectStyles = {
'& .main': { '& .main': {
backgroundColor: isFocused && '#343434', backgroundColor: isFocused && '#343434',
}, },
'&:last-child .MuiDivider-root': {
display: 'none',
},
}), }),
dropdownIndicator: (style) => ({ dropdownIndicator: (style) => ({
...style, ...style,

View file

@ -7,13 +7,16 @@ import { EnteFile } from 'types/file';
import { logError } from 'utils/sentry'; import { logError } from 'utils/sentry';
import { import {
Bbox,
DateValue, DateValue,
LocationSearchResponse, LocationSearchResponse,
Search,
SearchOption,
Suggestion, Suggestion,
SuggestionType, SuggestionType,
} from 'types/search'; } from 'types/search';
import { FILE_TYPE } from 'constants/file'; import { FILE_TYPE } from 'constants/file';
import { getFormattedDate, isInsideBox } from 'utils/search'; import { getFormattedDate, isInsideBox, isSameDayAnyYear } from 'utils/search';
const ENDPOINT = getEndpoint(); const ENDPOINT = getEndpoint();
@ -26,16 +29,35 @@ export const getAutoCompleteSuggestions =
if (!searchPhrase?.length) { if (!searchPhrase?.length) {
return []; return [];
} }
const options = [ const suggestions = [
...getHolidaySuggestion(searchPhrase), ...getHolidaySuggestion(searchPhrase),
...getYearSuggestion(searchPhrase), ...getYearSuggestion(searchPhrase),
...getDateSuggestion(searchPhrase), ...getDateSuggestion(searchPhrase),
...getCollectionSuggestion(searchPhrase, collections), ...getCollectionSuggestion(searchPhrase, collections),
...getFileSuggestion(searchPhrase, files), ...getFileSuggestion(searchPhrase, files),
...(await getLocationSuggestions(searchPhrase, files)), ...(await getLocationSuggestions(searchPhrase /* files*/)),
]; ];
return options; console.log(suggestions);
const previewImageAppendedOptions: SearchOption[] = suggestions
.map((suggestion) => ({
suggestion,
searchQuery: convertSuggestionToSearchQuery(suggestion),
}))
.map(({ suggestion, searchQuery }) => {
const resultFiles = files.filter((file) =>
isSearchedFiles(file, searchQuery)
);
return {
...suggestion,
fileCount: resultFiles.length,
previewFiles: resultFiles.slice(0, 3),
};
})
.filter((option) => option.fileCount);
return previewImageAppendedOptions;
}; };
function getHolidaySuggestion(searchPhrase: string): Suggestion[] { function getHolidaySuggestion(searchPhrase: string): Suggestion[] {
@ -135,6 +157,7 @@ function getCollectionSuggestion(
function getFileSuggestion(searchPhrase: string, files: EnteFile[]) { function getFileSuggestion(searchPhrase: string, files: EnteFile[]) {
const fileResults = searchFiles(searchPhrase, files); const fileResults = searchFiles(searchPhrase, files);
console.log(fileResults);
return fileResults.map((file) => ({ return fileResults.map((file) => ({
type: type:
file.type === FILE_TYPE.IMAGE file.type === FILE_TYPE.IMAGE
@ -145,31 +168,33 @@ function getFileSuggestion(searchPhrase: string, files: EnteFile[]) {
})); }));
} }
async function getLocationSuggestions(searchPhrase: string, files: EnteFile[]) { async function getLocationSuggestions(
searchPhrase: string /* files: EnteFile[]*/
) {
const locationResults = await searchLocation(searchPhrase); const locationResults = await searchLocation(searchPhrase);
const locationResultsHasFiles: boolean[] = new Array( // const locationResultsHasFiles: boolean[] = new Array(
locationResults.length // locationResults.length
).fill(false); // ).fill(false);
files.map((file) => { // files.map((file) => {
for (const [index, location] of locationResults.entries()) { // for (const [index, location] of locationResults.entries()) {
if ( // if (
isInsideBox( // isInsideBox(
{ // {
latitude: file.metadata.latitude, // latitude: file.metadata.latitude,
longitude: file.metadata.longitude, // longitude: file.metadata.longitude,
}, // },
location.bbox // location.bbox
) // )
) { // ) {
locationResultsHasFiles[index] = true; // locationResultsHasFiles[index] = true;
} // }
} // }
}); // });
const filteredLocationWithFiles = locationResults.filter( // const filteredLocationWithFiles = locationResults.filter(
(_, index) => locationResultsHasFiles[index] // (_, index) => locationResultsHasFiles[index]
); // );
return filteredLocationWithFiles.map( return locationResults.map(
(searchResult) => (searchResult) =>
({ ({
type: SuggestionType.LOCATION, type: SuggestionType.LOCATION,
@ -223,3 +248,48 @@ async function searchLocation(
} }
return []; return [];
} }
export function isSearchedFiles(file: EnteFile, search: Search) {
if (search?.date) {
return isSameDayAnyYear(search.date)(
new Date(file.metadata.creationTime / 1000)
);
}
if (search?.location) {
return isInsideBox(
{
latitude: file.metadata.latitude,
longitude: file.metadata.longitude,
},
search.location
);
}
if (search?.file) {
return file.id === search.file;
}
if (search?.collection) {
return search.collection === file.collectionID;
}
return false;
}
function convertSuggestionToSearchQuery(option: Suggestion): Search {
switch (option.type) {
case SuggestionType.DATE:
return {
date: option.value as DateValue,
};
case SuggestionType.LOCATION:
return {
location: option.value as Bbox,
};
case SuggestionType.COLLECTION:
return { collection: option.value as number };
case SuggestionType.IMAGE:
case SuggestionType.VIDEO:
return { file: option.value as number };
}
}

View file

@ -1,6 +1,6 @@
import { Collection } from 'types/collection'; import { Collection } from 'types/collection';
import { EnteFile } from 'types/file'; import { EnteFile } from 'types/file';
import { DateValue, Bbox } from 'types/search'; import { Search, SearchStats } from 'types/search';
export type SelectedState = { export type SelectedState = {
[k: number]: boolean; [k: number]: boolean;
@ -13,16 +13,6 @@ export type SetLoading = React.Dispatch<React.SetStateAction<Boolean>>;
export type SetSearchStats = React.Dispatch<React.SetStateAction<SearchStats>>; export type SetSearchStats = React.Dispatch<React.SetStateAction<SearchStats>>;
export type SetSearch = React.Dispatch<React.SetStateAction<Search>>; export type SetSearch = React.Dispatch<React.SetStateAction<Search>>;
export type Search = {
date?: DateValue;
location?: Bbox;
fileIndex?: number;
};
export interface SearchStats {
resultCount: number;
timeTaken: number;
}
export type GalleryContextType = { export type GalleryContextType = {
thumbs: Map<number, string>; thumbs: Map<number, string>;
files: Map<number, string>; files: Map<number, string>;

View file

@ -1,3 +1,5 @@
import { EnteFile } from 'types/file';
export type Bbox = [number, number, number, number]; export type Bbox = [number, number, number, number];
export interface LocationSearchResponse { export interface LocationSearchResponse {
@ -24,3 +26,19 @@ export interface Suggestion {
label: string; label: string;
value: Bbox | DateValue | number; value: Bbox | DateValue | number;
} }
export type Search = {
date?: DateValue;
location?: Bbox;
collection?: number;
file?: number;
};
export interface SearchStats {
resultCount: number;
timeTaken: number;
}
export interface SearchOption extends Suggestion {
fileCount: number;
previewFiles: EnteFile[];
}

View file

@ -1,4 +1,3 @@
import { EnteFile } from 'types/file';
import { Bbox, DateValue } from 'types/search'; import { Bbox, DateValue } from 'types/search';
import { Location } from 'types/upload'; import { Location } from 'types/upload';
@ -16,7 +15,8 @@ export function isInsideBox({ latitude, longitude }: Location, bbox: Bbox) {
} }
} }
export const isSameDay = (baseDate: DateValue) => (compareDate: Date) => { export const isSameDayAnyYear =
(baseDate: DateValue) => (compareDate: Date) => {
let same = true; let same = true;
if (baseDate.month || baseDate.month === 0) { if (baseDate.month || baseDate.month === 0) {
@ -32,15 +32,6 @@ export const isSameDay = (baseDate: DateValue) => (compareDate: Date) => {
return same; return same;
}; };
export function getFilesWithCreationDay(
files: EnteFile[],
searchedDate: DateValue
) {
const isSearchedDate = isSameDay(searchedDate);
return files.filter((file) =>
isSearchedDate(new Date(file.metadata.creationTime / 1000))
);
}
export function getFormattedDate(date: DateValue) { export function getFormattedDate(date: DateValue) {
const options = {}; const options = {};
date.date && (options['day'] = 'numeric'); date.date && (options['day'] = 'numeric');