[MOB-99] Job remove & more (#2514)

* Clear job and improve job manager design

* pullToRefresh, search adjustment, and more

* ts

* use rspc instead of query client

* Update JobManagerModal.tsx
This commit is contained in:
ameer2468 2024-05-28 22:02:50 +01:00 committed by GitHub
parent d64b21357b
commit 530e3c8ac8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 121 additions and 71 deletions

View file

@ -25,6 +25,7 @@ const SortByMenu = () => {
return ( return (
<View style={tw`flex-row items-center gap-1.5`}> <View style={tw`flex-row items-center gap-1.5`}>
<Menu <Menu
containerStyle={tw`max-w-44`}
trigger={<Trigger activeOption={sortOptions[searchStore.sort.by]} />} trigger={<Trigger activeOption={sortOptions[searchStore.sort.by]} />}
> >
@ -40,6 +41,7 @@ const SortByMenu = () => {
))} ))}
</Menu> </Menu>
<Menu <Menu
containerStyle={tw`max-w-40`}
trigger={<Trigger trigger={<Trigger
triggerIcon={searchStore.sort.direction === 'Asc' ? ArrowUpIcon : ArrowDownIcon} triggerIcon={searchStore.sort.direction === 'Asc' ? ArrowUpIcon : ArrowDownIcon}
activeOption={searchStore.sort.direction} activeOption={searchStore.sort.direction}

View file

@ -48,7 +48,7 @@ export default function Header({ route, navBack, title, search = false }: Props)
}} }}
> >
<MagnifyingGlass <MagnifyingGlass
size={24} size={20}
weight="bold" weight="bold"
color={tw.color('text-zinc-300')} color={tw.color('text-zinc-300')}
/> />

View file

