mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 13:23:28 +00:00
[ENG-624] Explorer order by (#831)
added order by + fixed explorer padding situation
This commit is contained in:
parent
0ba5874947
commit
3880428596
|
@ -42,12 +42,20 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
#[specta(inline)]
|
||||
enum Ordering {
|
||||
Name(bool),
|
||||
SizeInBytes(bool),
|
||||
DateCreated(bool),
|
||||
DateModified(bool),
|
||||
DateIndexed(bool),
|
||||
}
|
||||
|
||||
impl Ordering {
|
||||
fn get_direction(&self) -> Direction {
|
||||
match self {
|
||||
Self::Name(v) => v,
|
||||
Self::SizeInBytes(v) => v,
|
||||
Self::DateCreated(v) => v,
|
||||
Self::DateModified(v) => v,
|
||||
Self::DateIndexed(v) => v,
|
||||
}
|
||||
.then_some(Direction::Asc)
|
||||
.unwrap_or(Direction::Desc)
|
||||
|
@ -57,6 +65,10 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
|||
use file_path::*;
|
||||
match self {
|
||||
Self::Name(_) => name::order(dir),
|
||||
Self::SizeInBytes(_) => size_in_bytes::order(dir),
|
||||
Self::DateCreated(_) => date_created::order(dir),
|
||||
Self::DateModified(_) => date_modified::order(dir),
|
||||
Self::DateIndexed(_) => date_indexed::order(dir),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import clsx from 'clsx';
|
||||
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
@ -71,6 +72,8 @@ const GridViewItem = memo(({ data, selected, index, ...props }: GridViewItemProp
|
|||
);
|
||||
});
|
||||
|
||||
const LEFT_PADDING = 14;
|
||||
|
||||
export default () => {
|
||||
const explorerStore = useExplorerStore();
|
||||
const { data, scrollRef, onLoadMore, hasNextPage, isFetchingNextPage } =
|
||||
|
@ -107,7 +110,7 @@ export default () => {
|
|||
|
||||
function handleWindowResize() {
|
||||
if (scrollRef.current) {
|
||||
setWidth(scrollRef.current.offsetWidth);
|
||||
setWidth(scrollRef.current.offsetWidth - LEFT_PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +189,8 @@ export default () => {
|
|||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
height: `${rowVirtualizer.getTotalSize()}px`
|
||||
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||
marginLeft: `${LEFT_PADDING - 4}px`
|
||||
}}
|
||||
>
|
||||
{virtualRows.map((virtualRow) => (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnSizingState,
|
||||
|
@ -162,12 +163,12 @@ export default () => {
|
|||
return aName === bName
|
||||
? 0
|
||||
: aName > bName
|
||||
? desc
|
||||
? 1
|
||||
: -1
|
||||
: desc
|
||||
? -1
|
||||
: 1;
|
||||
? desc
|
||||
? 1
|
||||
: -1
|
||||
: desc
|
||||
? -1
|
||||
: 1;
|
||||
}
|
||||
|
||||
return aDate > bDate ? 1 : -1;
|
||||
|
@ -225,13 +226,13 @@ export default () => {
|
|||
...sizing,
|
||||
...(scrollWidth && nameWidth
|
||||
? {
|
||||
Name:
|
||||
nameWidth +
|
||||
scrollWidth -
|
||||
paddingX * 2 -
|
||||
scrollBarWidth -
|
||||
tableLength
|
||||
}
|
||||
Name:
|
||||
nameWidth +
|
||||
scrollWidth -
|
||||
paddingX * 2 -
|
||||
scrollBarWidth -
|
||||
tableLength
|
||||
}
|
||||
: {})
|
||||
};
|
||||
});
|
||||
|
@ -360,8 +361,8 @@ export default () => {
|
|||
i === 0
|
||||
? size + paddingX
|
||||
: i === headerGroup.headers.length - 1
|
||||
? size - paddingX
|
||||
: size
|
||||
? size - paddingX
|
||||
: size
|
||||
}}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
>
|
||||
|
@ -381,16 +382,16 @@ export default () => {
|
|||
{(i !== headerGroup.headers.length - 1 ||
|
||||
(i === headerGroup.headers.length - 1 &&
|
||||
!locked)) && (
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => {
|
||||
setLocked(false);
|
||||
header.getResizeHandler()(e);
|
||||
}}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
className="absolute right-0 h-[70%] w-2 cursor-col-resize border-r border-app-line/50"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => {
|
||||
setLocked(false);
|
||||
header.getResizeHandler()(e);
|
||||
}}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
className="absolute right-0 h-[70%] w-2 cursor-col-resize border-r border-app-line/50"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import clsx from 'clsx';
|
||||
import { ArrowsOutSimple } from 'phosphor-react';
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
import { useState } from 'react';
|
||||
import { RadixCheckbox, Select, SelectOption, Slider, tw } from '@sd/ui';
|
||||
import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
|
||||
import { ExplorerDirection, ExplorerOrderByKeys, getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
|
||||
import { getExplorerConfigStore, useExplorerConfigStore } from '~/hooks/useExplorerConfigStore';
|
||||
|
||||
const Heading = tw.div`text-ink-dull text-xs font-semibold`;
|
||||
const Subheading = tw.div`text-ink-dull mb-1 text-xs font-medium`;
|
||||
|
||||
const sortOptions = {
|
||||
const sortOptions: Record<ExplorerOrderByKeys, string> = {
|
||||
none: 'None',
|
||||
name: 'Name',
|
||||
kind: 'Kind',
|
||||
favorite: 'Favorite',
|
||||
date_created: 'Date Created',
|
||||
date_modified: 'Date Modified',
|
||||
date_last_opened: 'Date Last Opened'
|
||||
sizeInBytes: 'Size',
|
||||
dateCreated: 'Date created',
|
||||
dateModified: 'Date modified',
|
||||
dateIndexed: 'Date indexed'
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [stackBy, setStackBy] = useState('kind');
|
||||
|
||||
const explorerStore = useExplorerStore();
|
||||
const explorerConfig = useExplorerConfigStore();
|
||||
|
||||
return (
|
||||
<div className="p-4 ">
|
||||
<div className="p-4">
|
||||
<Subheading>Item size</Subheading>
|
||||
{explorerStore.layoutMode === 'media' ? (
|
||||
<Slider
|
||||
|
@ -52,7 +49,7 @@ export default () => {
|
|||
<div className="my-2 mt-4 grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col">
|
||||
<Subheading>Sort by</Subheading>
|
||||
<Select value={sortBy} size="sm" onChange={setSortBy}>
|
||||
<Select value={explorerStore.orderBy} size="sm" className='w-full' onChange={(value) => getExplorerStore().orderBy = value as ExplorerOrderByKeys}>
|
||||
{Object.entries(sortOptions).map(([value, text]) => (
|
||||
<SelectOption key={value} value={value}>
|
||||
{text}
|
||||
|
@ -61,11 +58,10 @@ export default () => {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<Subheading>Stack by</Subheading>
|
||||
<Select value={stackBy} size="sm" onChange={setStackBy}>
|
||||
<SelectOption value="kind">Kind</SelectOption>
|
||||
<SelectOption value="location">Location</SelectOption>
|
||||
<SelectOption value="node">Node</SelectOption>
|
||||
<Subheading>Direction</Subheading>
|
||||
<Select value={explorerStore.orderByDirection} size="sm" className='w-full' onChange={(value) => getExplorerStore().orderByDirection = value as ExplorerDirection}>
|
||||
<SelectOption value="asc">Asc</SelectOption>
|
||||
<SelectOption value="desc">Desc</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -101,7 +101,7 @@ export default memo((props: Props) => {
|
|||
ref={props.scrollRef || scrollRef}
|
||||
className={clsx(
|
||||
'custom-scroll explorer-scroll h-screen',
|
||||
layoutMode === 'grid' && 'overflow-x-hidden pl-4',
|
||||
layoutMode === 'grid' && 'overflow-x-hidden',
|
||||
props.viewClassName
|
||||
)}
|
||||
style={{ paddingTop: TOP_BAR_HEIGHT }}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { z } from 'zod';
|
||||
import { ExplorerItem, ObjectKind, ObjectKindKey, isObject, isPath } from '@sd/client';
|
||||
import { useZodSearchParams } from '~/hooks';
|
||||
import { ExplorerItem, ObjectKind, ObjectKindKey, Ordering, isObject, isPath } from '@sd/client';
|
||||
import { useExplorerStore, useZodSearchParams } from '~/hooks';
|
||||
|
||||
export function getExplorerItemData(data: ExplorerItem, hasNewThumbnail: boolean) {
|
||||
export function getExplorerItemData(data: ExplorerItem, hasNewThumbnail?: boolean) {
|
||||
const objectData = getItemObject(data);
|
||||
const filePath = getItemFilePath(data);
|
||||
|
||||
|
@ -15,6 +15,14 @@ export function getExplorerItemData(data: ExplorerItem, hasNewThumbnail: boolean
|
|||
};
|
||||
}
|
||||
|
||||
export function useExplorerOrder(): Ordering | undefined {
|
||||
const explorerStore = useExplorerStore();
|
||||
|
||||
if (explorerStore.orderBy === 'none') return undefined;
|
||||
|
||||
return { [explorerStore.orderBy]: explorerStore.orderByDirection === 'asc' } as Ordering;
|
||||
}
|
||||
|
||||
export function getItemObject(data: ExplorerItem) {
|
||||
return isObject(data) ? data.item : data.item.object;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export const Component = () => {
|
|||
style={{ paddingTop: TOP_BAR_HEIGHT }}
|
||||
>
|
||||
<PageContext.Provider value={{ ref }}>
|
||||
<div className="flex h-screen w-full flex-col p-5">
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<Outlet />
|
||||
</div>
|
||||
</PageContext.Provider>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
|
|||
import { useExplorerTopBarOptions } from '~/hooks/useExplorerTopBarOptions';
|
||||
import Explorer from '../Explorer';
|
||||
import DeleteDialog from '../Explorer/File/DeleteDialog';
|
||||
import { useExplorerSearchParams } from '../Explorer/util';
|
||||
import { useExplorerOrder, useExplorerSearchParams } from '../Explorer/util';
|
||||
import TopBarChildren from '../TopBar/TopBarChildren';
|
||||
|
||||
const PARAMS = z.object({
|
||||
|
@ -81,6 +81,7 @@ const useItems = () => {
|
|||
{
|
||||
library_id: library.uuid,
|
||||
arg: {
|
||||
order: useExplorerOrder(),
|
||||
locationId,
|
||||
take,
|
||||
...(explorerState.layoutMode === 'media'
|
||||
|
|
|
@ -87,7 +87,7 @@ export default () => {
|
|||
});
|
||||
mounted = true;
|
||||
return (
|
||||
<div className="flex w-full">
|
||||
<div className="flex w-full px-5 pt-4">
|
||||
{/* STAT CONTAINER */}
|
||||
<div className="-mb-1 flex h-20 overflow-hidden">
|
||||
{Object.entries(stats?.data || []).map(([key, value]) => {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { useMemo, useState } from 'react';
|
|||
import 'react-loading-skeleton/dist/skeleton.css';
|
||||
import { useExplorerStore, useExplorerTopBarOptions } from '~/hooks';
|
||||
import Explorer from '../Explorer';
|
||||
import { SEARCH_PARAMS } from '../Explorer/util';
|
||||
import { SEARCH_PARAMS, useExplorerOrder } from '../Explorer/util';
|
||||
import { usePageLayout } from '../PageLayout';
|
||||
import TopBarChildren from '../TopBar/TopBarChildren';
|
||||
import CategoryButton from '../overview/CategoryButton';
|
||||
|
@ -79,6 +79,7 @@ export const Component = () => {
|
|||
{
|
||||
library_id: library.uuid,
|
||||
arg: {
|
||||
order: useExplorerOrder(),
|
||||
favorite: isFavoritesCategory ? true : undefined,
|
||||
...(explorerStore.layoutMode === 'media'
|
||||
? { kind: [5, 7].includes(kind) ? [kind] : isFavoritesCategory ? [5, 7] : [5, 7, kind] }
|
||||
|
@ -91,7 +92,7 @@ export const Component = () => {
|
|||
'search.paths',
|
||||
{
|
||||
...queryKey[1].arg,
|
||||
cursor,
|
||||
cursor
|
||||
},
|
||||
]),
|
||||
getNextPageParam: (lastPage) => lastPage.cursor ?? undefined
|
||||
|
@ -123,8 +124,9 @@ export const Component = () => {
|
|||
isFetchingNextPage={query.isFetchingNextPage}
|
||||
scrollRef={page?.ref}
|
||||
>
|
||||
|
||||
<Statistics />
|
||||
<div className="no-scrollbar sticky top-0 z-50 mt-4 flex space-x-[1px] overflow-x-scroll bg-app/90 py-1.5 backdrop-blur">
|
||||
<div className="no-scrollbar sticky top-0 z-50 mt-2 flex space-x-[1px] overflow-x-scroll bg-app/90 px-5 py-1.5 backdrop-blur">
|
||||
{categories.data?.map((category) => {
|
||||
const iconString = CategoryToIcon[category.name] || 'Document';
|
||||
const icon = icons[iconString as keyof typeof icons];
|
||||
|
@ -140,6 +142,7 @@ export const Component = () => {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
</Explorer>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { proxy, useSnapshot } from 'valtio';
|
||||
import { ExplorerItem } from '@sd/client';
|
||||
import { ExplorerItem, Ordering } from '@sd/client';
|
||||
import { resetStore } from '@sd/client/src/stores/util';
|
||||
|
||||
type UnionKeys<T> = T extends any ? keyof T : never;
|
||||
|
||||
export type ExplorerLayoutMode = 'rows' | 'grid' | 'columns' | 'media';
|
||||
|
||||
export enum ExplorerKind {
|
||||
|
@ -12,6 +14,10 @@ export enum ExplorerKind {
|
|||
|
||||
export type CutCopyType = 'Cut' | 'Copy';
|
||||
|
||||
export type ExplorerOrderByKeys = UnionKeys<Ordering> | 'none';
|
||||
|
||||
export type ExplorerDirection = 'asc' | 'desc';
|
||||
|
||||
const state = {
|
||||
locationId: null as number | null,
|
||||
layoutMode: 'grid' as ExplorerLayoutMode,
|
||||
|
@ -35,7 +41,10 @@ const state = {
|
|||
quickViewObject: null as ExplorerItem | null,
|
||||
isRenaming: false,
|
||||
mediaColumns: 8,
|
||||
mediaAspectSquare: true
|
||||
mediaAspectSquare: true,
|
||||
orderBy: 'dateCreated' as ExplorerOrderByKeys,
|
||||
orderByDirection: 'desc' as ExplorerDirection,
|
||||
groupBy: 'none',
|
||||
};
|
||||
|
||||
// Keep the private and use `useExplorerState` or `getExplorerStore` or you will get production build issues.
|
||||
|
|
|
@ -200,7 +200,7 @@ export type CRDTOperation = { node: string; timestamp: number; id: string; typ:
|
|||
*/
|
||||
export type Salt = number[]
|
||||
|
||||
export type Ordering = { name: boolean }
|
||||
export type Ordering = { name: boolean } | { sizeInBytes: boolean } | { dateCreated: boolean } | { dateModified: boolean } | { dateIndexed: boolean }
|
||||
|
||||
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
|
||||
|
||||
|
|
Loading…
Reference in a new issue