mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 13:23:28 +00:00
[MOB-79] Categories search redirect (#2443)
Support clicking on categories to redirect to search
This commit is contained in:
parent
8fd00851ea
commit
da9fb959e8
|
@ -1,8 +1,10 @@
|
|||
import { formatNumber } from '@sd/client';
|
||||
import { Pressable, Text, View } from 'react-native';
|
||||
import { ClassInput } from 'twrnc';
|
||||
import { formatNumber } from '@sd/client';
|
||||
import { tw, twStyle } from '~/lib/tailwind';
|
||||
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useSearchStore } from '~/stores/searchStore';
|
||||
import { Icon, IconName } from '../icons/Icon';
|
||||
|
||||
interface CategoryItemProps {
|
||||
|
@ -16,7 +18,9 @@ interface CategoryItemProps {
|
|||
style?: ClassInput;
|
||||
}
|
||||
|
||||
const CategoryItem = ({ name, icon, items, style }: CategoryItemProps) => {
|
||||
const CategoryItem = ({ name, icon, items, style, kind }: CategoryItemProps) => {
|
||||
const navigation = useNavigation();
|
||||
const searchStore = useSearchStore();
|
||||
return (
|
||||
<Pressable
|
||||
style={twStyle(
|
||||
|
@ -25,7 +29,14 @@ const CategoryItem = ({ name, icon, items, style }: CategoryItemProps) => {
|
|||
style
|
||||
)}
|
||||
onPress={() => {
|
||||
//TODO: implement
|
||||
searchStore.updateFilters('kind', {
|
||||
name,
|
||||
icon: icon + '20' as IconName,
|
||||
id: kind
|
||||
}, true);
|
||||
navigation.navigate('SearchStack', {
|
||||
screen: 'Search',
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Icon name={icon} size={56} />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SearchFilterArgs } from '@sd/client';
|
||||
import { SearchFilterArgs, useLibraryQuery } from '@sd/client';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Filters, SearchFilters, getSearchStore, useSearchStore } from '~/stores/searchStore';
|
||||
|
||||
|
@ -6,11 +6,19 @@ import { Filters, SearchFilters, getSearchStore, useSearchStore } from '~/stores
|
|||
* This hook merges the selected filters from Filters page in order
|
||||
* to make query calls for saved searches and setups filters for the search
|
||||
* the data structure has been designed to match the desktop app
|
||||
* @param search - search input string value
|
||||
*/
|
||||
|
||||
export function useFiltersSearch() {
|
||||
const searchStore = useSearchStore();;
|
||||
|
||||
export function useFiltersSearch(search: string) {
|
||||
|
||||
const [name, ext] = useMemo(() => search.split('.'), [search]);
|
||||
const searchStore = useSearchStore();
|
||||
|
||||
const locations = useLibraryQuery(['locations.list'], {
|
||||
keepPreviousData: true,
|
||||
enabled: (name || ext) ? true : false,
|
||||
});
|
||||
|
||||
const filterFactory = (key: SearchFilters, value: Filters[keyof Filters]) => {
|
||||
|
||||
|
@ -51,6 +59,17 @@ export function useFiltersSearch() {
|
|||
|
||||
const filters = [] as SearchFilterArgs[];
|
||||
|
||||
//It's a global search if no locations have been selected
|
||||
if (searchStore.filters.locations.length === 0 || !name || !ext) {
|
||||
const locationIds = locations.data?.map((l) => l.id);
|
||||
if (locationIds) filters.push({ filePath: { locations: { in: locationIds } } });
|
||||
}
|
||||
|
||||
//handle search input
|
||||
if (name) filters.push({ filePath: { name: { contains: name } } });
|
||||
if (ext) filters.push({ filePath: { extension: { in: [ext] } } });
|
||||
|
||||
// handle selected filters
|
||||
for (const key in searchStore.filters) {
|
||||
|
||||
const filterKey = key as SearchFilters;
|
||||
|
@ -77,10 +96,10 @@ export function useFiltersSearch() {
|
|||
// makes sure the array is not 2D
|
||||
return filters.flat();
|
||||
|
||||
}, [searchStore.filters]);
|
||||
}, [searchStore.filters, search]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
getSearchStore().mergedFilters = mergedFilters;
|
||||
}, [searchStore.filters]);
|
||||
}, [searchStore.filters, search]);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useLibraryQuery } from '@sd/client';
|
||||
import { useMemo } from 'react';
|
||||
import { FlatList, View } from 'react-native';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { useLibraryQuery } from '@sd/client';
|
||||
import { IconName } from '~/components/icons/Icon';
|
||||
import ScreenContainer from '~/components/layout/ScreenContainer';
|
||||
import CategoryItem from '~/components/overview/CategoryItem';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useIsFocused } from '@react-navigation/native';
|
||||
import { SearchFilterArgs, useLibraryQuery, usePathsExplorerQuery } from '@sd/client';
|
||||
import { ArrowLeft, DotsThreeOutline, FunnelSimple, MagnifyingGlass } from 'phosphor-react-native';
|
||||
import { Suspense, useDeferredValue, useMemo, useState } from 'react';
|
||||
import { usePathsExplorerQuery } from '@sd/client';
|
||||
import { ArrowLeft, DotsThreeOutline, FunnelSimple } from 'phosphor-react-native';
|
||||
import { Suspense, useDeferredValue, useState } from 'react';
|
||||
import { ActivityIndicator, Platform, Pressable, TextInput, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import Explorer from '~/components/explorer/Explorer';
|
||||
|
@ -15,42 +15,21 @@ import { useSearchStore } from '~/stores/searchStore';
|
|||
|
||||
const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => {
|
||||
const headerHeight = useSafeAreaInsets().top;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const searchStore = useSearchStore();
|
||||
const explorerStore = useExplorerStore();
|
||||
const isFocused = useIsFocused();
|
||||
const appliedFiltersLength = Object.keys(searchStore.appliedFilters).length;
|
||||
const isAndroid = Platform.OS === 'android';
|
||||
const locations = useLibraryQuery(['locations.list'], {
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const deferredSearch = useDeferredValue(search);
|
||||
|
||||
const filters = useMemo(() => {
|
||||
const [name, ext] = deferredSearch.split('.');
|
||||
|
||||
const filters: SearchFilterArgs[] = [];
|
||||
|
||||
if (name) filters.push({ filePath: { name: { contains: name } } });
|
||||
if (ext) filters.push({ filePath: { extension: { in: [ext] } } });
|
||||
|
||||
if (name || ext) {
|
||||
// Add locations filter to search all locations
|
||||
if (locations.data && locations.data.length > 0) filters.push({ filePath: { locations: { in:
|
||||
locations.data?.map((location) => location.id) } } });
|
||||
}
|
||||
|
||||
return searchStore.mergedFilters.concat(filters);
|
||||
}, [deferredSearch, searchStore.mergedFilters, locations.data]);
|
||||
const appliedFiltersLength = Object.keys(searchStore.appliedFilters).length;
|
||||
const isAndroid = Platform.OS === 'android';
|
||||
|
||||
const objects = usePathsExplorerQuery({
|
||||
arg: {
|
||||
take: 30,
|
||||
filters
|
||||
filters: searchStore.mergedFilters,
|
||||
},
|
||||
enabled: isFocused && filters.length > 1, // only fetch when screen is focused & filters are applied
|
||||
enabled: isFocused && searchStore.mergedFilters.length > 1, // only fetch when screen is focused & filters are applied
|
||||
suspense: true,
|
||||
order: null,
|
||||
onSuccess: () => getExplorerStore().resetNewThumbnails()
|
||||
|
@ -60,7 +39,7 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => {
|
|||
const noObjects = objects.items?.length === 0 || !objects.items;
|
||||
const noSearch = deferredSearch.length === 0 && appliedFiltersLength === 0;
|
||||
|
||||
useFiltersSearch();
|
||||
useFiltersSearch(deferredSearch);
|
||||
|
||||
return (
|
||||
<View
|
||||
|
@ -87,17 +66,6 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => {
|
|||
style={tw`h-10 w-4/5 flex-wrap rounded-md border border-app-inputborder bg-app-input`}
|
||||
>
|
||||
<View style={tw`flex h-full flex-row items-center px-3`}>
|
||||
<View style={tw`mr-3`}>
|
||||
{loading ? (
|
||||
<ActivityIndicator size={'small'} color={'white'} />
|
||||
) : (
|
||||
<MagnifyingGlass
|
||||
size={20}
|
||||
weight="bold"
|
||||
color={tw.color('ink-dull')}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<TextInput
|
||||
value={search}
|
||||
onChangeText={(t) => setSearch(t)}
|
||||
|
|
|
@ -76,7 +76,8 @@ const searchStore = proxy<
|
|||
State & {
|
||||
updateFilters: <K extends keyof State['filters']>(
|
||||
filter: K,
|
||||
value: State['filters'][K] extends Array<infer U> ? U : State['filters'][K]
|
||||
value: State['filters'][K] extends Array<infer U> ? U : State['filters'][K],
|
||||
apply?: boolean
|
||||
) => void;
|
||||
applyFilters: () => void;
|
||||
setSearch: (search: string) => void;
|
||||
|
@ -89,7 +90,7 @@ const searchStore = proxy<
|
|||
>({
|
||||
...initialState,
|
||||
//for updating the filters upon value selection
|
||||
updateFilters: (filter, value) => {
|
||||
updateFilters: (filter, value, apply = false) => {
|
||||
if (filter === 'hidden') {
|
||||
// Directly assign boolean values without an array operation
|
||||
searchStore.filters['hidden'] = value as boolean;
|
||||
|
@ -107,6 +108,9 @@ const searchStore = proxy<
|
|||
searchStore.filters[filter] = updatedFilter;
|
||||
}
|
||||
}
|
||||
//instead of a useEffect or subscription - we can call applyFilters directly
|
||||
// useful when you want to apply the filters from another screen
|
||||
if (apply) searchStore.applyFilters();
|
||||
},
|
||||
//for clicking add filters and applying the selection
|
||||
applyFilters: () => {
|
||||
|
|
Loading…
Reference in a new issue