@ -1,8 +1,8 @@
import { TextItems } from '@sd/client';
import { Image } from 'expo-image'; import { Image } from 'expo-image';
import { Icon } from 'phosphor-react-native'; import { Icon } from 'phosphor-react-native';
import { Fragment } from 'react'; import { Fragment } from 'react';
import { Text, View, ViewStyle } from 'react-native'; import { Text, View, ViewStyle } from 'react-native';
import { TextItems } from '@sd/client';
import { styled, tw, twStyle } from '~/lib/tailwind'; import { styled, tw, twStyle } from '~/lib/tailwind';
type JobContainerProps = { type JobContainerProps = {
@ -25,7 +25,7 @@ export default function JobContainer(props: JobContainerProps) {
<View <View
style={twStyle( style={twStyle(
'flex flex-row justify-center', 'flex flex-row justify-center',
'border-b border-app-line/50 px-8 py-4', 'border-b border-app-line/30 px-8 py-4',
isChild && 'my-1.5 border-b-0 p-2 pl-12', isChild && 'my-1.5 border-b-0 p-2 pl-12',
restProps.containerStyle restProps.containerStyle
)} )}

View file

@ -1,9 +1,4 @@
import { Folder } from '@sd/assets/icons'; import { Folder } from '@sd/assets/icons';
import dayjs from 'dayjs';
import { DotsThreeVertical, Pause, Play, Stop } from 'phosphor-react-native';
import { useMemo, useState } from 'react';
import { Animated, Pressable, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
import { import {
getJobNiceActionName, getJobNiceActionName,
getTotalTasks, getTotalTasks,
@ -11,13 +6,20 @@ import {
JobProgressEvent, JobProgressEvent,
JobReport, JobReport,
useLibraryMutation, useLibraryMutation,
useRspcLibraryContext,
useTotalElapsedTimeText useTotalElapsedTimeText
} from '@sd/client'; } from '@sd/client';
import { tw } from '~/lib/tailwind'; import dayjs from 'dayjs';
import { DotsThreeVertical, Eye, Pause, Play, Stop, Trash } from 'phosphor-react-native';
import { SetStateAction, useMemo, useState } from 'react';
import { Animated, Pressable, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
import { tw, twStyle } from '~/lib/tailwind';
import { AnimatedHeight } from '../animation/layout'; import { AnimatedHeight } from '../animation/layout';
import { ProgressBar } from '../animation/ProgressBar'; import { ProgressBar } from '../animation/ProgressBar';
import { Button } from '../primitive/Button'; import { Button } from '../primitive/Button';
import { Menu, MenuItem } from '../primitive/Menu';
import { toast } from '../primitive/Toast'; import { toast } from '../primitive/Toast';
import Job from './Job'; import Job from './Job';
import JobContainer from './JobContainer'; import JobContainer from './JobContainer';
@ -58,11 +60,11 @@ export default function ({ group, progress }: JobGroupProps) {
return ( return (
<Animated.View <Animated.View
style={[ style={[
tw`flex flex-row items-center pr-4`, tw`mt-5 flex flex-row items-start pr-4`,
{ transform: [{ translateX: translate }] } { transform: [{ translateX: translate }] }
]} ]}
> >
<Options activeJob={runningJob} group={group} /> <Options showChildJobs={showChildJobs} setShowChildJobs={setShowChildJobs} activeJob={runningJob} group={group} />
</Animated.View> </Animated.View>
); );
}; };
@ -158,8 +160,23 @@ const toastErrorSuccess = (
}; };
}; };
function Options({ activeJob, group }: { activeJob?: JobReport; group: JobGroup }) { interface OptionsProps {
// const queryClient = useQueryClient(); activeJob?: JobReport;
group: JobGroup;
showChildJobs: boolean;
setShowChildJobs: React.Dispatch<SetStateAction<boolean>>
}
function Options({ activeJob, group, setShowChildJobs, showChildJobs }: OptionsProps) {
const rspc = useRspcLibraryContext();
const clearJob = useLibraryMutation(
['jobs.clear'], {
onSuccess: () => {
rspc.queryClient.invalidateQueries(['jobs.reports']);
}
})
const resumeJob = useLibraryMutation( const resumeJob = useLibraryMutation(
['jobs.resume'], ['jobs.resume'],
@ -179,32 +196,45 @@ function Options({ activeJob, group }: { activeJob?: JobReport; group: JobGroup
[group.jobs] [group.jobs]
); );
// const clearJob = useLibraryMutation( const clearJobHandler = () => {
// ['jobs.clear'], group.jobs.forEach((job) => {
// toastErrorSuccess('failed_to_remove_job', undefined, () => { clearJob.mutate(job.id);
// queryClient.invalidateQueries(['jobs.reports']); //only one toast for all jobs
// }) if (job.id === group.id)
// ); toast.success('Job has been removed');
});
};
return ( return (
<> <>
{/* Resume */} {/* Resume */}
{(group.status === 'Queued' || group.status === 'Paused' || isJobPaused) && ( {(group.status === 'Queued' || group.status === 'Paused' || isJobPaused) && (
<Button variant="outline" size="sm" onPress={() => resumeJob.mutate(group.id)}> <Button style={tw`h-7 w-7`} variant="outline" size="sm" onPress={() => resumeJob.mutate(group.id)}>
<Play size={18} color="white" /> <Play size={16} color="white" />
</Button> </Button>
)} )}
{/* TODO: This should remove the job from panel */} {/* TODO: This should remove the job from panel */}
{!activeJob !== undefined ? ( {!activeJob !== undefined ? (
<Button variant="outline" size="sm"> <Menu
containerStyle={tw`max-w-25`}
trigger={
<View style={tw`flex h-7 w-7 flex-row items-center justify-center rounded-md border border-app-inputborder`}>
<DotsThreeVertical size={16} color="white" /> <DotsThreeVertical size={16} color="white" />
</Button> </View>
}
>
<MenuItem
style={twStyle(showChildJobs ? 'rounded bg-app-screen/50' : 'bg-transparent')}
onSelect={() => setShowChildJobs(!showChildJobs)}
text="Expand" icon={Eye}/>
<MenuItem onSelect={clearJobHandler} text='Remove' icon={Trash}/>
</Menu>
) : ( ) : (
<View style={tw`flex flex-row gap-2`}> <View style={tw`flex flex-row gap-2`}>
<Button variant="outline" size="sm" onPress={() => pauseJob.mutate(group.id)}> <Button style={tw`h-7 w-7`} variant="outline" size="sm" onPress={() => pauseJob.mutate(group.id)}>
<Pause size={16} color="white" /> <Pause size={16} color="white" />
</Button> </Button>
<Button variant="outline" size="sm" onPress={() => cancelJob.mutate(group.id)}> <Button style={tw`h-7 w-7`} variant="outline" size="sm" onPress={() => cancelJob.mutate(group.id)}>
<Stop size={16} color="white" /> <Stop size={16} color="white" />
</Button> </Button>
</View> </View>

View file

@ -7,14 +7,14 @@ import { Icon, IconName } from '../icons/Icon';
interface Props { interface Props {
description: string; //description of empty state description: string; //description of empty state
icon: IconName; //Spacedrive icon icon?: IconName; //Spacedrive icon
style?: ClassInput; //Tailwind classes style?: ClassInput; //Tailwind classes
iconSize?: number; //Size of the icon iconSize?: number; //Size of the icon
textSize?: ClassInput; //Size of the text textStyle?: ClassInput; //Size of the text
includeHeaderHeight?: boolean; //Height of the header includeHeaderHeight?: boolean; //Height of the header
} }
const Empty = ({ description, icon, style, includeHeaderHeight = false, textSize = 'text-sm', iconSize = 38 }: Props) => { const Empty = ({ description, icon, style, includeHeaderHeight = false, textStyle, iconSize = 38 }: Props) => {
const headerHeight = useSafeAreaInsets().top; const headerHeight = useSafeAreaInsets().top;
return ( return (
<View <View
@ -25,8 +25,8 @@ const Empty = ({ description, icon, style, includeHeaderHeight = false, textSize
style style
)} )}
> >
<Icon name={icon} size={iconSize} /> {icon && <Icon name={icon} size={iconSize} />}
<Text style={twStyle(`mt-2 text-center font-medium text-ink-dull`, textSize)}> <Text style={twStyle(`mt-2 text-center text-sm font-medium text-ink-dull`, textStyle)}>
{description} {description}
</Text> </Text>
</View> </View>

View file

@ -20,7 +20,7 @@ const GridLocation: React.FC<GridLocationProps> = ({ location, modalRef }: GridL
<View style={tw`w-full flex-col justify-between gap-1`}> <View style={tw`w-full flex-col justify-between gap-1`}>
<View style={tw`flex-row items-center justify-between`}> <View style={tw`flex-row items-center justify-between`}>
<View style={tw`relative`}> <View style={tw`relative`}>
<FolderIcon size={42} /> <FolderIcon size={36} />
<View <View
style={twStyle( style={twStyle(
'z-5 absolute bottom-[6px] right-[2px] h-2 w-2 rounded-full', 'z-5 absolute bottom-[6px] right-[2px] h-2 w-2 rounded-full',

View file

@ -27,6 +27,8 @@ const ImportModal = forwardRef<ModalRef, unknown>((_, ref) => {
//custom message handling //custom message handling
if (error.message.startsWith("location already exists")) { if (error.message.startsWith("location already exists")) {
return toast.error('This location has already been added'); return toast.error('This location has already been added');
} else if (error.message.startsWith("nested location currently")) {
return toast.error('Nested locations are currently not supported');
} }
switch (error.message) { switch (error.message) {
case 'NEED_RELINK': case 'NEED_RELINK':

View file

@ -1,40 +1,51 @@
import { forwardRef } from 'react'; import { BottomSheetFlatList } from '@gorhom/bottom-sheet';
import { FlatList, Text, View } from 'react-native';
import { useJobProgress, useLibraryQuery } from '@sd/client'; import { useJobProgress, useLibraryQuery } from '@sd/client';
import { forwardRef, useEffect } from 'react';
import JobGroup from '~/components/job/JobGroup'; import JobGroup from '~/components/job/JobGroup';
import Empty from '~/components/layout/Empty';
import { Modal, ModalRef } from '~/components/layout/Modal'; import { Modal, ModalRef } from '~/components/layout/Modal';
import useForwardedRef from '~/hooks/useForwardedRef';
import { tw } from '~/lib/tailwind'; import { tw } from '~/lib/tailwind';
// TODO: //TODO: Handle data fetching better when modal is opened
// - When there is no job, make modal height smaller
// - Add clear all jobs button
export const JobManagerModal = forwardRef<ModalRef, unknown>((_, ref) => { export const JobManagerModal = forwardRef<ModalRef, unknown>((_, ref) => {
// const queryClient = useQueryClient(); // const rspc = useRspcLibraryContext();
const jobGroups = useLibraryQuery(['jobs.reports']); const jobGroups = useLibraryQuery(['jobs.reports']);
const progress = useJobProgress(jobGroups.data); const progress = useJobProgress(jobGroups.data);
const modalRef = useForwardedRef(ref);
//TODO: Add clear all jobs button
// const clearAllJobs = useLibraryMutation(['jobs.clearAll'], { // const clearAllJobs = useLibraryMutation(['jobs.clearAll'], {
// onError: () => { // onError: () => {
// // TODO: Show error toast // toast.error('Failed to clear all jobs.');
// }, // },
// onSuccess: () => { // onSuccess: () => {
// queryClient.invalidateQueries(['jobs.reports ']); // queryClient.invalidateQueries(['jobs.reports ']);
// } // }
// }); // });
useEffect(() => {
if (jobGroups.data?.length === 0) {
modalRef.current?.snapToPosition('20');
}
}, [jobGroups, modalRef]);
return ( return (
<Modal ref={ref} snapPoints={['60']} title="Recent Jobs" showCloseButton> <Modal
<FlatList ref={modalRef}
snapPoints={['60']}
title="Recent Jobs"
showCloseButton
>
<BottomSheetFlatList
data={jobGroups.data} data={jobGroups.data}
style={tw`flex-1`} style={tw`flex-1`}
keyExtractor={(i) => i.id} keyExtractor={(i) => i.id}
contentContainerStyle={tw`mt-4`} contentContainerStyle={tw`mt-4`}
renderItem={({ item }) => <JobGroup group={item} progress={progress} />} renderItem={({ item }) => <JobGroup group={item} progress={progress} />}
ListEmptyComponent={ ListEmptyComponent={
<View style={tw`flex h-60 items-center justify-center`}> <Empty style="border-0" description='No jobs.'/>
<Text style={tw`text-center text-base text-ink-dull`}>No jobs.</Text>
</View>
} }
/> />
</Modal> </Modal>

View file

@ -11,8 +11,8 @@ const button = cva(['items-center justify-center rounded-md border shadow-sm'],
gray: ['border-app-box bg-app shadow-none'], gray: ['border-app-box bg-app shadow-none'],
darkgray: ['border-app-box bg-app shadow-none'], darkgray: ['border-app-box bg-app shadow-none'],
accent: ['border-accent-deep bg-accent shadow-md shadow-app-shade/10'], accent: ['border-accent-deep bg-accent shadow-md shadow-app-shade/10'],
outline: ['border-app-lightborder bg-black shadow-none'], outline: ['border border-app-inputborder bg-transparent shadow-none'],
transparent: ['border-0 bg-black shadow-none'], transparent: ['border-0 bg-transparent shadow-none'],
dashed: ['border border-dashed border-app-line bg-transparent shadow-none'] dashed: ['border border-dashed border-app-line bg-transparent shadow-none']
}, },
size: { size: {

View file

@ -14,13 +14,14 @@ type MenuProps = {
trigger: React.ReactNode; trigger: React.ReactNode;
children: React.ReactNode[] | React.ReactNode; children: React.ReactNode[] | React.ReactNode;
triggerStyle?: ClassInput; triggerStyle?: ClassInput;
containerStyle?: ClassInput;
}; };
// TODO: Still looks a bit off... // TODO: Still looks a bit off...
export const Menu = (props: MenuProps) => ( export const Menu = (props: MenuProps) => (
<PMenu style={twStyle(props.triggerStyle)}> <PMenu style={twStyle(props.triggerStyle)}>
<MenuTrigger>{props.trigger}</MenuTrigger> <MenuTrigger>{props.trigger}</MenuTrigger>
<MenuOptions optionsContainerStyle={tw`rounded-md border border-app-cardborder bg-app-menu p-1`}> <MenuOptions optionsContainerStyle={twStyle(`rounded-md border border-app-cardborder bg-app-menu p-1`, props.containerStyle)}>
{props.children} {props.children}
</MenuOptions> </MenuOptions>
</PMenu> </PMenu>
@ -28,24 +29,25 @@ export const Menu = (props: MenuProps) => (
type MenuItemProps = { type MenuItemProps = {
icon?: Icon; icon?: Icon;
textStyle?: ClassInput;
iconStyle?: ClassInput;
style?: ClassInput;
} & MenuOptionProps; } & MenuOptionProps;
export const MenuItem = ({ icon, ...props }: MenuItemProps) => { export const MenuItem = ({ icon, textStyle, iconStyle, style, ...props }: MenuItemProps) => {
const Icon = icon; const Icon = icon;
return ( return (
<View style={tw`flex flex-1 flex-row items-center`}> <View style={twStyle(`flex-1 flex-row items-center px-2 py-1`, style)}>
{Icon && ( {Icon && (
<View style={tw`ml-1`}> <Icon size={14} style={twStyle(`text-ink-dull`, iconStyle)} />
<Icon size={16} style={tw`text-ink`} />
</View>
)} )}
<MenuOption <MenuOption
{...props} {...props}
customStyles={{ customStyles={{
optionText: tw`w-full py-1 text-sm font-medium text-ink` optionText: twStyle(`text-sm font-medium text-ink-dull`, textStyle)
}} }}
style={tw`flex flex-row items-center`} style={tw`flex flex-row`}
/> />
</View> </View>
); );

View file

@ -5,13 +5,13 @@ import Toast, { ToastConfig } from 'react-native-toast-message';
import { tw } from '~/lib/tailwind'; import { tw } from '~/lib/tailwind';
const baseStyles = 'max-w-[340px] flex-row gap-1 items-center justify-center overflow-hidden rounded-md border p-3 shadow-lg bg-app-input border-app-inputborder'; const baseStyles = 'max-w-[340px] flex-row gap-1 items-center justify-center overflow-hidden rounded-md border p-3 shadow-lg bg-app-input border-app-inputborder';
const containerStyle = 'flex-row items-start gap-2' const containerStyle = 'flex-row items-start gap-1.5'
const toastConfig: ToastConfig = { const toastConfig: ToastConfig = {
success: ({ text1, ...rest }) => ( success: ({ text1, ...rest }) => (
<View style={tw.style(baseStyles)}> <View style={tw.style(baseStyles)}>
<View style={tw.style(containerStyle)}> <View style={tw.style(containerStyle)}>
<CheckCircle size={24} weight="fill" color={tw.color("text-green-500")} /> <CheckCircle size={20} weight="fill" color={tw.color("text-green-500")} />
<Text style={tw`self-center text-left text-sm font-medium text-ink`} numberOfLines={3}> <Text style={tw`self-center text-left text-sm font-medium text-ink`} numberOfLines={3}>
{text1} {text1}
</Text> </Text>
@ -21,7 +21,7 @@ const toastConfig: ToastConfig = {
error: ({ text1, ...rest }) => ( error: ({ text1, ...rest }) => (
<View style={tw.style(baseStyles)}> <View style={tw.style(baseStyles)}>
<View style={tw.style(containerStyle)}> <View style={tw.style(containerStyle)}>
<WarningCircle size={24} weight="fill" color={tw.color("text-red-500")} /> <WarningCircle size={20} weight="fill" color={tw.color("text-red-500")} />
<Text style={tw`self-center text-left text-sm font-medium text-ink`} numberOfLines={3}> <Text style={tw`self-center text-left text-sm font-medium text-ink`} numberOfLines={3}>
{text1} {text1}
</Text> </Text>
@ -31,7 +31,7 @@ const toastConfig: ToastConfig = {
info: ({ text1, ...rest }) => ( info: ({ text1, ...rest }) => (
<View style={tw.style(baseStyles)}> <View style={tw.style(baseStyles)}>
<View style={tw.style(containerStyle)}> <View style={tw.style(containerStyle)}>
<Info size={24} weight="fill" color={tw.color("text-accent")} /> <Info size={20} weight="fill" color={tw.color("text-accent")} />
<Text style={tw`self-center text-left text-sm font-medium text-ink`} numberOfLines={3}> <Text style={tw`self-center text-left text-sm font-medium text-ink`} numberOfLines={3}>
{text1} {text1}
</Text> </Text>

View file

@ -79,7 +79,7 @@ module.exports = {
// shadow // shadow
shade: `hsla(${DARK_HUE}, 15%, 0%, ${ALPHA})`, shade: `hsla(${DARK_HUE}, 15%, 0%, ${ALPHA})`,
// menu // menu
menu: `hsla(${DARK_HUE}, 25%, 5%, ${ALPHA})` menu: `hsla(${DARK_HUE}, 10%, 5%, ${ALPHA})`
}, },
sidebar: { sidebar: {
box: `hsla(${DARK_HUE}, 15%, 16%, ${ALPHA})`, box: `hsla(${DARK_HUE}, 15%, 16%, ${ALPHA})`,

View file

@ -17,7 +17,6 @@ export function useFiltersSearch(search: string) {
const locations = useLibraryQuery(['locations.list'], { const locations = useLibraryQuery(['locations.list'], {
keepPreviousData: true, keepPreviousData: true,
enabled: (name || ext) ? true : false,
}); });
const filterFactory = (key: SearchFilters, value: Filters[keyof Filters]) => { const filterFactory = (key: SearchFilters, value: Filters[keyof Filters]) => {

View file

@ -1,4 +1,3 @@
import BrowseCategories from '~/components/browse/BrowseCategories';
import BrowseLocations from '~/components/browse/BrowseLocations'; import BrowseLocations from '~/components/browse/BrowseLocations';
import BrowseTags from '~/components/browse/BrowseTags'; import BrowseTags from '~/components/browse/BrowseTags';
import ScreenContainer from '~/components/layout/ScreenContainer'; import ScreenContainer from '~/components/layout/ScreenContainer';
@ -6,7 +5,7 @@ import ScreenContainer from '~/components/layout/ScreenContainer';
export default function BrowseScreen() { export default function BrowseScreen() {
return ( return (
<ScreenContainer> <ScreenContainer>
<BrowseCategories /> {/* <BrowseCategories /> */}
<BrowseLocations /> <BrowseLocations />
<BrowseTags /> <BrowseTags />
</ScreenContainer> </ScreenContainer>

View file

@ -77,7 +77,6 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP
includeHeaderHeight includeHeaderHeight
icon={'FolderNoSpace'} icon={'FolderNoSpace'}
style={tw`flex-1 items-center justify-center border-0`} style={tw`flex-1 items-center justify-center border-0`}
textSize="text-md"
iconSize={100} iconSize={100}
description={'No files found'} description={'No files found'}
/>} />}

View file

@ -60,7 +60,6 @@ export default function LocationsScreen({ viewStyle }: Props) {
<Empty <Empty
icon="Folder" icon="Folder"
style={'border-0'} style={'border-0'}
textSize="text-md"
iconSize={84} iconSize={84}
description="You have not added any locations" description="You have not added any locations"
/> />

View file

@ -38,7 +38,6 @@ export default function TagScreen({ navigation, route }: BrowseStackScreenProps<
includeHeaderHeight includeHeaderHeight
icon={'Tags'} icon={'Tags'}
style={tw`flex-1 items-center justify-center border-0`} style={tw`flex-1 items-center justify-center border-0`}
textSize="text-md"
iconSize={100} iconSize={100}
description={'No items assigned to this tag'} description={'No items assigned to this tag'}
/>} {...objects} />; />} {...objects} />;

View file

@ -65,7 +65,6 @@ export default function TagsScreen({ viewStyle = 'list' }: Props) {
<Empty <Empty
icon="Tags" icon="Tags"
style={'border-0'} style={'border-0'}
textSize="text-md"
iconSize={84} iconSize={84}
description="You have not created any tags" description="You have not created any tags"
/> />

View file

@ -1,5 +1,5 @@
import { useIsFocused } from '@react-navigation/native'; import { useIsFocused } from '@react-navigation/native';
import { usePathsExplorerQuery } from '@sd/client'; import { useLibraryQuery, usePathsExplorerQuery } from '@sd/client';
import { ArrowLeft, DotsThree, FunnelSimple } from 'phosphor-react-native'; import { ArrowLeft, DotsThree, FunnelSimple } from 'phosphor-react-native';
import { Suspense, useDeferredValue, useState } from 'react'; import { Suspense, useDeferredValue, useState } from 'react';
import { ActivityIndicator, Platform, Pressable, TextInput, View } from 'react-native'; import { ActivityIndicator, Platform, Pressable, TextInput, View } from 'react-native';
@ -23,6 +23,8 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => {
const deferredSearch = useDeferredValue(search); const deferredSearch = useDeferredValue(search);
const order = useSortBy(); const order = useSortBy();
const locations = useLibraryQuery(['locations.list']).data ?? [];
const objects = usePathsExplorerQuery({ const objects = usePathsExplorerQuery({
order, order,
arg: { arg: {
@ -43,6 +45,13 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => {
const noObjects = objects.items?.length === 0 || !objects.items; const noObjects = objects.items?.length === 0 || !objects.items;
const noSearch = deferredSearch.length === 0 && appliedFiltersLength === 0; const noSearch = deferredSearch.length === 0 && appliedFiltersLength === 0;
const searchIcon =
locations.length > 0 && noObjects && noSearch ? 'FolderNoSpace' :
noSearch && noObjects ? 'Search' : 'FolderNoSpace';
const searchDescription = locations.length === 0 ? 'You have not added any locations to search' : noObjects
|| noSearch ? 'No files found' : 'No results found for this search';
return ( return (
<View <View
style={twStyle('relative z-50 flex-1 bg-app-header', { style={twStyle('relative z-50 flex-1 bg-app-header', {
@ -117,12 +126,12 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => {
emptyComponent={ emptyComponent={
<Empty <Empty
includeHeaderHeight includeHeaderHeight
icon={noSearch ? 'Search' : 'FolderNoSpace'} icon={searchIcon}
description={searchDescription}
style={tw`flex-1 items-center justify-center border-0`} style={tw`flex-1 items-center justify-center border-0`}
textSize="text-md" textStyle={tw`max-w-[220px]`}
iconSize={100} iconSize={100}
description={noSearch ? 'Add filters or type to search for files' : 'No files found'} />
/>
} }
tabHeight={false} /> tabHeight={false} />
</Suspense> </Suspense>