[ENG-624] Explorer order by (#831)

added order by + fixed explorer padding situation
This commit is contained in:
Jamie Pine 2023-05-19 08:51:17 -07:00 committed by GitHub
parent 0ba5874947
commit 3880428596
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 92 additions and 57 deletions

View file

@ -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),
}
}
}

View file

@ -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) => (

View file

@ -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>

View file

@ -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';

View file

@ -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>

View file

@ -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 }}

View file

@ -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;
}

View file

@ -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>

View file

@ -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'

View file

@ -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]) => {

View file

@ -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>
</>
);

View file

@ -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.

View file

@ -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 }