mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 13:23:28 +00:00
[ENG-1078] Fix pagination (#1299)
* fix 'load more' breaking * paginate all paginated queries by model id * arrays start at 0 stupid
This commit is contained in:
parent
a92d6c2faf
commit
2d1ce9af03
|
@ -203,7 +203,7 @@ pub enum FilePathObjectCursor {
|
|||
#[derive(Deserialize, Type, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum FilePathCursorVariant {
|
||||
None(file_path::pub_id::Type),
|
||||
None,
|
||||
Name(CursorOrderItem<String>),
|
||||
// SizeInBytes(CursorOrderItem<Vec<u8>>),
|
||||
DateCreated(CursorOrderItem<DateTime<FixedOffset>>),
|
||||
|
@ -222,7 +222,7 @@ pub struct FilePathCursor {
|
|||
#[derive(Deserialize, Type, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ObjectCursor {
|
||||
None(object::pub_id::Type),
|
||||
None,
|
||||
DateAccessed(CursorOrderItem<DateTime<FixedOffset>>),
|
||||
Kind(CursorOrderItem<i32>),
|
||||
}
|
||||
|
@ -256,10 +256,10 @@ impl ObjectOrder {
|
|||
|
||||
#[derive(Deserialize, Type, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum OrderAndPagination<TOrder, TCursor> {
|
||||
pub enum OrderAndPagination<TId, TOrder, TCursor> {
|
||||
OrderOnly(TOrder),
|
||||
Offset { offset: i32, order: Option<TOrder> },
|
||||
Cursor(TCursor),
|
||||
Cursor { id: TId, cursor: TCursor },
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Type, Debug, Default, Clone, Copy)]
|
||||
|
@ -396,7 +396,8 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
struct FilePathSearchArgs {
|
||||
take: u8,
|
||||
#[specta(optional)]
|
||||
order_and_pagination: Option<OrderAndPagination<FilePathOrder, FilePathCursor>>,
|
||||
order_and_pagination:
|
||||
Option<OrderAndPagination<file_path::id::Type, FilePathOrder, FilePathCursor>>,
|
||||
#[serde(default)]
|
||||
filter: FilePathFilterArgs,
|
||||
#[serde(default = "default_group_directories")]
|
||||
|
@ -442,7 +443,7 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
query = query.order_by(order.into_param())
|
||||
}
|
||||
}
|
||||
OrderAndPagination::Cursor(cursor) => {
|
||||
OrderAndPagination::Cursor { id, cursor } => {
|
||||
// This may seem dumb but it's vital!
|
||||
// If we're grouping by directories + all directories have been fetched,
|
||||
// we don't want to include them in the results.
|
||||
|
@ -457,10 +458,21 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
($field:ident, $item:ident) => {{
|
||||
let item = $item;
|
||||
|
||||
query.add_where(match item.order {
|
||||
SortOrder::Asc => file_path::$field::gt(item.data),
|
||||
SortOrder::Desc => file_path::$field::lt(item.data),
|
||||
});
|
||||
let data = item.data.clone();
|
||||
|
||||
query.add_where(or![
|
||||
match item.order {
|
||||
SortOrder::Asc => file_path::$field::gt(data),
|
||||
SortOrder::Desc => file_path::$field::lt(data),
|
||||
},
|
||||
prisma_client_rust::and![
|
||||
file_path::$field::equals(Some(item.data)),
|
||||
match item.order {
|
||||
SortOrder::Asc => file_path::id::gt(id),
|
||||
SortOrder::Desc => file_path::id::lt(id),
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
query = query
|
||||
.order_by(file_path::$field::order(item.order.into()));
|
||||
|
@ -468,8 +480,8 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
}
|
||||
|
||||
match cursor.variant {
|
||||
FilePathCursorVariant::None(item) => {
|
||||
query = query.cursor(file_path::pub_id::equals(item));
|
||||
FilePathCursorVariant::None => {
|
||||
query.add_where(file_path::id::gt(id));
|
||||
}
|
||||
FilePathCursorVariant::Name(item) => arm!(name, item),
|
||||
FilePathCursorVariant::DateCreated(item) => {
|
||||
|
@ -511,8 +523,8 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
}
|
||||
};
|
||||
|
||||
query = query
|
||||
.order_by(file_path::pub_id::order(prisma::SortOrder::Asc));
|
||||
query =
|
||||
query.order_by(file_path::id::order(prisma::SortOrder::Asc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -574,7 +586,8 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
struct ObjectSearchArgs {
|
||||
take: u8,
|
||||
#[specta(optional)]
|
||||
order_and_pagination: Option<OrderAndPagination<ObjectOrder, ObjectCursor>>,
|
||||
order_and_pagination:
|
||||
Option<OrderAndPagination<object::id::Type, ObjectOrder, ObjectCursor>>,
|
||||
#[serde(default)]
|
||||
filter: ObjectFilterArgs,
|
||||
}
|
||||
|
@ -607,15 +620,26 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
query = query.order_by(order.into_param())
|
||||
}
|
||||
}
|
||||
OrderAndPagination::Cursor(cursor) => {
|
||||
OrderAndPagination::Cursor { id, cursor } => {
|
||||
macro_rules! arm {
|
||||
($field:ident, $item:ident) => {{
|
||||
let item = $item;
|
||||
|
||||
query.add_where(match item.order {
|
||||
SortOrder::Asc => object::$field::gt(item.data),
|
||||
SortOrder::Desc => object::$field::lt(item.data),
|
||||
});
|
||||
let data = item.data.clone();
|
||||
|
||||
query.add_where(or![
|
||||
match item.order {
|
||||
SortOrder::Asc => object::$field::gt(data),
|
||||
SortOrder::Desc => object::$field::lt(data),
|
||||
},
|
||||
prisma_client_rust::and![
|
||||
object::$field::equals(Some(item.data)),
|
||||
match item.order {
|
||||
SortOrder::Asc => object::id::gt(id),
|
||||
SortOrder::Desc => object::id::lt(id),
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
query = query
|
||||
.order_by(object::$field::order(item.order.into()));
|
||||
|
@ -623,8 +647,8 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
}
|
||||
|
||||
match cursor {
|
||||
ObjectCursor::None(item) => {
|
||||
query = query.cursor(object::pub_id::equals(item));
|
||||
ObjectCursor::None => {
|
||||
query.add_where(object::id::gt(id));
|
||||
}
|
||||
ObjectCursor::Kind(item) => arm!(kind, item),
|
||||
ObjectCursor::DateAccessed(item) => arm!(date_accessed, item),
|
||||
|
|
3
interface/app/$libraryId/Explorer/queries/index.ts
Normal file
3
interface/app/$libraryId/Explorer/queries/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './useExplorerInfiniteQuery';
|
||||
export * from './usePathsInfiniteQuery';
|
||||
export * from './useObjectsInfiniteQuery';
|
|
@ -0,0 +1,10 @@
|
|||
import { UseInfiniteQueryOptions } from '@tanstack/react-query';
|
||||
import { ExplorerItem, LibraryConfigWrapped, SearchData } from '@sd/client';
|
||||
import { Ordering } from '../store';
|
||||
import { UseExplorerSettings } from '../useExplorer';
|
||||
|
||||
export type UseExplorerInfiniteQueryArgs<TArg, TOrder extends Ordering> = {
|
||||
library: LibraryConfigWrapped;
|
||||
arg: TArg;
|
||||
settings: UseExplorerSettings<TOrder>;
|
||||
} & Pick<UseInfiniteQueryOptions<SearchData<ExplorerItem>>, 'enabled'>;
|
|
@ -1,27 +1,19 @@
|
|||
import { UseInfiniteQueryOptions, useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
ExplorerItem,
|
||||
LibraryConfigWrapped,
|
||||
ObjectCursor,
|
||||
ObjectOrder,
|
||||
ObjectSearchArgs,
|
||||
OrderAndPagination,
|
||||
SearchData,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
import { getExplorerStore } from './store';
|
||||
import { UseExplorerSettings } from './useExplorer';
|
||||
import { UseExplorerInfiniteQueryArgs } from './useExplorerInfiniteQuery';
|
||||
|
||||
export function useObjectsInfiniteQuery({
|
||||
library,
|
||||
arg,
|
||||
settings,
|
||||
...args
|
||||
}: {
|
||||
library: LibraryConfigWrapped;
|
||||
arg: ObjectSearchArgs;
|
||||
settings: UseExplorerSettings<ObjectOrder>;
|
||||
} & Pick<UseInfiniteQueryOptions<SearchData<ExplorerItem>>, 'enabled'>) {
|
||||
}: UseExplorerInfiniteQueryArgs<ObjectSearchArgs, ObjectOrder>) {
|
||||
const ctx = useRspcLibraryContext();
|
||||
const explorerSettings = settings.useSettingsSnapshot();
|
||||
|
||||
|
@ -35,14 +27,14 @@ export function useObjectsInfiniteQuery({
|
|||
const cItem: Extract<ExplorerItem, { type: 'Object' }> = pageParam;
|
||||
const { order } = explorerSettings;
|
||||
|
||||
let orderAndPagination: OrderAndPagination<ObjectOrder, ObjectCursor> | undefined;
|
||||
let orderAndPagination: (typeof arg)['orderAndPagination'];
|
||||
|
||||
if (!cItem) {
|
||||
if (order) orderAndPagination = { orderOnly: order };
|
||||
} else {
|
||||
let cursor: ObjectCursor | undefined;
|
||||
|
||||
if (!order) cursor = { none: [] };
|
||||
if (!order) cursor = 'none';
|
||||
else if (cItem) {
|
||||
const direction = order.value;
|
||||
|
||||
|
@ -61,7 +53,7 @@ export function useObjectsInfiniteQuery({
|
|||
}
|
||||
}
|
||||
|
||||
if (cursor) orderAndPagination = { cursor };
|
||||
if (cursor) orderAndPagination = { cursor: { cursor, id: cItem.item.id } };
|
||||
}
|
||||
|
||||
arg.orderAndPagination = orderAndPagination;
|
||||
|
@ -70,7 +62,7 @@ export function useObjectsInfiniteQuery({
|
|||
},
|
||||
getNextPageParam: (lastPage) => {
|
||||
if (lastPage.items.length < arg.take) return undefined;
|
||||
else return lastPage.items[arg.take];
|
||||
else return lastPage.items[arg.take - 1];
|
||||
},
|
||||
...args
|
||||
});
|
|
@ -1,29 +1,21 @@
|
|||
import { UseInfiniteQueryOptions, useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
ExplorerItem,
|
||||
FilePathCursor,
|
||||
FilePathCursorVariant,
|
||||
FilePathObjectCursor,
|
||||
FilePathOrder,
|
||||
FilePathSearchArgs,
|
||||
LibraryConfigWrapped,
|
||||
OrderAndPagination,
|
||||
SearchData,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
import { getExplorerStore } from './store';
|
||||
import { UseExplorerSettings } from './useExplorer';
|
||||
import { getExplorerStore } from '../store';
|
||||
import { UseExplorerInfiniteQueryArgs } from './useExplorerInfiniteQuery';
|
||||
|
||||
export function usePathsInfiniteQuery({
|
||||
library,
|
||||
arg,
|
||||
settings,
|
||||
...args
|
||||
}: {
|
||||
library: LibraryConfigWrapped;
|
||||
arg: FilePathSearchArgs;
|
||||
settings: UseExplorerSettings<FilePathOrder>;
|
||||
} & Pick<UseInfiniteQueryOptions<SearchData<ExplorerItem>>, 'enabled'>) {
|
||||
}: UseExplorerInfiniteQueryArgs<FilePathSearchArgs, FilePathOrder>) {
|
||||
const ctx = useRspcLibraryContext();
|
||||
const explorerSettings = settings.useSettingsSnapshot();
|
||||
|
||||
|
@ -37,14 +29,14 @@ export function usePathsInfiniteQuery({
|
|||
const cItem: Extract<ExplorerItem, { type: 'Path' }> = pageParam;
|
||||
const { order } = explorerSettings;
|
||||
|
||||
let orderAndPagination: OrderAndPagination<FilePathOrder, FilePathCursor> | undefined;
|
||||
let orderAndPagination: (typeof arg)['orderAndPagination'];
|
||||
|
||||
if (!cItem) {
|
||||
if (order) orderAndPagination = { orderOnly: order };
|
||||
} else {
|
||||
let variant: FilePathCursorVariant | undefined;
|
||||
|
||||
if (!order) variant = { none: [] };
|
||||
if (!order) variant = 'none';
|
||||
else if (cItem) {
|
||||
switch (order.field) {
|
||||
case 'name': {
|
||||
|
@ -136,7 +128,7 @@ export function usePathsInfiniteQuery({
|
|||
|
||||
if (variant)
|
||||
orderAndPagination = {
|
||||
cursor: { variant, isDir: cItem.item.is_dir }
|
||||
cursor: { cursor: { variant, isDir: cItem.item.is_dir }, id: cItem.item.id }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -146,7 +138,7 @@ export function usePathsInfiniteQuery({
|
|||
},
|
||||
getNextPageParam: (lastPage) => {
|
||||
if (lastPage.items.length < arg.take) return undefined;
|
||||
else return lastPage.items[arg.take];
|
||||
else return lastPage.items[arg.take - 1];
|
||||
},
|
||||
onSuccess: () => getExplorerStore().resetNewThumbnails(),
|
||||
...args
|
|
@ -18,9 +18,9 @@ import { useKeyDeleteFile, useZodRouteParams } from '~/hooks';
|
|||
import Explorer from '../Explorer';
|
||||
import { ExplorerContextProvider } from '../Explorer/Context';
|
||||
import { DefaultTopBarOptions } from '../Explorer/TopBarOptions';
|
||||
import { usePathsInfiniteQuery } from '../Explorer/queries';
|
||||
import { createDefaultExplorerSettings, filePathOrderingKeysSchema } from '../Explorer/store';
|
||||
import { UseExplorerSettings, useExplorer, useExplorerSettings } from '../Explorer/useExplorer';
|
||||
import { usePathsInfiniteQuery } from '../Explorer/usePathsInfiniteQuery';
|
||||
import { useExplorerSearchParams } from '../Explorer/util';
|
||||
import { TopBarPortal } from '../TopBar/Portal';
|
||||
import LocationOptions from './LocationOptions';
|
||||
|
|
|
@ -11,14 +11,13 @@ import {
|
|||
useLibraryQuery,
|
||||
useRspcLibraryContext
|
||||
} from '@sd/client';
|
||||
import { useObjectsInfiniteQuery, usePathsInfiniteQuery } from '../Explorer/queries';
|
||||
import {
|
||||
createDefaultExplorerSettings,
|
||||
filePathOrderingKeysSchema,
|
||||
objectOrderingKeysSchema
|
||||
} from '../Explorer/store';
|
||||
import { useExplorer, useExplorerSettings } from '../Explorer/useExplorer';
|
||||
import { useObjectsInfiniteQuery } from '../Explorer/useObjectsInfiniteQuery';
|
||||
import { usePathsInfiniteQuery } from '../Explorer/usePathsInfiniteQuery';
|
||||
import { usePageLayoutContext } from '../PageLayout/Context';
|
||||
|
||||
export const IconForCategory: Partial<Record<Category, string>> = {
|
||||
|
|
|
@ -168,7 +168,7 @@ export type FilePath = { id: number; pub_id: number[]; is_dir: boolean | null; c
|
|||
|
||||
export type FilePathCursor = { isDir: boolean; variant: FilePathCursorVariant }
|
||||
|
||||
export type FilePathCursorVariant = { none: number[] } | { name: CursorOrderItem<string> } | { dateCreated: CursorOrderItem<string> } | { dateModified: CursorOrderItem<string> } | { dateIndexed: CursorOrderItem<string> } | { object: FilePathObjectCursor }
|
||||
export type FilePathCursorVariant = "none" | { name: CursorOrderItem<string> } | { dateCreated: CursorOrderItem<string> } | { dateModified: CursorOrderItem<string> } | { dateIndexed: CursorOrderItem<string> } | { object: FilePathObjectCursor }
|
||||
|
||||
export type FilePathFilterArgs = { locationId?: number | null; search?: string | null; extension?: string | null; createdAt?: OptionalRange<string>; path?: string | null; object?: ObjectFilterArgs | null }
|
||||
|
||||
|
@ -176,7 +176,7 @@ export type FilePathObjectCursor = { dateAccessed: CursorOrderItem<string> } | {
|
|||
|
||||
export type FilePathOrder = { field: "name"; value: SortOrder } | { field: "sizeInBytes"; value: SortOrder } | { field: "dateCreated"; value: SortOrder } | { field: "dateModified"; value: SortOrder } | { field: "dateIndexed"; value: SortOrder } | { field: "object"; value: ObjectOrder }
|
||||
|
||||
export type FilePathSearchArgs = { take: number; orderAndPagination?: OrderAndPagination<FilePathOrder, FilePathCursor> | null; filter?: FilePathFilterArgs; groupDirectories?: boolean }
|
||||
export type FilePathSearchArgs = { take: number; orderAndPagination?: OrderAndPagination<number, FilePathOrder, FilePathCursor> | null; filter?: FilePathFilterArgs; groupDirectories?: boolean }
|
||||
|
||||
export type FilePathWithObject = { id: number; pub_id: number[]; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location_id: number | null; materialized_path: string | null; name: string | null; extension: string | null; size_in_bytes: string | null; size_in_bytes_bytes: number[] | null; inode: number[] | null; device: number[] | null; object_id: number | null; key_id: number | null; date_created: string | null; date_modified: string | null; date_indexed: string | null; object: Object | null }
|
||||
|
||||
|
@ -307,7 +307,7 @@ export type NotificationId = { type: "library"; id: [string, number] } | { type:
|
|||
|
||||
export type Object = { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null }
|
||||
|
||||
export type ObjectCursor = { none: number[] } | { dateAccessed: CursorOrderItem<string> } | { kind: CursorOrderItem<number> }
|
||||
export type ObjectCursor = "none" | { dateAccessed: CursorOrderItem<string> } | { kind: CursorOrderItem<number> }
|
||||
|
||||
export type ObjectFilterArgs = { favorite?: boolean | null; hidden?: ObjectHiddenFilter; dateAccessed?: MaybeNot<string | null> | null; kind?: number[]; tags?: number[]; category?: Category | null }
|
||||
|
||||
|
@ -315,7 +315,7 @@ export type ObjectHiddenFilter = "exclude" | "include"
|
|||
|
||||
export type ObjectOrder = { field: "dateAccessed"; value: SortOrder } | { field: "kind"; value: SortOrder }
|
||||
|
||||
export type ObjectSearchArgs = { take: number; orderAndPagination?: OrderAndPagination<ObjectOrder, ObjectCursor> | null; filter?: ObjectFilterArgs }
|
||||
export type ObjectSearchArgs = { take: number; orderAndPagination?: OrderAndPagination<number, ObjectOrder, ObjectCursor> | null; filter?: ObjectFilterArgs }
|
||||
|
||||
export type ObjectValidatorArgs = { id: number; path: string }
|
||||
|
||||
|
@ -329,7 +329,7 @@ export type OperatingSystem = "Windows" | "Linux" | "MacOS" | "Ios" | "Android"
|
|||
|
||||
export type OptionalRange<T> = { from: T | null; to: T | null }
|
||||
|
||||
export type OrderAndPagination<TOrder, TCursor> = { orderOnly: TOrder } | { offset: { offset: number; order: TOrder | null } } | { cursor: TCursor }
|
||||
export type OrderAndPagination<TId, TOrder, TCursor> = { orderOnly: TOrder } | { offset: { offset: number; order: TOrder | null } } | { cursor: { id: TId; cursor: TCursor } }
|
||||
|
||||
export type Orientation = "Normal" | "MirroredHorizontal" | "CW90" | "MirroredVertical" | "MirroredHorizontalAnd270CW" | "MirroredHorizontalAnd90CW" | "CW180" | "CW270"
|
||||
|
||||
|
|
Loading…
Reference in a new issue