mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-02 11:13:29 +00:00
[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:
parent
d64b21357b
commit
530e3c8ac8
|
@ -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}
|
||||||
|
|
|
@ -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')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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})`,
|
||||||
|
|
|
@ -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]) => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'}
|
||||||
/>}
|
/>}
|
||||||
|
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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} />;
|
||||||
|
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue