diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index ac5f5232b..54e45cb5d 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -4,6 +4,19 @@ import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'; +import { + ClientContextProvider, + LibraryContextProvider, + P2PContextProvider, + RspcProvider, + initPlausible, + useBridgeQuery, + useClientContext, + useInvalidateQuery, + usePlausibleEvent, + usePlausiblePageViewMonitor, + usePlausiblePingMonitor +} from '@sd/client'; import { QueryClient } from '@tanstack/react-query'; import dayjs from 'dayjs'; import advancedFormat from 'dayjs/plugin/advancedFormat'; @@ -17,19 +30,6 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { MenuProvider } from 'react-native-popup-menu'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useSnapshot } from 'valtio'; -import { - ClientContextProvider, - initPlausible, - LibraryContextProvider, - P2PContextProvider, - RspcProvider, - useBridgeQuery, - useClientContext, - useInvalidateQuery, - usePlausibleEvent, - usePlausiblePageViewMonitor, - usePlausiblePingMonitor -} from '@sd/client'; import { GlobalModals } from './components/modal/GlobalModals'; import { Toast, toastConfig } from './components/primitive/Toast'; @@ -55,13 +55,17 @@ function AppNavigation() { const plausibleEvent = usePlausibleEvent(); const buildInfo = useBridgeQuery(['buildInfo']); - initPlausible({ platformType: 'mobile', buildInfo: buildInfo?.data }); - const navRef = useNavigationContainerRef(); const routeNameRef = useRef(); const [currentPath, setCurrentPath] = useState('/'); + useEffect(() => { + if (buildInfo?.data) { + initPlausible({ platformType: 'mobile', buildInfo: buildInfo.data }); + } + }, [buildInfo]); + usePlausiblePageViewMonitor({ currentPath }); usePlausiblePingMonitor({ currentPath }); @@ -73,9 +77,11 @@ function AppNavigation() { return () => clearInterval(interval); }, [plausibleEvent]); - if (library === null && libraries.data) { - currentLibraryStore.id = libraries.data[0]?.uuid ?? null; - } + useEffect(() => { + if (library === null && libraries.data) { + currentLibraryStore.id = libraries.data[0]?.uuid ?? null; + } + }, [library, libraries]); return ( { const navigation = useNavigation['navigation']>(); return ( - + Library diff --git a/apps/mobile/src/components/browse/BrowseLocations.tsx b/apps/mobile/src/components/browse/BrowseLocations.tsx index 819a50e22..991404cab 100644 --- a/apps/mobile/src/components/browse/BrowseLocations.tsx +++ b/apps/mobile/src/components/browse/BrowseLocations.tsx @@ -1,14 +1,15 @@ import { useNavigation } from '@react-navigation/native'; import { useLibraryQuery } from '@sd/client'; -import { DotsThree, Plus } from 'phosphor-react-native'; -import { useRef } from 'react'; -import { Text, View } from 'react-native'; +import { useRef, useState } from 'react'; +import { FlatList, Text, View } from 'react-native'; import { ModalRef } from '~/components/layout/Modal'; -import { tw } from '~/lib/tailwind'; +import { tw, twStyle } from '~/lib/tailwind'; import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack'; import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack'; +import { Plus } from 'phosphor-react-native'; import Empty from '../layout/Empty'; +import Fade from '../layout/Fade'; import { LocationItem } from '../locations/LocationItem'; import ImportModal from '../modal/ImportModal'; import { Button } from '../primitive/Button'; @@ -20,54 +21,64 @@ const BrowseLocations = () => { >(); const modalRef = useRef(null); - + const [showAll, setShowAll] = useState(false); const result = useLibraryQuery(['locations.list'], { keepPreviousData: true }); const locations = result.data; return ( - - + + Locations - - {locations?.length === 0 ? ( - - ) : ( - <> - {locations?.slice(0, 3).map((location) => ( + + + } + numColumns={showAll ? 3 : 1} + horizontal={showAll ? false : true} + contentContainerStyle={twStyle(locations?.length === 0 && 'w-full','px-5')} + key={showAll ? '_locations' : 'alllocationcols'} + keyExtractor={(item) => item.id.toString()} + scrollEnabled={showAll ? false : true} + showsHorizontalScrollIndicator={false} + renderItem={({ item }) => { + return ( navigation.navigate('SettingsStack', { screen: 'EditLocationSettings', - params: { id: location.id }, + params: { id: item.id }, initial: false }) } - onPress={() => navigation.navigate('Location', { id: location.id })} - /> - ))} - - )} + onPress={() => navigation.navigate('Location', { id: item.id })} + /> + )} + } + /> + diff --git a/apps/mobile/src/components/browse/BrowseTags.tsx b/apps/mobile/src/components/browse/BrowseTags.tsx index 4fc8369ae..a1360362d 100644 --- a/apps/mobile/src/components/browse/BrowseTags.tsx +++ b/apps/mobile/src/components/browse/BrowseTags.tsx @@ -1,13 +1,14 @@ import { useNavigation } from '@react-navigation/native'; import { useLibraryQuery } from '@sd/client'; -import { DotsThree, Plus } from 'phosphor-react-native'; -import React, { useRef } from 'react'; -import { Text, View } from 'react-native'; +import { Plus } from 'phosphor-react-native'; +import React, { useRef, useState } from 'react'; +import { FlatList, Text, View } from 'react-native'; import { ModalRef } from '~/components/layout/Modal'; -import { tw } from '~/lib/tailwind'; +import { tw, twStyle } from '~/lib/tailwind'; import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack'; import Empty from '../layout/Empty'; +import Fade from '../layout/Fade'; import CreateTagModal from '../modal/tag/CreateTagModal'; import { Button } from '../primitive/Button'; import { TagItem } from '../tags/TagItem'; @@ -19,47 +20,58 @@ const BrowseTags = () => { const tagData = tags.data; const modalRef = useRef(null); + const [showAll, setShowAll] = useState(false); return ( - - + + Tags - - {tagData?.length === 0 ? ( - - ) : ( - tagData - ?.slice(0, 3) - .map((tag) => ( + + + } + numColumns={showAll ? 3 : 1} + contentContainerStyle={twStyle(tagData?.length === 0 && 'w-full','px-5')} + horizontal={showAll ? false : true} + key={showAll ? '_tags' : 'alltagcols'} + keyExtractor={(item) => item.id.toString()} + scrollEnabled={showAll ? false : true} + showsHorizontalScrollIndicator={false} + renderItem={({ item }) => ( - navigation.navigate('Tag', { id: tag.id, color: tag.color! }) - } - /> - )) - )} + style={twStyle(showAll && 'max-w-[31%] flex-1')} + key={item.id} + tag={item} + onPress={() => + navigation.navigate('Tag', { id: item.id, color: item.color! }) + } + /> + )} + /> + diff --git a/apps/mobile/src/components/drawer/DrawerContent.tsx b/apps/mobile/src/components/drawer/DrawerContent.tsx index 02817e080..b46099351 100644 --- a/apps/mobile/src/components/drawer/DrawerContent.tsx +++ b/apps/mobile/src/components/drawer/DrawerContent.tsx @@ -1,11 +1,11 @@ import { DrawerContentScrollView } from '@react-navigation/drawer'; import { DrawerContentComponentProps } from '@react-navigation/drawer/lib/typescript/src/types'; import { AppLogo } from '@sd/assets/images'; +import { JobManagerContextProvider, useLibraryQuery } from '@sd/client'; import { Image } from 'expo-image'; import { CheckCircle } from 'phosphor-react-native'; import { useRef } from 'react'; import { Platform, Pressable, Text, View } from 'react-native'; -import { JobManagerContextProvider, useLibraryQuery } from '@sd/client'; import Layout from '~/constants/Layout'; import { tw, twStyle } from '~/lib/tailwind'; diff --git a/apps/mobile/src/components/drawer/DrawerLocations.tsx b/apps/mobile/src/components/drawer/DrawerLocations.tsx index 28a367cb5..2af0da0f6 100644 --- a/apps/mobile/src/components/drawer/DrawerLocations.tsx +++ b/apps/mobile/src/components/drawer/DrawerLocations.tsx @@ -1,14 +1,14 @@ import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'; import { useNavigation } from '@react-navigation/native'; -import { useRef } from 'react'; -import { Pressable, Text, View } from 'react-native'; import { + Location, arraysEqual, humanizeSize, - Location, useLibraryQuery, useOnlineLocations } from '@sd/client'; +import { useRef } from 'react'; +import { Pressable, Text, View } from 'react-native'; import { ModalRef } from '~/components/layout/Modal'; import { tw, twStyle } from '~/lib/tailwind'; @@ -49,8 +49,8 @@ const DrawerLocationItem: React.FC = ({ {location.name ?? ''} - - + + {`${humanizeSize(location.size_in_bytes)}`} diff --git a/apps/mobile/src/components/explorer/FileRow.tsx b/apps/mobile/src/components/explorer/FileRow.tsx index d376c0177..25ac79a5f 100644 --- a/apps/mobile/src/components/explorer/FileRow.tsx +++ b/apps/mobile/src/components/explorer/FileRow.tsx @@ -27,7 +27,7 @@ const FileRow = ({ data }: FileRowProps) => { height: getExplorerStore().listItemSize })} > - + diff --git a/apps/mobile/src/components/layout/Modal.tsx b/apps/mobile/src/components/layout/Modal.tsx index 6d2b60524..ecdc86872 100644 --- a/apps/mobile/src/components/layout/Modal.tsx +++ b/apps/mobile/src/components/layout/Modal.tsx @@ -6,10 +6,10 @@ import { BottomSheetHandleProps, BottomSheetModal, BottomSheetModalProps, - BottomSheetScrollView + BottomSheetScrollView, } from '@gorhom/bottom-sheet'; import { X } from 'phosphor-react-native'; -import { forwardRef, ReactNode } from 'react'; +import { ReactNode, forwardRef } from 'react'; import { Platform, Pressable, Text, View } from 'react-native'; import useForwardedRef from '~/hooks/useForwardedRef'; import { tw, twStyle } from '~/lib/tailwind'; diff --git a/apps/mobile/src/components/locations/GridLocation.tsx b/apps/mobile/src/components/locations/GridLocation.tsx index ed40b9c25..47c8e1853 100644 --- a/apps/mobile/src/components/locations/GridLocation.tsx +++ b/apps/mobile/src/components/locations/GridLocation.tsx @@ -1,6 +1,6 @@ +import { Location, arraysEqual, humanizeSize, useOnlineLocations } from '@sd/client'; import { DotsThreeOutlineVertical } from 'phosphor-react-native'; import { Pressable, Text, View } from 'react-native'; -import { arraysEqual, humanizeSize, Location, useOnlineLocations } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import FolderIcon from '../icons/FolderIcon'; @@ -16,7 +16,7 @@ const GridLocation: React.FC = ({ location, modalRef }: GridL const onlineLocations = useOnlineLocations(); const online = onlineLocations.some((l) => arraysEqual(location.pub_id, l)); return ( - + @@ -46,9 +46,11 @@ const GridLocation: React.FC = ({ location, modalRef }: GridL {location.path} - + + {`${humanizeSize(location.size_in_bytes)}`} + ); }; diff --git a/apps/mobile/src/components/locations/ListLocation.tsx b/apps/mobile/src/components/locations/ListLocation.tsx index 90fc66b8b..66b242a18 100644 --- a/apps/mobile/src/components/locations/ListLocation.tsx +++ b/apps/mobile/src/components/locations/ListLocation.tsx @@ -61,10 +61,10 @@ const ListLocation = ({ location }: ListLocationProps) => { - - + + {`${humanizeSize(location.size_in_bytes)}`} diff --git a/apps/mobile/src/components/locations/LocationItem.tsx b/apps/mobile/src/components/locations/LocationItem.tsx index fae5729db..a9f476724 100644 --- a/apps/mobile/src/components/locations/LocationItem.tsx +++ b/apps/mobile/src/components/locations/LocationItem.tsx @@ -1,8 +1,9 @@ +import { Location } from '@sd/client'; import { useRef } from 'react'; import { Pressable } from 'react-native'; -import { Location } from '@sd/client'; import { twStyle } from '~/lib/tailwind'; +import { ClassInput } from 'twrnc'; import { ModalRef } from '../layout/Modal'; import { LocationModal } from '../modal/location/LocationModal'; import GridLocation from './GridLocation'; @@ -13,19 +14,21 @@ type LocationItemProps = { onPress: () => void; viewStyle?: 'grid' | 'list'; editLocation: () => void; + style?: ClassInput; }; export const LocationItem = ({ location, onPress, editLocation, - viewStyle = 'grid' + viewStyle = 'grid', + style }: LocationItemProps) => { const modalRef = useRef(null); return ( <> {viewStyle === 'grid' ? ( diff --git a/apps/mobile/src/components/modal/AddTagModal.tsx b/apps/mobile/src/components/modal/AddTagModal.tsx index 1beee726a..e45d16502 100644 --- a/apps/mobile/src/components/modal/AddTagModal.tsx +++ b/apps/mobile/src/components/modal/AddTagModal.tsx @@ -1,4 +1,4 @@ -import { Tag, getItemObject, useLibraryMutation, useLibraryQuery, useRspcContext } from "@sd/client"; +import { Tag, getItemObject, useLibraryMutation, useLibraryQuery, useRspcLibraryContext } from "@sd/client"; import { CaretLeft, Plus } from "phosphor-react-native"; import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FlatList, NativeScrollEvent, Pressable, Text, View } from "react-native"; @@ -24,7 +24,7 @@ const AddTagModal = forwardRef((_, ref) => { const [startedScrolling, setStartedScrolling] = useState(false); const [reachedBottom, setReachedBottom] = useState(true); // needs to be set to true for initial rendering fade to be correct - const rspc = useRspcContext(); + const rspc = useRspcLibraryContext(); const tagsQuery = useLibraryQuery(['tags.list']); const tagsObjectQuery = useLibraryQuery(['tags.getForObject', objectData?.id ?? -1]); const mutation = useLibraryMutation(['tags.assign'], { diff --git a/apps/mobile/src/components/modal/ImportModal.tsx b/apps/mobile/src/components/modal/ImportModal.tsx index 70e98a93f..7980284c9 100644 --- a/apps/mobile/src/components/modal/ImportModal.tsx +++ b/apps/mobile/src/components/modal/ImportModal.tsx @@ -1,14 +1,15 @@ import * as RNFS from '@dr.pogodin/react-native-fs'; +import { useLibraryMutation, useRspcLibraryContext } from '@sd/client'; import { forwardRef, useCallback } from 'react'; import { Alert, Platform, Text, View } from 'react-native'; import DocumentPicker from 'react-native-document-picker'; -import { useLibraryMutation, useRspcLibraryContext } from '@sd/client'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; import useForwardedRef from '~/hooks/useForwardedRef'; import { tw } from '~/lib/tailwind'; import { Icon } from '../icons/Icon'; +import { toast } from '../primitive/Toast'; // import * as ML from 'expo-media-library'; @@ -22,17 +23,27 @@ const ImportModal = forwardRef((_, ref) => { const createLocation = useLibraryMutation('locations.create', { onError: (error, variables) => { + modalRef.current?.close(); + //custom message handling + if (error.message.startsWith("location already exists")) { + return toast.error('This location has already been added'); + } switch (error.message) { case 'NEED_RELINK': if (!variables.dry_run) relinkLocation.mutate(variables.path); + toast.info('Please relink the location'); break; case 'ADD_LIBRARY': addLocationToLibrary.mutate(variables); break; default: + toast.error(error.message); throw new Error('Unimplemented custom remote error handling'); } }, + onSuccess: () => { + toast.success('Location added successfully'); + }, onSettled: () => { rspc.queryClient.invalidateQueries(['locations.list']); modalRef.current?.close(); diff --git a/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx b/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx index 9dc6a59fd..8d221305e 100644 --- a/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx +++ b/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx @@ -1,6 +1,7 @@ -import { useRef } from 'react'; import { useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; +import { useRef } from 'react'; import { ConfirmModal, ModalRef } from '~/components/layout/Modal'; +import { toast } from '~/components/primitive/Toast'; type Props = { locationId: number; @@ -20,6 +21,10 @@ const DeleteLocationModal = ({ trigger, onSubmit, locationId, triggerStyle }: Pr onSuccess: () => { submitPlausibleEvent({ event: { type: 'locationDelete' } }); onSubmit?.(); + toast.success('Location deleted successfully'); + }, + onError: (error) => { + toast.error(error.message); }, onSettled: () => { modalRef.current?.close(); diff --git a/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx b/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx index 54eb5e8cf..8d68272cc 100644 --- a/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx +++ b/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx @@ -1,6 +1,7 @@ -import { useRef } from 'react'; import { useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; +import { useRef } from 'react'; import { ConfirmModal, ModalRef } from '~/components/layout/Modal'; +import { toast } from '~/components/primitive/Toast'; type Props = { tagId: number; @@ -19,6 +20,7 @@ const DeleteTagModal = ({ trigger, onSubmit, tagId, triggerStyle }: Props) => { submitPlausibleEvent({ event: { type: 'tagDelete' } }); onSubmit?.(); rspc.queryClient.invalidateQueries(['tags.list']); + toast.success('Tag deleted successfully'); }, onSettled: () => { modalRef.current?.close(); diff --git a/apps/mobile/src/components/modal/inspector/ActionsModal.tsx b/apps/mobile/src/components/modal/inspector/ActionsModal.tsx index b0dcd79ee..f310ad2c9 100644 --- a/apps/mobile/src/components/modal/inspector/ActionsModal.tsx +++ b/apps/mobile/src/components/modal/inspector/ActionsModal.tsx @@ -3,7 +3,8 @@ import { getItemObject, humanizeSize, useLibraryMutation, - useLibraryQuery + useLibraryQuery, + useRspcContext } from '@sd/client'; import dayjs from 'dayjs'; import { @@ -28,6 +29,7 @@ import { tw, twStyle } from '~/lib/tailwind'; import { useActionsModalStore } from '~/stores/modalStore'; import FileInfoModal from './FileInfoModal'; +import RenameModal from './RenameModal'; type ActionsContainerProps = PropsWithChildren<{ style?: ViewStyle; @@ -65,8 +67,10 @@ const ActionDivider = () => ; export const ActionsModal = () => { const fileInfoRef = useRef(null); + const renameRef = useRef(null); const { modalRef, data } = useActionsModalStore(); + const rspc = useRspcContext(); const objectData = data && getItemObject(data); const filePath = data && getIndexedItemFilePath(data); @@ -77,6 +81,13 @@ export const ActionsModal = () => { enabled: filePath != null }); + const deleteFile = useLibraryMutation('files.deleteFiles', { + onSuccess: () => { + rspc.queryClient.invalidateQueries(['search.paths']) + modalRef.current?.dismiss(); + } + }); + async function handleOpen() { const absolutePath = queriedFullPath.data; if (!absolutePath) return; @@ -141,7 +152,9 @@ export const ActionsModal = () => { /> - + { + renameRef.current?.present(); + }} icon={Pencil} title="Rename" /> @@ -154,11 +167,19 @@ export const ActionsModal = () => { - + { + if (filePath && filePath.location_id) { + await deleteFile.mutateAsync({ + location_id: filePath.location_id, + file_path_ids: [filePath.id] + }); + } + }} /> )} + ); diff --git a/apps/mobile/src/components/modal/inspector/RenameModal.tsx b/apps/mobile/src/components/modal/inspector/RenameModal.tsx new file mode 100644 index 000000000..4bc7f100b --- /dev/null +++ b/apps/mobile/src/components/modal/inspector/RenameModal.tsx @@ -0,0 +1,90 @@ +import { getIndexedItemFilePath, useLibraryMutation, useRspcLibraryContext } from '@sd/client'; +import React, { forwardRef, useEffect, useRef, useState } from 'react'; +import { Text, View } from 'react-native'; +import { TextInput } from 'react-native-gesture-handler'; +import { Modal, ModalRef } from '~/components/layout/Modal'; +import { Button } from '~/components/primitive/Button'; +import { ModalInput } from '~/components/primitive/Input'; +import { toast } from '~/components/primitive/Toast'; +import useForwardedRef from '~/hooks/useForwardedRef'; +import { tw } from '~/lib/tailwind'; +import { useActionsModalStore } from '~/stores/modalStore'; + +interface Props { + objectName: string; +} + +const RenameModal = forwardRef((props, ref) => { + const modalRef = useForwardedRef(ref); + const [newName, setNewName] = useState(''); + const rspc = useRspcLibraryContext(); + const { data } = useActionsModalStore(); + const inputRef = useRef(null); + + const filePathData = data && getIndexedItemFilePath(data); + + const renameFile = useLibraryMutation(['files.renameFile'], { + onSuccess: () => { + modalRef.current?.dismiss(); + rspc.queryClient.invalidateQueries(['search.paths']); + }, + onError: () => { + toast.error('Failed to rename object'); + } + }); + + // set input value to object name on initial render + useEffect(() => { + setNewName(props.objectName); + }, [props.objectName]); + + const textRenameHandler = async () => { + switch (data?.type) { + case 'Path': + case 'Object': { + if (!filePathData) throw new Error('Failed to get file path object'); + + const { id, location_id } = filePathData; + + if (!location_id) throw new Error('Missing location id'); + + await renameFile.mutateAsync({ + location_id: location_id, + kind: { + One: { + from_file_path_id: id, + to: newName + } + } + }); + break; + } + } + }; + + return ( + setNewName(props.objectName)} + enableContentPanningGesture={false} + enablePanDownToClose={false} + snapPoints={['20']} + > + + inputRef.current?.setSelection(0, newName.length)} + value={newName} + onChangeText={(t) => setNewName(t)} + /> + + + + ); +}); + +export default RenameModal; diff --git a/apps/mobile/src/components/modal/tag/CreateTagModal.tsx b/apps/mobile/src/components/modal/tag/CreateTagModal.tsx index d92fc40cf..ff4f9fdd0 100644 --- a/apps/mobile/src/components/modal/tag/CreateTagModal.tsx +++ b/apps/mobile/src/components/modal/tag/CreateTagModal.tsx @@ -1,16 +1,17 @@ -import { forwardRef, useEffect, useState } from 'react'; -import { Pressable, Text, View } from 'react-native'; -import ColorPicker from 'react-native-wheel-color-picker'; import { ToastDefautlColor, useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; +import { forwardRef, useEffect, useState } from 'react'; +import { Pressable, Text, View } from 'react-native'; +import ColorPicker from 'react-native-wheel-color-picker'; import { FadeInAnimation } from '~/components/animation/layout'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; import { ModalInput } from '~/components/primitive/Input'; +import { toast } from '~/components/primitive/Toast'; import useForwardedRef from '~/hooks/useForwardedRef'; import { useKeyboard } from '~/hooks/useKeyboard'; import { tw, twStyle } from '~/lib/tailwind'; @@ -36,8 +37,12 @@ const CreateTagModal = forwardRef((_, ref) => { rspc.queryClient.invalidateQueries(['tags.list']); + toast.success('Tag created successfully'); submitPlausibleEvent({ event: { type: 'tagCreate' } }); }, + onError: (error) => { + toast.error(error.message); + }, onSettled: () => { // Close modal modalRef.current?.dismiss(); @@ -57,7 +62,7 @@ const CreateTagModal = forwardRef((_, ref) => { return ( { // Resets form onDismiss @@ -94,7 +99,7 @@ const CreateTagModal = forwardRef((_, ref) => { diff --git a/apps/mobile/src/components/overview/Locations.tsx b/apps/mobile/src/components/overview/Locations.tsx index 62984ac07..fb24ca639 100644 --- a/apps/mobile/src/components/overview/Locations.tsx +++ b/apps/mobile/src/components/overview/Locations.tsx @@ -1,8 +1,8 @@ import { useNavigation } from '@react-navigation/native'; +import { useLibraryQuery } from '@sd/client'; import React, { useRef } from 'react'; import { Pressable, Text, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; -import { useLibraryQuery } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import { OverviewStackScreenProps } from '~/navigation/tabs/OverviewStack'; @@ -25,10 +25,11 @@ const Locations = () => { <> + location.id.toString()} ItemSeparatorComponent={() => } @@ -73,6 +74,7 @@ const Locations = () => { )} /> + diff --git a/apps/mobile/src/components/overview/OverviewStats.tsx b/apps/mobile/src/components/overview/OverviewStats.tsx index da1719629..d72eba4e7 100644 --- a/apps/mobile/src/components/overview/OverviewStats.tsx +++ b/apps/mobile/src/components/overview/OverviewStats.tsx @@ -37,7 +37,7 @@ const StatItem = ({ title, bytes, isLoading, style }: StatItemProps) => { hidden: isLoading })} > - {title} + {title} {count} {unit} @@ -104,7 +104,7 @@ const OverviewStats = ({ stats }: Props) => { }; return ( - + Statistics diff --git a/apps/mobile/src/components/primitive/Button.tsx b/apps/mobile/src/components/primitive/Button.tsx index 37eabd611..16b5c0958 100644 --- a/apps/mobile/src/components/primitive/Button.tsx +++ b/apps/mobile/src/components/primitive/Button.tsx @@ -8,10 +8,10 @@ const button = cva(['items-center justify-center rounded-md border shadow-sm'], variants: { variant: { danger: ['border-red-800 bg-red-600 shadow-none'], - gray: ['border-app-lightborder bg-app-button shadow-none'], + gray: ['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'], - outline: ['border-sidebar-line/60 bg-black shadow-none'], + outline: ['border-app-lightborder bg-black shadow-none'], transparent: ['border-0 bg-black shadow-none'], dashed: ['border border-dashed border-app-line bg-transparent shadow-none'] }, diff --git a/apps/mobile/src/components/primitive/Input.tsx b/apps/mobile/src/components/primitive/Input.tsx index 023b5c21b..abe5b453f 100644 --- a/apps/mobile/src/components/primitive/Input.tsx +++ b/apps/mobile/src/components/primitive/Input.tsx @@ -1,7 +1,7 @@ import { BottomSheetTextInput } from '@gorhom/bottom-sheet'; import { cva, VariantProps } from 'class-variance-authority'; import { Eye, EyeSlash } from 'phosphor-react-native'; -import { useState } from 'react'; +import { forwardRef, useState } from 'react'; import { Pressable, TextInputProps as RNTextInputProps, TextInput, View } from 'react-native'; import { tw, twStyle } from '~/lib/tailwind'; @@ -23,28 +23,32 @@ const input = cva(['rounded-md border text-sm leading-tight shadow-sm'], { type InputProps = VariantProps & RNTextInputProps; -export const Input = ({ variant, size, ...props }: InputProps) => { - const { style, ...otherProps } = props; +export const Input = forwardRef((props, ref) => { + const { style, variant, size, ...otherProps } = props; return ( ); -}; +}) // To use in modals (for keyboard handling) -export const ModalInput = ({ variant, size, ...props }: InputProps) => { - const { style, ...otherProps } = props; +export const ModalInput = forwardRef((props, ref) => { + const { style, variant, size, ...otherProps } = props; return ( ); -}; +}) // Same as Input but configured with password props & show/hide password button diff --git a/apps/mobile/src/components/primitive/Toast.tsx b/apps/mobile/src/components/primitive/Toast.tsx index a22fc0d83..0ba8d9528 100644 --- a/apps/mobile/src/components/primitive/Toast.tsx +++ b/apps/mobile/src/components/primitive/Toast.tsx @@ -1,30 +1,41 @@ /* eslint-disable no-restricted-imports */ +import { CheckCircle, Info, WarningCircle } from 'phosphor-react-native'; import { Text, View } from 'react-native'; import Toast, { ToastConfig } from 'react-native-toast-message'; import { tw } from '~/lib/tailwind'; -const baseStyles = 'w-[340px] flex-row overflow-hidden rounded-md border p-3 shadow-lg'; +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 toastConfig: ToastConfig = { success: ({ text1, ...rest }) => ( - - + + + + {text1} + ), error: ({ text1, ...rest }) => ( - - + + + + {text1} + ), info: ({ text1, ...rest }) => ( - - + + + + {text1} + ) }; diff --git a/apps/mobile/src/components/tags/GridTag.tsx b/apps/mobile/src/components/tags/GridTag.tsx index 98fb764fb..a2598126d 100644 --- a/apps/mobile/src/components/tags/GridTag.tsx +++ b/apps/mobile/src/components/tags/GridTag.tsx @@ -16,7 +16,7 @@ const GridTag = ({ tag, modalRef }: GridTagProps) => { diff --git a/apps/mobile/src/components/tags/TagItem.tsx b/apps/mobile/src/components/tags/TagItem.tsx index e131fe501..7aba00a01 100644 --- a/apps/mobile/src/components/tags/TagItem.tsx +++ b/apps/mobile/src/components/tags/TagItem.tsx @@ -1,8 +1,9 @@ +import { Tag } from '@sd/client'; import { useRef } from 'react'; import { Pressable } from 'react-native'; -import { Tag } from '@sd/client'; import { twStyle } from '~/lib/tailwind'; +import { ClassInput } from 'twrnc'; import { ModalRef } from '../layout/Modal'; import { TagModal } from '../modal/tag/TagModal'; import GridTag from './GridTag'; @@ -12,14 +13,15 @@ type TagItemProps = { tag: Tag; onPress: () => void; viewStyle?: 'grid' | 'list'; + style?: ClassInput; }; -export const TagItem = ({ tag, onPress, viewStyle = 'grid' }: TagItemProps) => { +export const TagItem = ({ tag, onPress, style, viewStyle = 'grid' }: TagItemProps) => { const modalRef = useRef(null); return ( <> diff --git a/apps/mobile/src/hooks/useFiltersSearch.ts b/apps/mobile/src/hooks/useFiltersSearch.ts index 37c17608f..a93bd0216 100644 --- a/apps/mobile/src/hooks/useFiltersSearch.ts +++ b/apps/mobile/src/hooks/useFiltersSearch.ts @@ -60,7 +60,7 @@ export function useFiltersSearch(search: string) { const filters = [] as SearchFilterArgs[]; //It's a global search if no locations have been selected - if (searchStore.filters.locations.length === 0 || !name || !ext) { + if (searchStore.filters.locations.length === 0) { const locationIds = locations.data?.map((l) => l.id); if (locationIds) filters.push({ filePath: { locations: { in: locationIds } } }); } diff --git a/apps/mobile/src/screens/browse/Location.tsx b/apps/mobile/src/screens/browse/Location.tsx index fe02529d0..39a767968 100644 --- a/apps/mobile/src/screens/browse/Location.tsx +++ b/apps/mobile/src/screens/browse/Location.tsx @@ -1,4 +1,4 @@ -import { useLibraryQuery, usePathsExplorerQuery } from '@sd/client'; +import { useLibraryQuery, useLibrarySubscription, usePathsExplorerQuery } from '@sd/client'; import { useEffect, useMemo } from 'react'; import Explorer from '~/components/explorer/Explorer'; import Empty from '~/components/layout/Empty'; @@ -19,6 +19,13 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP .pop(); }, [path]) + // makes sure that the location shows newest/modified objects + // when a location is opened + useLibrarySubscription( + ['locations.quickRescan', { sub_path: path ?? '', location_id: id }], + { onData() {} } + ); + const paths = usePathsExplorerQuery({ arg: { filters: [ diff --git a/apps/mobile/tests/add-tag.yml b/apps/mobile/tests/add-tag.yml index 893545b0e..9eb1236f6 100644 --- a/apps/mobile/tests/add-tag.yml +++ b/apps/mobile/tests/add-tag.yml @@ -5,10 +5,10 @@ appId: com.spacedrive.app id: 'browse-tab' - waitForAnimationToEnd - tapOn: - id: 'navigate-tags-screen' -- tapOn: - id: 'create-tag-modal' + id: 'create-tag-button' - inputText: 'MyTag' - tapOn: text: 'Create' +- tapOn: + id: 'show-all-tags-button' - assertVisible: 'MyTag' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d102aeed..0f94df447 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