WiP
This commit is contained in:
parent
49ec70f5cf
commit
94d6d34625
7 changed files with 113 additions and 29 deletions
|
@ -30,6 +30,7 @@ import { LocationTagData } from 'types/entity';
|
|||
import { FILE_TYPE } from 'constants/file';
|
||||
import { InputActionMeta } from 'react-select/src/types';
|
||||
import { components } from 'react-select';
|
||||
import { City } from 'services/locationSearchService';
|
||||
|
||||
interface Iprops {
|
||||
isOpen: boolean;
|
||||
|
@ -122,6 +123,12 @@ export default function SearchInput(props: Iprops) {
|
|||
};
|
||||
props.setIsOpen(true);
|
||||
break;
|
||||
case SuggestionType.CITY:
|
||||
search = {
|
||||
city: selectedOption.value as City,
|
||||
};
|
||||
props.setIsOpen(true);
|
||||
break;
|
||||
case SuggestionType.COLLECTION:
|
||||
search = { collection: selectedOption.value as number };
|
||||
setValue(null);
|
||||
|
|
|
@ -17,6 +17,7 @@ const getIconByType = (type: SuggestionType) => {
|
|||
case SuggestionType.DATE:
|
||||
return <CalendarIcon />;
|
||||
case SuggestionType.LOCATION:
|
||||
case SuggestionType.CITY:
|
||||
return <LocationIcon />;
|
||||
case SuggestionType.COLLECTION:
|
||||
return <FolderIcon />;
|
||||
|
|
|
@ -120,7 +120,7 @@ import GalleryEmptyState from 'components/GalleryEmptyState';
|
|||
import AuthenticateUserModal from 'components/AuthenticateUserModal';
|
||||
import useMemoSingleThreaded from '@ente/shared/hooks/useMemoSingleThreaded';
|
||||
import { isArchivedFile } from 'utils/magicMetadata';
|
||||
import { isSameDayAnyYear, isInsideLocationTag } from 'utils/search';
|
||||
import { isSameDayAnyYear } from 'utils/search';
|
||||
import { getSessionExpiredMessage } from 'utils/ui';
|
||||
import { syncEntities } from 'services/entityService';
|
||||
import { constructUserIDToEmailMap } from 'services/collectionService';
|
||||
|
@ -131,7 +131,10 @@ import { ClipService } from 'services/clipService';
|
|||
import isElectron from 'is-electron';
|
||||
import downloadManager from 'services/download';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import locationSearchService from 'services/locationSearchService';
|
||||
import locationSearchService, {
|
||||
isInsideCity,
|
||||
isInsideLocationTag,
|
||||
} from 'services/locationSearchService';
|
||||
|
||||
export const DeadCenter = styled('div')`
|
||||
flex: 1;
|
||||
|
@ -518,6 +521,18 @@ export default function Gallery() {
|
|||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
search?.city &&
|
||||
!isInsideCity(
|
||||
{
|
||||
latitude: item.metadata.latitude,
|
||||
longitude: item.metadata.longitude,
|
||||
},
|
||||
search.city
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
search?.person &&
|
||||
search.person.files.indexOf(item.id) === -1
|
||||
|
|
|
@ -1,23 +1,68 @@
|
|||
import { CITIES_URL } from '@ente/shared/constants/urls';
|
||||
import { LocationTagData } from 'types/entity';
|
||||
import { Location } from 'types/upload';
|
||||
|
||||
interface City {
|
||||
export interface City {
|
||||
city: string;
|
||||
country: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
|
||||
const DEFAULT_CITY_RADIUS = 10;
|
||||
|
||||
class LocationSearchService {
|
||||
private cities: Array<City> = [];
|
||||
private citiesPromise: Promise<void>;
|
||||
|
||||
loadCities() {
|
||||
fetch(CITIES_URL).then((response) => {
|
||||
response.json().then((data) => {
|
||||
this.cities = data;
|
||||
console.log(this.cities);
|
||||
if (this.citiesPromise) {
|
||||
return;
|
||||
}
|
||||
this.citiesPromise = fetch(CITIES_URL).then((response) => {
|
||||
return response.json().then((data) => {
|
||||
this.cities = data['data'];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async searchCities(searchTerm: string) {
|
||||
if (!this.citiesPromise) {
|
||||
this.loadCities();
|
||||
}
|
||||
await this.citiesPromise;
|
||||
return this.cities.filter((city) => {
|
||||
return city.city.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new LocationSearchService();
|
||||
|
||||
export function isInsideLocationTag(
|
||||
location: Location,
|
||||
locationTag: LocationTagData
|
||||
) {
|
||||
const { centerPoint, aSquare, bSquare } = locationTag;
|
||||
const { latitude, longitude } = location;
|
||||
const x = Math.abs(centerPoint.latitude - latitude);
|
||||
const y = Math.abs(centerPoint.longitude - longitude);
|
||||
if ((x * x) / aSquare + (y * y) / bSquare <= 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Verify correctness
|
||||
export function isInsideCity(location: Location, city: City) {
|
||||
const { lat, lng } = city;
|
||||
const { latitude, longitude } = location;
|
||||
const x = Math.abs(lat - latitude);
|
||||
const y = Math.abs(lng - longitude);
|
||||
if (x * x + y * y <= DEFAULT_CITY_RADIUS * DEFAULT_CITY_RADIUS) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,7 @@ import {
|
|||
ClipSearchScores,
|
||||
} from 'types/search';
|
||||
import ObjectService from './machineLearning/objectService';
|
||||
import {
|
||||
getFormattedDate,
|
||||
isInsideLocationTag,
|
||||
isSameDayAnyYear,
|
||||
} from 'utils/search';
|
||||
import { getFormattedDate, isSameDayAnyYear } from 'utils/search';
|
||||
import { Person, Thing } from 'types/machineLearning';
|
||||
import { getUniqueFiles } from 'utils/file';
|
||||
import { getLatestEntities } from './entityService';
|
||||
|
@ -31,6 +27,11 @@ import { ClipService, computeClipMatchScore } from './clipService';
|
|||
import { CustomError } from '@ente/shared/error';
|
||||
import { Model } from 'types/embedding';
|
||||
import { getLocalEmbeddings } from './embeddingService';
|
||||
import locationSearchService, {
|
||||
City,
|
||||
isInsideCity,
|
||||
isInsideLocationTag,
|
||||
} from './locationSearchService';
|
||||
|
||||
const DIGITS = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
|
||||
|
||||
|
@ -61,6 +62,7 @@ export const getAutoCompleteSuggestions =
|
|||
getFileNameSuggestion(searchPhrase, files),
|
||||
getFileCaptionSuggestion(searchPhrase, files),
|
||||
...(await getLocationTagSuggestions(searchPhrase)),
|
||||
...(await getCitySuggestions(searchPhrase)),
|
||||
...(await getThingSuggestion(searchPhrase)),
|
||||
].filter((suggestion) => !!suggestion);
|
||||
|
||||
|
@ -279,6 +281,21 @@ async function getLocationTagSuggestions(searchPhrase: string) {
|
|||
);
|
||||
}
|
||||
|
||||
async function getCitySuggestions(searchPhrase: string) {
|
||||
const searchResults = await locationSearchService.searchCities(
|
||||
searchPhrase
|
||||
);
|
||||
|
||||
return searchResults.map(
|
||||
(city) =>
|
||||
({
|
||||
type: SuggestionType.CITY,
|
||||
value: city,
|
||||
label: city.city,
|
||||
} as Suggestion)
|
||||
);
|
||||
}
|
||||
|
||||
async function getThingSuggestion(searchPhrase: string): Promise<Suggestion[]> {
|
||||
const thingResults = await searchThing(searchPhrase);
|
||||
|
||||
|
@ -425,6 +442,15 @@ function isSearchedFile(file: EnteFile, search: Search) {
|
|||
search.location
|
||||
);
|
||||
}
|
||||
if (search?.city) {
|
||||
return isInsideCity(
|
||||
{
|
||||
latitude: file.metadata.latitude,
|
||||
longitude: file.metadata.longitude,
|
||||
},
|
||||
search.city
|
||||
);
|
||||
}
|
||||
if (search?.files) {
|
||||
return search.files.indexOf(file.id) !== -1;
|
||||
}
|
||||
|
@ -460,6 +486,9 @@ function convertSuggestionToSearchQuery(option: Suggestion): Search {
|
|||
location: option.value as LocationTagData,
|
||||
};
|
||||
|
||||
case SuggestionType.CITY:
|
||||
return { city: option.value as City };
|
||||
|
||||
case SuggestionType.COLLECTION:
|
||||
return { collection: option.value as number };
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { IndexStatus } from 'types/machineLearning/ui';
|
|||
import { EnteFile } from 'types/file';
|
||||
import { LocationTagData } from 'types/entity';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { City } from 'services/locationSearchService';
|
||||
|
||||
export enum SuggestionType {
|
||||
DATE = 'DATE',
|
||||
|
@ -16,6 +17,7 @@ export enum SuggestionType {
|
|||
FILE_CAPTION = 'FILE_CAPTION',
|
||||
FILE_TYPE = 'FILE_TYPE',
|
||||
CLIP = 'CLIP',
|
||||
CITY = 'CITY',
|
||||
}
|
||||
|
||||
export interface DateValue {
|
||||
|
@ -35,6 +37,7 @@ export interface Suggestion {
|
|||
| Thing
|
||||
| WordGroup
|
||||
| LocationTagData
|
||||
| City
|
||||
| FILE_TYPE
|
||||
| ClipSearchScores;
|
||||
hide?: boolean;
|
||||
|
@ -43,6 +46,7 @@ export interface Suggestion {
|
|||
export type Search = {
|
||||
date?: DateValue;
|
||||
location?: LocationTagData;
|
||||
city?: City;
|
||||
collection?: number;
|
||||
files?: number[];
|
||||
person?: Person;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { LocationTagData } from 'types/entity';
|
||||
import { DateValue } from 'types/search';
|
||||
import { Location } from 'types/upload';
|
||||
|
||||
export const isSameDayAnyYear =
|
||||
(baseDate: DateValue) => (compareDate: Date) => {
|
||||
|
@ -28,18 +26,3 @@ export function getFormattedDate(date: DateValue) {
|
|||
new Date(date.year ?? 1, date.month ?? 1, date.date ?? 1)
|
||||
);
|
||||
}
|
||||
|
||||
export function isInsideLocationTag(
|
||||
location: Location,
|
||||
locationTag: LocationTagData
|
||||
) {
|
||||
const { centerPoint, aSquare, bSquare } = locationTag;
|
||||
const { latitude, longitude } = location;
|
||||
const x = Math.abs(centerPoint.latitude - latitude);
|
||||
const y = Math.abs(centerPoint.longitude - longitude);
|
||||
if ((x * x) / aSquare + (y * y) / bSquare <= 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue