mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-02 10:03:28 +00:00
[MOB-69] Infinite scroll & explorer query hooks (#2197)
* run sim before everything * move passwordmeter * annoying maestro * remove bad extension from recom * move explorer query logic to @sd/client * update mobile packages * working explorer * search with the new query * tag explorer * revert maestro version bump
This commit is contained in:
parent
72451a07bc
commit
4fc8dcfb48
4
.github/workflows/mobile-ci.yml
vendored
4
.github/workflows/mobile-ci.yml
vendored
|
@ -201,7 +201,9 @@ jobs:
|
|||
- name: Run Simulator
|
||||
uses: futureware-tech/simulator-action@v3
|
||||
with:
|
||||
model: 'iPhone SE (3rd generation)'
|
||||
model: 'iPhone 15'
|
||||
os_version: 17
|
||||
erase_before_boot: false
|
||||
|
||||
- name: Run Tests
|
||||
env:
|
||||
|
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -7,7 +7,6 @@
|
|||
"bradlc.vscode-tailwindcss", // Provides Tailwind CSS IntelliSense
|
||||
"prisma.prisma", // Prisma is an open-source database toolkit
|
||||
"dbaeumer.vscode-eslint", // Integrates ESLint JavaScript into VS Code
|
||||
"esbenp.prettier-vscode", // Code formatter using prettier
|
||||
"inlang.vs-code-extension" // Improved i18n DX (Internationalization Developer Experience)
|
||||
"esbenp.prettier-vscode" // Code formatter using prettier
|
||||
]
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@
|
|||
"@hookform/resolvers": "^3.1.0",
|
||||
"@oscartbeaumont-sd/rspc-client": "=0.0.0-main-dc31e5b2",
|
||||
"@oscartbeaumont-sd/rspc-react": "=0.0.0-main-dc31e5b2",
|
||||
"@react-native-async-storage/async-storage": "~1.21.0",
|
||||
"@react-native-masked-view/masked-view": "^0.3.0",
|
||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||
"@react-navigation/drawer": "^6.6.3",
|
||||
"@react-navigation/native": "^6.1.7",
|
||||
"@react-navigation/stack": "^6.3.17",
|
||||
"@react-native-async-storage/async-storage": "~1.22.3",
|
||||
"@react-native-masked-view/masked-view": "^0.3.1",
|
||||
"@react-navigation/bottom-tabs": "^6.5.19",
|
||||
"@react-navigation/drawer": "^6.6.14",
|
||||
"@react-navigation/native": "^6.1.16",
|
||||
"@react-navigation/stack": "^6.3.28",
|
||||
"@sd/assets": "workspace:*",
|
||||
"@sd/client": "workspace:*",
|
||||
"@shopify/flash-list": "1.6.3",
|
||||
|
@ -36,8 +36,8 @@
|
|||
"babel-preset-solid": "^1.8.9",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"event-target-polyfill": "^0.0.3",
|
||||
"expo": "~50.0.7",
|
||||
"event-target-polyfill": "^0.0.4",
|
||||
"expo": "~50.0.11",
|
||||
"expo-av": "^13.10.5",
|
||||
"expo-blur": "^12.9.2",
|
||||
"expo-build-properties": "~0.11.1",
|
||||
|
@ -45,9 +45,9 @@
|
|||
"expo-media-library": "~15.9.1",
|
||||
"expo-splash-screen": "~0.26.4",
|
||||
"expo-status-bar": "~1.11.1",
|
||||
"lottie-react-native": "6.5.1",
|
||||
"lottie-react-native": "6.7.0",
|
||||
"metro-react-native-babel-transformer": "^0.77.0",
|
||||
"moti": "^0.26.0",
|
||||
"moti": "^0.28.1",
|
||||
"phosphor-react-native": "^2.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-hook-form": "^7.47.0",
|
||||
|
@ -67,7 +67,7 @@
|
|||
"react-native-wheel-color-picker": "^1.2.0",
|
||||
"rive-react-native": "^6.2.3",
|
||||
"solid-js": "^1.8.8",
|
||||
"twrnc": "^3.6.4",
|
||||
"twrnc": "^4.1.0",
|
||||
"use-count-up": "^3.0.1",
|
||||
"use-debounce": "^9.0.4",
|
||||
"valtio": "^1.11.2",
|
||||
|
@ -75,7 +75,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@rnx-kit/metro-config": "^1.3.14",
|
||||
"@rnx-kit/metro-config": "^1.3.15",
|
||||
"@sd/config": "workspace:*",
|
||||
"@types/react": "^18.2.61",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useNavigation } from '@react-navigation/native';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { FlashList, FlashListProps } from '@shopify/flash-list';
|
||||
import { UseInfiniteQueryResult } from '@tanstack/react-query';
|
||||
import { AnimatePresence, MotiView } from 'moti';
|
||||
import { MonitorPlay, Rows, SlidersHorizontal, SquaresFour } from 'phosphor-react-native';
|
||||
import { useState } from 'react';
|
||||
import { Pressable, View } from 'react-native';
|
||||
import { isPath, type ExplorerItem } from '@sd/client';
|
||||
import { ActivityIndicator, Pressable, View } from 'react-native';
|
||||
import { isPath, SearchData, type ExplorerItem } from '@sd/client';
|
||||
import Layout from '~/constants/Layout';
|
||||
import { tw } from '~/lib/tailwind';
|
||||
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
|
||||
|
@ -16,11 +17,15 @@ import FileItem from './FileItem';
|
|||
import FileRow from './FileRow';
|
||||
|
||||
type ExplorerProps = {
|
||||
items?: ExplorerItem[];
|
||||
tabHeight?: boolean;
|
||||
items: ExplorerItem[] | null;
|
||||
/** Function to fetch next page of items. */
|
||||
loadMore: () => void;
|
||||
query: UseInfiniteQueryResult<SearchData<ExplorerItem>>;
|
||||
count?: number;
|
||||
};
|
||||
|
||||
const Explorer = ({ items, tabHeight }: ExplorerProps) => {
|
||||
const Explorer = (props: ExplorerProps) => {
|
||||
const navigation = useNavigation<BrowseStackScreenProps<'Location'>['navigation']>();
|
||||
const explorerStore = useExplorerStore();
|
||||
const [layoutMode, setLayoutMode] = useState<ExplorerLayoutMode>(getExplorerStore().layoutMode);
|
||||
|
@ -46,7 +51,7 @@ const Explorer = ({ items, tabHeight }: ExplorerProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<ScreenContainer tabHeight={tabHeight} scrollview={false} style={'gap-0 py-0'}>
|
||||
<ScreenContainer tabHeight={props.tabHeight} scrollview={false} style={'gap-0 py-0'}>
|
||||
{/* Header */}
|
||||
<View style={tw`flex flex-row items-center justify-between`}>
|
||||
{/* Sort By */}
|
||||
|
@ -80,36 +85,33 @@ const Explorer = ({ items, tabHeight }: ExplorerProps) => {
|
|||
)} */}
|
||||
</View>
|
||||
{/* Items */}
|
||||
{items && (
|
||||
<FlashList
|
||||
key={layoutMode}
|
||||
numColumns={layoutMode === 'grid' ? getExplorerStore().gridNumColumns : 1}
|
||||
data={items}
|
||||
keyExtractor={(item) =>
|
||||
item.type === 'NonIndexedPath'
|
||||
? item.item.path
|
||||
: item.type === 'SpacedropPeer'
|
||||
? item.item.name
|
||||
: item.item.id.toString()
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<Pressable onPress={() => handlePress(item)}>
|
||||
{layoutMode === 'grid' ? (
|
||||
<FileItem data={item} />
|
||||
) : (
|
||||
<FileRow data={item} />
|
||||
)}
|
||||
</Pressable>
|
||||
)}
|
||||
contentContainerStyle={tw`p-2`}
|
||||
extraData={layoutMode}
|
||||
estimatedItemSize={
|
||||
layoutMode === 'grid'
|
||||
? Layout.window.width / getExplorerStore().gridNumColumns
|
||||
: getExplorerStore().listItemSize
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<FlashList
|
||||
key={layoutMode}
|
||||
numColumns={layoutMode === 'grid' ? getExplorerStore().gridNumColumns : 1}
|
||||
data={props.items ?? []}
|
||||
keyExtractor={(item) =>
|
||||
item.type === 'NonIndexedPath'
|
||||
? item.item.path
|
||||
: item.type === 'SpacedropPeer'
|
||||
? item.item.name
|
||||
: item.item.id.toString()
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<Pressable onPress={() => handlePress(item)}>
|
||||
{layoutMode === 'grid' ? <FileItem data={item} /> : <FileRow data={item} />}
|
||||
</Pressable>
|
||||
)}
|
||||
contentContainerStyle={tw`p-2`}
|
||||
extraData={layoutMode}
|
||||
estimatedItemSize={
|
||||
layoutMode === 'grid'
|
||||
? Layout.window.width / getExplorerStore().gridNumColumns
|
||||
: getExplorerStore().listItemSize
|
||||
}
|
||||
onEndReached={() => props.loadMore?.()}
|
||||
onEndReachedThreshold={0.6}
|
||||
ListFooterComponent={props.query.isFetchingNextPage ? <ActivityIndicator /> : null}
|
||||
/>
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -95,11 +95,11 @@ const OverviewStats = ({ stats }: Props) => {
|
|||
}
|
||||
return (
|
||||
<StatItem
|
||||
key={`${library.uuid} ${key}`}
|
||||
key={`${library.uuid}_${key}`}
|
||||
title={StatItemNames[key as keyof Statistics]!}
|
||||
bytes={bytes}
|
||||
isLoading={stats.isLoading}
|
||||
style={tw`${isTotalStat ? 'h-[101px] w-full' : 'w-full'} flex-1`}
|
||||
style={twStyle(isTotalStat && 'h-[101px]', 'w-full flex-1')}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ export const kinds = Object.keys(ObjectKind)
|
|||
|
||||
const Kind = () => {
|
||||
const searchStore = useSearchStore();
|
||||
|
||||
return (
|
||||
<MotiView
|
||||
layout={LinearTransition.duration(300)}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { changeTwTheme, tw } from '~/lib/tailwind';
|
|||
|
||||
export function useTheme() {
|
||||
// Enables screen size breakpoints, etc. for tailwind
|
||||
useDeviceContext(tw, { withDeviceColorScheme: false });
|
||||
useDeviceContext(tw, { initialColorScheme: 'light', observeDeviceColorSchemeChanges: false });
|
||||
|
||||
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
|
||||
import { useEffect } from 'react';
|
||||
import { useCache, useLibraryQuery, useNodes, usePathsExplorerQuery } from '@sd/client';
|
||||
import Explorer from '~/components/explorer/Explorer';
|
||||
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
|
||||
import { getExplorerStore } from '~/stores/explorerStore';
|
||||
|
@ -11,25 +11,31 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP
|
|||
useNodes(location.data?.nodes);
|
||||
const locationData = useCache(location.data?.item);
|
||||
|
||||
// FIXME: This is the correct query, but it doesn't work and then provides a deserialization error.
|
||||
const paths = useLibraryQuery([
|
||||
'search.paths',
|
||||
{
|
||||
const paths = usePathsExplorerQuery({
|
||||
arg: {
|
||||
filters: [
|
||||
// ...search.allFilters,
|
||||
{ filePath: { locations: { in: [id] } } },
|
||||
{
|
||||
filePath: {
|
||||
// locations: { in: [Number(id)] }, // temporarlily disabled to navigate into folders. Note: This makes the query return all locations in the library.
|
||||
path: { location_id: id, path: path ?? '', include_descendants: true }
|
||||
path: {
|
||||
location_id: id,
|
||||
path: path ?? '',
|
||||
include_descendants: false
|
||||
// include_descendants:
|
||||
// search.search !== '' ||
|
||||
// search.dynamicFilters.length > 0 ||
|
||||
// (layoutMode === 'media' && mediaViewWithDescendants)
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
take: 100
|
||||
}
|
||||
]);
|
||||
|
||||
const pathsItemsReferences = useMemo(() => paths.data?.items ?? [], [paths.data]);
|
||||
useNodes(paths.data?.nodes);
|
||||
const pathsItems = useCache(pathsItemsReferences);
|
||||
// !showHiddenFiles && { filePath: { hidden: false } }
|
||||
].filter(Boolean) as any,
|
||||
take: 30
|
||||
},
|
||||
order: null,
|
||||
onSuccess: () => getExplorerStore().resetNewThumbnails()
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Set screen title to location.
|
||||
|
@ -53,5 +59,5 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP
|
|||
getExplorerStore().path = path ?? '';
|
||||
}, [id, path]);
|
||||
|
||||
return <Explorer items={pathsItems} />;
|
||||
return <Explorer {...paths} />;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
|
||||
import { useCache, useLibraryQuery, useNodes, useObjectsExplorerQuery } from '@sd/client';
|
||||
import Explorer from '~/components/explorer/Explorer';
|
||||
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
|
||||
|
||||
export default function TagScreen({ navigation, route }: BrowseStackScreenProps<'Tag'>) {
|
||||
const { id } = route.params;
|
||||
|
||||
const search = useLibraryQuery([
|
||||
'search.objects',
|
||||
{
|
||||
filters: [{ object: { tags: { in: [id] } } }],
|
||||
take: 100
|
||||
}
|
||||
]);
|
||||
useNodes(search.data?.nodes);
|
||||
const searchData = useCache(search.data?.items);
|
||||
|
||||
const tag = useLibraryQuery(['tags.get', id]);
|
||||
useNodes(tag.data?.nodes);
|
||||
const tagData = useCache(tag.data?.item);
|
||||
|
||||
const objects = useObjectsExplorerQuery({
|
||||
arg: { filters: [{ object: { tags: { in: [id] } } }], take: 30 },
|
||||
order: null
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Set screen title to tag name.
|
||||
navigation.setOptions({
|
||||
|
@ -27,5 +22,5 @@ export default function TagScreen({ navigation, route }: BrowseStackScreenProps<
|
|||
});
|
||||
}, [tagData?.name, navigation]);
|
||||
|
||||
return <Explorer items={searchData} />;
|
||||
return <Explorer {...objects} />;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { CheckCircle } from 'phosphor-react-native';
|
||||
import React from 'react';
|
||||
import { useLibraryQuery } from '@sd/client';
|
||||
import { Pressable, View } from 'react-native';
|
||||
import { JobManagerContextProvider, useLibraryQuery } from '@sd/client';
|
||||
import { PulseAnimation } from '~/components/animation/lottie';
|
||||
import BrowseCategories from '~/components/browse/BrowseCategories';
|
||||
import BrowseLocations from '~/components/browse/BrowseLocations';
|
||||
import BrowseTags from '~/components/browse/BrowseTags';
|
||||
import Jobs from '~/components/browse/Jobs';
|
||||
import { ModalRef } from '~/components/layout/Modal';
|
||||
import ScreenContainer from '~/components/layout/ScreenContainer';
|
||||
import { JobManagerModal } from '~/components/modal/job/JobManagerModal';
|
||||
import { tw } from '~/lib/tailwind';
|
||||
|
||||
function JobIcon() {
|
||||
|
@ -19,20 +22,23 @@ function JobIcon() {
|
|||
}
|
||||
|
||||
export default function BrowseScreen() {
|
||||
const modalRef = React.useRef<ModalRef>(null);
|
||||
|
||||
return (
|
||||
<ScreenContainer>
|
||||
<BrowseCategories />
|
||||
<BrowseLocations />
|
||||
<BrowseTags />
|
||||
<Jobs />
|
||||
{/* <View style={tw`flex-row items-center w-full gap-x-4`}>
|
||||
<JobManagerContextProvider>
|
||||
<Pressable onPress={() => modalRef.current?.present()}>
|
||||
<JobIcon />
|
||||
</Pressable>
|
||||
<JobManagerModal ref={modalRef} />
|
||||
</JobManagerContextProvider>
|
||||
</View> */}
|
||||
{/* TODO: Remove this when the new job manager is live, this is here for debugging purposes. */}
|
||||
<View style={tw`w-full flex-row items-center gap-x-4`}>
|
||||
<JobManagerContextProvider>
|
||||
<Pressable onPress={() => modalRef.current?.present()}>
|
||||
<JobIcon />
|
||||
</Pressable>
|
||||
<JobManagerModal ref={modalRef} />
|
||||
</JobManagerContextProvider>
|
||||
</View>
|
||||
</ScreenContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ArrowLeft, FunnelSimple, MagnifyingGlass } from 'phosphor-react-native'
|
|||
import { Suspense, useDeferredValue, useMemo, useState } from 'react';
|
||||
import { ActivityIndicator, Pressable, TextInput, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { getExplorerItemData, SearchFilterArgs, useCache, useLibraryQuery } from '@sd/client';
|
||||
import { SearchFilterArgs, useObjectsExplorerQuery } from '@sd/client';
|
||||
import Explorer from '~/components/explorer/Explorer';
|
||||
import FiltersBar from '~/components/search/filters/FiltersBar';
|
||||
import { tw, twStyle } from '~/lib/tailwind';
|
||||
|
@ -10,8 +10,6 @@ import { SearchStackScreenProps } from '~/navigation/SearchStack';
|
|||
import { getExplorerStore } from '~/stores/explorerStore';
|
||||
import { useSearchStore } from '~/stores/searchStore';
|
||||
|
||||
// TODO: Animations!
|
||||
|
||||
const SearchScreen = ({ navigation }: SearchStackScreenProps<'Home'>) => {
|
||||
const { top } = useSafeAreaInsets();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
@ -35,36 +33,16 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Home'>) => {
|
|||
return filters;
|
||||
}, [deferredSearch]);
|
||||
|
||||
const query = useLibraryQuery(
|
||||
[
|
||||
'search.paths',
|
||||
{
|
||||
// ...args,
|
||||
filters,
|
||||
take: 100
|
||||
}
|
||||
],
|
||||
{
|
||||
suspense: true,
|
||||
enabled: !!deferredSearch,
|
||||
onSuccess: () => getExplorerStore().resetNewThumbnails()
|
||||
}
|
||||
);
|
||||
|
||||
const pathsItemsReferences = useMemo(() => query.data?.items ?? [], [query.data]);
|
||||
const pathsItems = useCache(pathsItemsReferences);
|
||||
|
||||
const items = useMemo(() => {
|
||||
// Mobile does not thave media layout
|
||||
// if (explorerStore.layoutMode !== 'media') return pathsItems;
|
||||
|
||||
return (
|
||||
pathsItems?.filter((item) => {
|
||||
const { kind } = getExplorerItemData(item);
|
||||
return kind === 'Video' || kind === 'Image';
|
||||
}) ?? []
|
||||
);
|
||||
}, [pathsItems]);
|
||||
const objects = useObjectsExplorerQuery({
|
||||
arg: {
|
||||
take: 30,
|
||||
filters
|
||||
},
|
||||
order: null,
|
||||
suspense: true,
|
||||
enabled: !!deferredSearch,
|
||||
onSuccess: () => getExplorerStore().resetNewThumbnails()
|
||||
});
|
||||
|
||||
return (
|
||||
<View
|
||||
|
@ -74,7 +52,7 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Home'>) => {
|
|||
>
|
||||
{/* Header */}
|
||||
<View style={tw`border-b border-app-line/50`}>
|
||||
{/* Search area input container*/}
|
||||
{/* Search area input container */}
|
||||
<View style={tw`flex-row items-center gap-4 px-5 pb-3`}>
|
||||
{/* Back Button */}
|
||||
<Pressable
|
||||
|
@ -116,11 +94,7 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Home'>) => {
|
|||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
navigation.navigate('Filters');
|
||||
}}
|
||||
>
|
||||
<Pressable onPress={() => navigation.navigate('Filters')}>
|
||||
<View
|
||||
style={tw`h-10 w-10 items-center justify-center rounded-md border border-app-line/50 bg-app-box/50`}
|
||||
>
|
||||
|
@ -134,7 +108,7 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Home'>) => {
|
|||
{/* Content */}
|
||||
<View style={tw`flex-1`}>
|
||||
<Suspense fallback={<ActivityIndicator />}>
|
||||
<Explorer tabHeight={false} items={items} />
|
||||
<Explorer {...objects} tabHeight={false} />
|
||||
</Suspense>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ContextType, createContext, PropsWithChildren, useContext } from 'react';
|
||||
import { type Ordering } from '@sd/client';
|
||||
|
||||
import { Ordering } from './store';
|
||||
import { UseExplorer } from './useExplorer';
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import {
|
||||
createOrdering,
|
||||
explorerLayout,
|
||||
getOrderingDirection,
|
||||
orderingKey,
|
||||
useExplorerLayoutStore
|
||||
} from '@sd/client';
|
||||
import { RadixCheckbox, Select, SelectOption, Slider, tw, z } from '@sd/ui';
|
||||
import { explorerLayout, useExplorerLayoutStore } from '~/../packages/client/src';
|
||||
import i18n from '~/app/I18n';
|
||||
import { SortOrderSchema } from '~/app/route-schemas';
|
||||
import { useLocale } from '~/hooks';
|
||||
|
||||
import { useExplorerContext } from './Context';
|
||||
import { createOrdering, getOrderingDirection, orderingKey } from './store';
|
||||
|
||||
const Subheading = tw.div`text-ink-dull mb-1 text-xs font-medium`;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import React, { memo, useCallback, useEffect, useLayoutEffect, useRef, useState
|
|||
import BasicSticky from 'react-sticky-el';
|
||||
import { useWindowEventListener } from 'rooks';
|
||||
import useResizeObserver from 'use-resize-observer';
|
||||
import { type ExplorerItem } from '@sd/client';
|
||||
import { createOrdering, getOrderingDirection, orderingKey, type ExplorerItem } from '@sd/client';
|
||||
import { ContextMenu } from '@sd/ui';
|
||||
import { TruncatedText } from '~/components';
|
||||
import { useShortcut } from '~/hooks';
|
||||
|
@ -15,7 +15,6 @@ import { isNonEmptyObject } from '~/util';
|
|||
import { useLayoutContext } from '../../../Layout/Context';
|
||||
import { useExplorerContext } from '../../Context';
|
||||
import { getQuickPreviewStore, useQuickPreviewStore } from '../../QuickPreview/store';
|
||||
import { createOrdering, getOrderingDirection, orderingKey } from '../../store';
|
||||
import { uniqueId } from '../../util';
|
||||
import { useExplorerViewContext } from '../Context';
|
||||
import { useDragScrollable } from '../useDragScrollable';
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { LoadMoreTrigger, useGrid, useScrollMargin, useVirtualizer } from '@virtual-grid/react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { getExplorerItemData } from '@sd/client';
|
||||
import { getExplorerItemData, getOrderingDirection, orderingKey } from '@sd/client';
|
||||
|
||||
import { useExplorerContext } from '../../Context';
|
||||
import { getOrderingDirection, orderingKey } from '../../store';
|
||||
import { getItemData, getItemId, uniqueId } from '../../util';
|
||||
import { useExplorerViewContext } from '../Context';
|
||||
import { DragSelect } from '../Grid/DragSelect';
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export * from './useExplorerInfiniteQuery';
|
||||
export * from './usePathsInfiniteQuery';
|
||||
export * from './usePathsOffsetInfiniteQuery';
|
||||
export * from './usePathsExplorerQuery';
|
||||
export * from './useObjectsInfiniteQuery';
|
||||
export * from './useObjectsOffsetInfiniteQuery';
|
||||
export * from './useObjectsExplorerQuery';
|
|
@ -7,7 +7,7 @@ import {
|
|||
type ExplorerItem,
|
||||
type ExplorerLayout,
|
||||
type ExplorerSettings,
|
||||
type SortOrder
|
||||
type Ordering
|
||||
} from '@sd/client';
|
||||
|
||||
export enum ExplorerKind {
|
||||
|
@ -16,54 +16,6 @@ export enum ExplorerKind {
|
|||
Space
|
||||
}
|
||||
|
||||
export type Ordering = { field: string; value: SortOrder | Ordering };
|
||||
// branded type for added type-safety
|
||||
export type OrderingKey = string & {};
|
||||
|
||||
type OrderingValue<T extends Ordering, K extends string> = Extract<T, { field: K }>['value'];
|
||||
|
||||
export type OrderingKeys<T extends Ordering> = T extends Ordering
|
||||
? {
|
||||
[K in T['field']]: OrderingValue<T, K> extends SortOrder
|
||||
? K
|
||||
: OrderingValue<T, K> extends Ordering
|
||||
? `${K}.${OrderingKeys<OrderingValue<T, K>>}`
|
||||
: never;
|
||||
}[T['field']]
|
||||
: never;
|
||||
|
||||
export function orderingKey(ordering: Ordering): OrderingKey {
|
||||
let base = ordering.field;
|
||||
|
||||
if (typeof ordering.value === 'object') {
|
||||
base += `.${orderingKey(ordering.value)}`;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
export function createOrdering<TOrdering extends Ordering = Ordering>(
|
||||
key: OrderingKey,
|
||||
value: SortOrder
|
||||
): TOrdering {
|
||||
return key
|
||||
.split('.')
|
||||
.reverse()
|
||||
.reduce((acc, field, i) => {
|
||||
if (i === 0)
|
||||
return {
|
||||
field,
|
||||
value
|
||||
};
|
||||
else return { field, value: acc };
|
||||
}, {} as any);
|
||||
}
|
||||
|
||||
export function getOrderingDirection(ordering: Ordering): SortOrder {
|
||||
if (typeof ordering.value === 'object') return getOrderingDirection(ordering.value);
|
||||
else return ordering.value;
|
||||
}
|
||||
|
||||
export const createDefaultExplorerSettings = <TOrder extends Ordering>(args?: {
|
||||
order?: TOrder | null;
|
||||
}) =>
|
||||
|
|
|
@ -11,8 +11,9 @@ import type {
|
|||
NodeState,
|
||||
Tag
|
||||
} from '@sd/client';
|
||||
import { type Ordering, type OrderingKeys } from '@sd/client';
|
||||
|
||||
import { createDefaultExplorerSettings, type Ordering, type OrderingKeys } from './store';
|
||||
import { createDefaultExplorerSettings } from './store';
|
||||
import { uniqueId } from './util';
|
||||
|
||||
export type ExplorerParent =
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useMemo } from 'react';
|
||||
import { ObjectFilterArgs, ObjectKindEnum, ObjectOrder, SearchFilterArgs } from '@sd/client';
|
||||
import { ObjectKindEnum, ObjectOrder, SearchFilterArgs, useObjectsExplorerQuery } from '@sd/client';
|
||||
import { Icon } from '~/components';
|
||||
import { useRouteTitle } from '~/hooks';
|
||||
|
||||
import Explorer from './Explorer';
|
||||
import { ExplorerContextProvider } from './Explorer/Context';
|
||||
import { useObjectsExplorerQuery } from './Explorer/queries/useObjectsExplorerQuery';
|
||||
import { createDefaultExplorerSettings, objectOrderingKeysSchema } from './Explorer/store';
|
||||
import { DefaultTopBarOptions } from './Explorer/TopBarOptions';
|
||||
import { useExplorer, useExplorerSettings } from './Explorer/useExplorer';
|
||||
|
@ -49,7 +48,7 @@ export function Component() {
|
|||
{ object: { favorite: true } }
|
||||
]
|
||||
},
|
||||
explorerSettings
|
||||
order: explorerSettings.useSettingsSnapshot().order
|
||||
});
|
||||
|
||||
const explorer = useExplorer({
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
useLibrarySubscription,
|
||||
useNodes,
|
||||
useOnlineLocations,
|
||||
usePathsExplorerQuery,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
import { Loader, Tooltip } from '@sd/ui';
|
||||
|
@ -31,8 +32,11 @@ import { useQuickRescan } from '~/hooks/useQuickRescan';
|
|||
|
||||
import Explorer from '../Explorer';
|
||||
import { ExplorerContextProvider } from '../Explorer/Context';
|
||||
import { usePathsExplorerQuery } from '../Explorer/queries';
|
||||
import { createDefaultExplorerSettings, filePathOrderingKeysSchema } from '../Explorer/store';
|
||||
import {
|
||||
createDefaultExplorerSettings,
|
||||
explorerStore,
|
||||
filePathOrderingKeysSchema
|
||||
} from '../Explorer/store';
|
||||
import { DefaultTopBarOptions } from '../Explorer/TopBarOptions';
|
||||
import { useExplorer, UseExplorerSettings, useExplorerSettings } from '../Explorer/useExplorer';
|
||||
import { useExplorerSearchParams } from '../Explorer/util';
|
||||
|
@ -89,7 +93,8 @@ const LocationExplorer = ({ location }: { location: Location; path?: string }) =
|
|||
].filter(Boolean) as any,
|
||||
take
|
||||
},
|
||||
explorerSettings
|
||||
order: explorerSettings.useSettingsSnapshot().order,
|
||||
onSuccess: () => explorerStore.resetNewThumbnails()
|
||||
});
|
||||
|
||||
const explorer = useExplorer({
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useMemo } from 'react';
|
||||
import { ObjectKindEnum, ObjectOrder, SearchFilterArgs } from '@sd/client';
|
||||
import { ObjectKindEnum, ObjectOrder, SearchFilterArgs, useObjectsExplorerQuery } from '@sd/client';
|
||||
import { Icon } from '~/components';
|
||||
import { useRouteTitle } from '~/hooks';
|
||||
|
||||
import Explorer from './Explorer';
|
||||
import { ExplorerContextProvider } from './Explorer/Context';
|
||||
import { useObjectsExplorerQuery } from './Explorer/queries/useObjectsExplorerQuery';
|
||||
import { createDefaultExplorerSettings, objectOrderingKeysSchema } from './Explorer/store';
|
||||
import { DefaultTopBarOptions } from './Explorer/TopBarOptions';
|
||||
import { useExplorer, useExplorerSettings } from './Explorer/useExplorer';
|
||||
|
@ -49,7 +48,7 @@ export function Component() {
|
|||
{ object: { dateAccessed: { from: new Date(0).toISOString() } } }
|
||||
]
|
||||
},
|
||||
explorerSettings
|
||||
order: explorerSettings.useSettingsSnapshot().order
|
||||
});
|
||||
|
||||
const explorer = useExplorer({
|
||||
|
|
|
@ -4,9 +4,9 @@ import { useMemo } from 'react';
|
|||
import {
|
||||
FilePathOrder,
|
||||
SearchFilterArgs,
|
||||
useCache,
|
||||
useLibraryMutation,
|
||||
useLibraryQuery
|
||||
useLibraryQuery,
|
||||
usePathsExplorerQuery
|
||||
} from '@sd/client';
|
||||
import { Button } from '@sd/ui';
|
||||
import { SearchIdParamsSchema } from '~/app/route-schemas';
|
||||
|
@ -14,8 +14,11 @@ import { useRouteTitle, useZodRouteParams } from '~/hooks';
|
|||
|
||||
import Explorer from '../Explorer';
|
||||
import { ExplorerContextProvider } from '../Explorer/Context';
|
||||
import { usePathsExplorerQuery } from '../Explorer/queries';
|
||||
import { createDefaultExplorerSettings, filePathOrderingKeysSchema } from '../Explorer/store';
|
||||
import {
|
||||
createDefaultExplorerSettings,
|
||||
explorerStore,
|
||||
filePathOrderingKeysSchema
|
||||
} from '../Explorer/store';
|
||||
import { DefaultTopBarOptions } from '../Explorer/TopBarOptions';
|
||||
import { useExplorer, useExplorerSettings } from '../Explorer/useExplorer';
|
||||
import { EmptyNotice } from '../Explorer/View/EmptyNotice';
|
||||
|
@ -55,7 +58,8 @@ export const Component = () => {
|
|||
|
||||
const paths = usePathsExplorerQuery({
|
||||
arg: { filters: search.allFilters, take: 50 },
|
||||
explorerSettings
|
||||
order: explorerSettings.useSettingsSnapshot().order,
|
||||
onSuccess: () => explorerStore.resetNewThumbnails()
|
||||
});
|
||||
|
||||
const explorer = useExplorer({
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { useSearchParams as useRawSearchParams } from 'react-router-dom';
|
||||
import { ObjectKindEnum, ObjectOrder } from '@sd/client';
|
||||
import { ObjectKindEnum, ObjectOrder, useObjectsExplorerQuery } from '@sd/client';
|
||||
import { Icon } from '~/components';
|
||||
import { useRouteTitle } from '~/hooks';
|
||||
|
||||
import { SearchContextProvider, SearchOptions, useSearch } from '.';
|
||||
import Explorer from '../Explorer';
|
||||
import { ExplorerContextProvider } from '../Explorer/Context';
|
||||
import { useObjectsExplorerQuery } from '../Explorer/queries/useObjectsExplorerQuery';
|
||||
import { createDefaultExplorerSettings, objectOrderingKeysSchema } from '../Explorer/store';
|
||||
import { DefaultTopBarOptions } from '../Explorer/TopBarOptions';
|
||||
import { useExplorer, UseExplorerSettings, useExplorerSettings } from '../Explorer/useExplorer';
|
||||
|
@ -36,7 +35,7 @@ export function Component() {
|
|||
take: 100,
|
||||
filters: search.allFilters
|
||||
},
|
||||
explorerSettings
|
||||
order: explorerSettings.useSettingsSnapshot().order
|
||||
});
|
||||
|
||||
const explorer = useExplorer({
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import { useMemo } from 'react';
|
||||
import { ObjectKindEnum, ObjectOrder, useCache, useLibraryQuery, useNodes } from '@sd/client';
|
||||
import {
|
||||
ObjectKindEnum,
|
||||
ObjectOrder,
|
||||
useCache,
|
||||
useLibraryQuery,
|
||||
useNodes,
|
||||
useObjectsExplorerQuery
|
||||
} from '@sd/client';
|
||||
import { LocationIdParamsSchema } from '~/app/route-schemas';
|
||||
import { Icon } from '~/components';
|
||||
import { useRouteTitle, useZodRouteParams } from '~/hooks';
|
||||
|
||||
import Explorer from '../Explorer';
|
||||
import { ExplorerContextProvider } from '../Explorer/Context';
|
||||
import { useObjectsExplorerQuery } from '../Explorer/queries';
|
||||
import { createDefaultExplorerSettings, objectOrderingKeysSchema } from '../Explorer/store';
|
||||
import { DefaultTopBarOptions } from '../Explorer/TopBarOptions';
|
||||
import { useExplorer, useExplorerSettings } from '../Explorer/useExplorer';
|
||||
|
@ -48,7 +54,7 @@ export function Component() {
|
|||
|
||||
const objects = useObjectsExplorerQuery({
|
||||
arg: { take: 100, filters: search.allFilters },
|
||||
explorerSettings
|
||||
order: explorerSettings.useSettingsSnapshot().order
|
||||
});
|
||||
|
||||
const explorer = useExplorer({
|
||||
|
|
57
packages/client/src/explorer/index.ts
Normal file
57
packages/client/src/explorer/index.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { SortOrder } from '../core';
|
||||
|
||||
export * from './useExplorerInfiniteQuery';
|
||||
export * from './usePathsInfiniteQuery';
|
||||
export * from './usePathsOffsetInfiniteQuery';
|
||||
export * from './usePathsExplorerQuery';
|
||||
export * from './useObjectsInfiniteQuery';
|
||||
export * from './useObjectsOffsetInfiniteQuery';
|
||||
export * from './useObjectsExplorerQuery';
|
||||
|
||||
export type Ordering = { field: string; value: SortOrder | Ordering };
|
||||
// branded type for added type-safety
|
||||
export type OrderingKey = string & {};
|
||||
|
||||
type OrderingValue<T extends Ordering, K extends string> = Extract<T, { field: K }>['value'];
|
||||
|
||||
export type OrderingKeys<T extends Ordering> = T extends Ordering
|
||||
? {
|
||||
[K in T['field']]: OrderingValue<T, K> extends SortOrder
|
||||
? K
|
||||
: OrderingValue<T, K> extends Ordering
|
||||
? `${K}.${OrderingKeys<OrderingValue<T, K>>}`
|
||||
: never;
|
||||
}[T['field']]
|
||||
: never;
|
||||
|
||||
export function orderingKey(ordering: Ordering): OrderingKey {
|
||||
let base = ordering.field;
|
||||
|
||||
if (typeof ordering.value === 'object') {
|
||||
base += `.${orderingKey(ordering.value)}`;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
export function createOrdering<TOrdering extends Ordering = Ordering>(
|
||||
key: OrderingKey,
|
||||
value: SortOrder
|
||||
): TOrdering {
|
||||
return key
|
||||
.split('.')
|
||||
.reverse()
|
||||
.reduce((acc, field, i) => {
|
||||
if (i === 0)
|
||||
return {
|
||||
field,
|
||||
value
|
||||
};
|
||||
else return { field, value: acc };
|
||||
}, {} as any);
|
||||
}
|
||||
|
||||
export function getOrderingDirection(ordering: Ordering): SortOrder {
|
||||
if (typeof ordering.value === 'object') return getOrderingDirection(ordering.value);
|
||||
else return ordering.value;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { UseInfiniteQueryOptions } from '@tanstack/react-query';
|
||||
import { ExplorerItem, SearchData } from '@sd/client';
|
||||
|
||||
import { Ordering } from '../store';
|
||||
import { UseExplorerSettings } from '../useExplorer';
|
||||
import { ExplorerItem, SearchData } from '../core';
|
||||
import { Ordering } from './index';
|
||||
|
||||
export type UseExplorerInfiniteQueryArgs<TArg, TOrder extends Ordering> = {
|
||||
arg: TArg;
|
||||
explorerSettings: UseExplorerSettings<TOrder>;
|
||||
order: TOrder | null;
|
||||
onSuccess?: () => void;
|
||||
} & Pick<UseInfiniteQueryOptions<SearchData<ExplorerItem>>, 'enabled' | 'suspense'>;
|
|
@ -1,6 +1,8 @@
|
|||
import { UseInfiniteQueryResult, UseQueryResult } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { SearchData, useCache } from '@sd/client';
|
||||
|
||||
import { useCache } from '../cache';
|
||||
import { SearchData } from '../core';
|
||||
|
||||
export function useExplorerQuery<Q>(
|
||||
query: UseInfiniteQueryResult<SearchData<Q>>,
|
|
@ -1,13 +1,12 @@
|
|||
import { ObjectOrder, ObjectSearchArgs, useLibraryQuery } from '@sd/client';
|
||||
|
||||
import { UseExplorerSettings } from '../useExplorer';
|
||||
import { ObjectOrder, ObjectSearchArgs } from '../core';
|
||||
import { useLibraryQuery } from '../rspc';
|
||||
import { UseExplorerInfiniteQueryArgs } from './useExplorerInfiniteQuery';
|
||||
import { useExplorerQuery } from './useExplorerQuery';
|
||||
import { useObjectsOffsetInfiniteQuery } from './useObjectsOffsetInfiniteQuery';
|
||||
|
||||
export function useObjectsExplorerQuery(props: {
|
||||
arg: ObjectSearchArgs;
|
||||
explorerSettings: UseExplorerSettings<ObjectOrder>;
|
||||
}) {
|
||||
export function useObjectsExplorerQuery(
|
||||
props: UseExplorerInfiniteQueryArgs<ObjectSearchArgs, ObjectOrder>
|
||||
) {
|
||||
const query = useObjectsOffsetInfiniteQuery(props);
|
||||
|
||||
const count = useLibraryQuery(['search.objectsCount', { filters: props.arg.filters }], {
|
|
@ -1,35 +1,28 @@
|
|||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
ExplorerItem,
|
||||
ObjectCursor,
|
||||
ObjectOrder,
|
||||
ObjectSearchArgs,
|
||||
useLibraryContext,
|
||||
useNodes,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
|
||||
import { useNodes } from '../cache';
|
||||
import { ExplorerItem, ObjectCursor, ObjectOrder, ObjectSearchArgs } from '../core';
|
||||
import { useLibraryContext } from '../hooks';
|
||||
import { useRspcLibraryContext } from '../rspc';
|
||||
import { UseExplorerInfiniteQueryArgs } from './useExplorerInfiniteQuery';
|
||||
|
||||
export function useObjectsInfiniteQuery({
|
||||
arg,
|
||||
explorerSettings,
|
||||
order,
|
||||
...args
|
||||
}: UseExplorerInfiniteQueryArgs<ObjectSearchArgs, ObjectOrder>) {
|
||||
const { library } = useLibraryContext();
|
||||
const ctx = useRspcLibraryContext();
|
||||
const settings = explorerSettings.useSettingsSnapshot();
|
||||
|
||||
if (settings.order) {
|
||||
arg.orderAndPagination = { orderOnly: settings.order };
|
||||
if (order) {
|
||||
arg.orderAndPagination = { orderOnly: order };
|
||||
}
|
||||
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: ['search.objects', { library_id: library.uuid, arg }] as const,
|
||||
queryFn: ({ pageParam, queryKey: [_, { arg }] }) => {
|
||||
const cItem: Extract<ExplorerItem, { type: 'Object' }> = pageParam;
|
||||
const { order } = settings;
|
||||
|
||||
let orderAndPagination: (typeof arg)['orderAndPagination'];
|
||||
|
|
@ -1,36 +1,28 @@
|
|||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
ExplorerItem,
|
||||
ObjectOrder,
|
||||
ObjectSearchArgs,
|
||||
useLibraryContext,
|
||||
useNodes,
|
||||
useNormalisedCache,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
|
||||
import { useNodes, useNormalisedCache } from '../cache';
|
||||
import { ObjectOrder, ObjectSearchArgs } from '../core';
|
||||
import { useLibraryContext } from '../hooks';
|
||||
import { useRspcLibraryContext } from '../rspc';
|
||||
import { UseExplorerInfiniteQueryArgs } from './useExplorerInfiniteQuery';
|
||||
|
||||
export function useObjectsOffsetInfiniteQuery({
|
||||
arg,
|
||||
explorerSettings,
|
||||
order,
|
||||
...args
|
||||
}: UseExplorerInfiniteQueryArgs<ObjectSearchArgs, ObjectOrder>) {
|
||||
const { library } = useLibraryContext();
|
||||
const ctx = useRspcLibraryContext();
|
||||
const settings = explorerSettings.useSettingsSnapshot();
|
||||
const cache = useNormalisedCache();
|
||||
|
||||
if (settings.order) {
|
||||
arg.orderAndPagination = { orderOnly: settings.order };
|
||||
if (order) {
|
||||
arg.orderAndPagination = { orderOnly: order };
|
||||
}
|
||||
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: ['search.objects', { library_id: library.uuid, arg }] as const,
|
||||
queryFn: async ({ pageParam, queryKey: [_, { arg }] }) => {
|
||||
const { order } = settings;
|
||||
|
||||
let orderAndPagination: (typeof arg)['orderAndPagination'];
|
||||
|
||||
if (!pageParam) {
|
|
@ -1,12 +1,13 @@
|
|||
import { FilePathOrder, FilePathSearchArgs, useLibraryQuery } from '@sd/client';
|
||||
|
||||
import { UseExplorerSettings } from '../useExplorer';
|
||||
import { FilePathOrder, FilePathSearchArgs } from '../core';
|
||||
import { useLibraryQuery } from '../rspc';
|
||||
import { useExplorerQuery } from './useExplorerQuery';
|
||||
import { usePathsOffsetInfiniteQuery } from './usePathsOffsetInfiniteQuery';
|
||||
|
||||
export function usePathsExplorerQuery(props: {
|
||||
arg: FilePathSearchArgs;
|
||||
explorerSettings: UseExplorerSettings<FilePathOrder>;
|
||||
order: FilePathOrder | null;
|
||||
/** This callback will fire any time the query successfully fetches new data. (NOTE: This will be removed on the next major version (react-query)) */
|
||||
onSuccess?: () => void;
|
||||
}) {
|
||||
const query = usePathsOffsetInfiniteQuery(props);
|
||||
|
|
@ -1,32 +1,30 @@
|
|||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useNodes, useNormalisedCache } from '../cache';
|
||||
import {
|
||||
ExplorerItem,
|
||||
FilePathCursorVariant,
|
||||
FilePathObjectCursor,
|
||||
FilePathOrder,
|
||||
FilePathSearchArgs,
|
||||
useLibraryContext,
|
||||
useNodes,
|
||||
useNormalisedCache,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
|
||||
import { explorerStore } from '../store';
|
||||
FilePathSearchArgs
|
||||
} from '../core';
|
||||
import { useLibraryContext } from '../hooks';
|
||||
import { useRspcLibraryContext } from '../rspc';
|
||||
import { UseExplorerInfiniteQueryArgs } from './useExplorerInfiniteQuery';
|
||||
|
||||
export function usePathsInfiniteQuery({
|
||||
arg,
|
||||
explorerSettings,
|
||||
order,
|
||||
onSuccess,
|
||||
...args
|
||||
}: UseExplorerInfiniteQueryArgs<FilePathSearchArgs, FilePathOrder>) {
|
||||
const { library } = useLibraryContext();
|
||||
const ctx = useRspcLibraryContext();
|
||||
const settings = explorerSettings.useSettingsSnapshot();
|
||||
const cache = useNormalisedCache();
|
||||
|
||||
if (settings.order) {
|
||||
arg.orderAndPagination = { orderOnly: settings.order };
|
||||
if (order) {
|
||||
arg.orderAndPagination = { orderOnly: order };
|
||||
if (arg.orderAndPagination.orderOnly.field === 'sizeInBytes') delete arg.take;
|
||||
}
|
||||
|
||||
|
@ -34,7 +32,6 @@ export function usePathsInfiniteQuery({
|
|||
queryKey: ['search.paths', { library_id: library.uuid, arg }] as const,
|
||||
queryFn: async ({ pageParam, queryKey: [_, { arg }] }) => {
|
||||
const cItem: Extract<ExplorerItem, { type: 'Path' }> = pageParam;
|
||||
const { order } = settings;
|
||||
|
||||
let orderAndPagination: (typeof arg)['orderAndPagination'];
|
||||
|
||||
|
@ -133,7 +130,7 @@ export function usePathsInfiniteQuery({
|
|||
if (lastPage.items.length < arg.take) return undefined;
|
||||
else return lastPage.nodes[arg.take - 1];
|
||||
},
|
||||
onSuccess: () => explorerStore.resetNewThumbnails(),
|
||||
onSuccess,
|
||||
...args
|
||||
});
|
||||
|
|
@ -1,31 +1,26 @@
|
|||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
FilePathOrder,
|
||||
FilePathSearchArgs,
|
||||
useLibraryContext,
|
||||
useNodes,
|
||||
useNormalisedCache,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
|
||||
import { explorerStore } from '../store';
|
||||
import { useNodes, useNormalisedCache } from '../cache';
|
||||
import { FilePathOrder, FilePathSearchArgs } from '../core';
|
||||
import { useLibraryContext } from '../hooks';
|
||||
import { useRspcLibraryContext } from '../rspc';
|
||||
import { UseExplorerInfiniteQueryArgs } from './useExplorerInfiniteQuery';
|
||||
|
||||
export function usePathsOffsetInfiniteQuery({
|
||||
arg,
|
||||
explorerSettings,
|
||||
order,
|
||||
onSuccess,
|
||||
...args
|
||||
}: UseExplorerInfiniteQueryArgs<FilePathSearchArgs, FilePathOrder>) {
|
||||
const take = arg.take ?? 100;
|
||||
|
||||
const { library } = useLibraryContext();
|
||||
const ctx = useRspcLibraryContext();
|
||||
const settings = explorerSettings.useSettingsSnapshot();
|
||||
const cache = useNormalisedCache();
|
||||
|
||||
if (settings.order) {
|
||||
arg.orderAndPagination = { orderOnly: settings.order };
|
||||
if (order) {
|
||||
arg.orderAndPagination = { orderOnly: order };
|
||||
if (arg.orderAndPagination.orderOnly.field === 'sizeInBytes') delete arg.take;
|
||||
}
|
||||
|
||||
|
@ -38,8 +33,6 @@ export function usePathsOffsetInfiniteQuery({
|
|||
}
|
||||
] satisfies [any, any],
|
||||
queryFn: async ({ pageParam, queryKey: [_, { arg }] }) => {
|
||||
const { order } = settings;
|
||||
|
||||
let orderAndPagination: (typeof arg)['orderAndPagination'];
|
||||
|
||||
if (!pageParam) {
|
||||
|
@ -63,7 +56,7 @@ export function usePathsOffsetInfiniteQuery({
|
|||
getNextPageParam: ({ nodes, offset, arg }) => {
|
||||
if (nodes.length >= arg.take) return (offset ?? 0) + 1;
|
||||
},
|
||||
onSuccess: () => explorerStore.resetNewThumbnails(),
|
||||
onSuccess,
|
||||
...args
|
||||
});
|
||||
|
|
@ -33,3 +33,4 @@ export * from './form';
|
|||
export * from './cache';
|
||||
export * from './color';
|
||||
export * from './solid';
|
||||
export * from './explorer';
|
||||
|
|
655
pnpm-lock.yaml
655
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue