[ENG-623] - List view obstruction fix (#839)

* Obstruction fix

* add location_id as type

* PR tweaks

* Update core.ts
This commit is contained in:
ameer2468 2023-05-22 07:25:53 +03:00 committed by GitHub
parent 36b7bf686a
commit 40beeb2a20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 74 additions and 52 deletions

View file

@ -72,7 +72,11 @@ const ListViewItem = memo((props: ListViewItemProps) => {
);
});
export default () => {
interface Props {
listViewHeadersClassName?: string;
}
export default (props: Props) => {
const explorerStore = useExplorerStore();
const dismissibleNoticeStore = useDismissibleNoticeStore();
const { data, scrollRef, onLoadMore, hasNextPage, isFetchingNextPage } =
@ -271,7 +275,6 @@ export default () => {
// Resize view on item selection/deselection
useEffect(() => {
const { selectedRowIndex } = explorerStore;
if (
explorerStore.showInspector &&
typeof lastSelectedIndex.current !== typeof selectedRowIndex
@ -340,7 +343,8 @@ export default () => {
onClick={(e) => e.stopPropagation()}
className={clsx(
'sticky top-0 z-20 table-header-group',
isScrolled && 'top-bar-blur !bg-app/90'
isScrolled && 'top-bar-blur !bg-app/90',
props.listViewHeadersClassName
)}
>
{table.getHeaderGroups().map((headerGroup) => (

View file

@ -84,6 +84,7 @@ interface Props {
hasNextPage?: boolean;
isFetchingNextPage?: boolean;
viewClassName?: string;
listViewHeadersClassName?: string;
scrollRef?: React.RefObject<HTMLDivElement>;
}
@ -118,7 +119,9 @@ export default memo((props: Props) => {
}}
>
{layoutMode === 'grid' && <GridView />}
{layoutMode === 'rows' && <ListView />}
{layoutMode === 'rows' && (
<ListView listViewHeadersClassName={props.listViewHeadersClassName} />
)}
{layoutMode === 'media' && <MediaView />}
</ViewContext.Provider>
</div>

View file

@ -3,6 +3,7 @@ import { ReactNode, useEffect, useMemo } from 'react';
import { useKey } from 'rooks';
import { ExplorerItem, useLibrarySubscription } from '@sd/client';
import { getExplorerStore, useExplorerStore } from '~/hooks';
import useKeyDeleteFile from '~/hooks/useKeyDeleteFile';
import ExplorerContextMenu from './ContextMenu';
import { Inspector } from './Inspector';
import View from './View';
@ -20,12 +21,20 @@ interface Props {
children?: ReactNode;
inspectorClassName?: string;
explorerClassName?: string;
listViewHeadersClassName?: string;
scrollRef?: React.RefObject<HTMLDivElement>;
}
export default function Explorer(props: Props) {
const { selectedRowIndex, ...expStore } = useExplorerStore();
const [{ path }] = useExplorerSearchParams();
const selectedItem = useMemo(() => {
if (selectedRowIndex === null) return null;
return props.items?.[selectedRowIndex] ?? null;
}, [selectedRowIndex, props.items]);
useKeyDeleteFile(selectedItem, expStore.locationId);
useLibrarySubscription(['jobs.newThumbnail'], {
onStarted: () => {
@ -44,12 +53,6 @@ export default function Explorer(props: Props) {
getExplorerStore().selectedRowIndex = null;
}, [path]);
const selectedItem = useMemo(() => {
if (selectedRowIndex === null) return null;
return props.items?.[selectedRowIndex] ?? null;
}, [selectedRowIndex, props.items]);
useKey('Space', (e) => {
e.preventDefault();
@ -68,6 +71,7 @@ export default function Explorer(props: Props) {
data={props.items}
onLoadMore={props.onLoadMore}
hasNextPage={props.hasNextPage}
listViewHeadersClassName={props.listViewHeadersClassName}
isFetchingNextPage={props.isFetchingNextPage}
viewClassName={props.viewClassName}
/>

View file

@ -1,9 +1,14 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import { useEffect, useMemo } from 'react';
import { useKey } from 'rooks';
import { z } from 'zod';
import { useLibraryContext, useLibraryMutation, useLibraryQuery, useRspcLibraryContext } from '@sd/client';
import { Folder, dialogManager } from '@sd/ui';
import {
ExplorerItem,
useLibraryContext,
useLibraryMutation,
useLibraryQuery,
useRspcLibraryContext
} from '@sd/client';
import { Folder } from '@sd/ui';
import {
getExplorerStore,
useExplorerStore,
@ -11,10 +16,10 @@ import {
useZodRouteParams
} from '~/hooks';
import Explorer from '../Explorer';
import DeleteDialog from '../Explorer/File/DeleteDialog';
import { useExplorerOrder, useExplorerSearchParams } from '../Explorer/util';
import { TopBarPortal } from '../TopBar/Portal';
import TopBarOptions from '../TopBar/TopBarOptions';
import useKeyDeleteFile from '~/hooks/useKeyDeleteFile';
const PARAMS = z.object({
id: z.coerce.number()
@ -32,36 +37,22 @@ export const Component = () => {
const { mutate: quickRescan } = useLibraryMutation('locations.quickRescan');
const explorerStore = getExplorerStore();
useEffect(() => {
explorerStore.locationId = location_id;
if (location_id !== null) quickRescan({ location_id, sub_path: path ?? '' });
}, [explorerStore, location_id, path, quickRescan]);
const { query, items } = useItems();
const file = explorerStore.selectedRowIndex !== null && items?.[explorerStore.selectedRowIndex];
useKeyDeleteFile(file as ExplorerItem, location_id)
useKey('Delete', (e) => {
e.preventDefault();
const explorerStore = getExplorerStore();
if (explorerStore.selectedRowIndex === null) return;
const file = items?.[explorerStore.selectedRowIndex];
if (!file) return;
dialogManager.create((dp) => (
<DeleteDialog {...dp} location_id={location_id} path_id={file.item.id} />
));
});
return (
<>
<TopBarPortal
left={
<>
<Folder size={22} className="ml-3 mr-2 -mt-[1px] inline-block" />
<Folder size={22} className="-mt-[1px] ml-3 mr-2 inline-block" />
<span className="text-sm font-medium">
{path ? getLastSectionOfPath(path) : location?.name}
</span>
@ -73,7 +64,7 @@ export const Component = () => {
/>
}
/>
<div className="relative flex w-full flex-col">
<div className="relative flex flex-col w-full">
<Explorer
items={items}
onLoadMore={query.fetchNextPage}
@ -125,7 +116,6 @@ const useItems = () => {
return { query, items };
};
function getLastSectionOfPath(path: string): string | undefined {
if (path.endsWith('/')) {
path = path.slice(0, -1);

View file

@ -135,17 +135,18 @@ export const Component = () => {
/>
}
/>
<Statistics />
<Explorer
inspectorClassName="!pt-0 !fixed !top-[50px] !right-[10px] !w-[260px]"
explorerClassName="!overflow-visible" // required to ensure categories are sticky, remove with caution
viewClassName="!pl-0 !pt-0 !h-auto"
inspectorClassName="!pt-0 !fixed !top-[50px] !right-[10px] !w-[260px]"
viewClassName="!pl-0 !pt-[0] !h-auto !overflow-visible"
explorerClassName="!overflow-visible" //required to keep categories sticky, remove with caution
listViewHeadersClassName="!top-[65px] z-30"
items={items}
onLoadMore={query.fetchNextPage}
hasNextPage={query.hasNextPage}
isFetchingNextPage={query.isFetchingNextPage}
scrollRef={page?.ref}
>
<Statistics />
<div className="no-scrollbar sticky top-0 z-10 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';
@ -156,7 +157,9 @@ export const Component = () => {
icon={getIcon(iconString, isDark)}
items={category.count}
selected={selectedCategory === category.name}
onClick={() => setSelectedCategory(category.name)}
onClick={() => {
setSelectedCategory(category.name);
}}
/>
);
})}

View file

@ -0,0 +1,18 @@
import { useKey } from 'rooks';
import { ExplorerItem } from '@sd/client';
import { dialogManager } from '@sd/ui';
import DeleteDialog from '../app/$libraryId/Explorer/File/DeleteDialog';
const useKeyDeleteFile = (selectedItem: ExplorerItem | null, location_id: number | null) => {
return useKey('Delete', (e) => {
e.preventDefault();
if (!selectedItem || !location_id) return;
dialogManager.create((dp) => (
<DeleteDialog {...dp} location_id={location_id} path_id={selectedItem.item.id} />
));
});
};
export default useKeyDeleteFile;

View file

@ -103,6 +103,8 @@ export type NodeConfig = { id: string; name: string; p2p_port: number | null; p2
export type CategoryItem = { name: string; count: number }
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
/**
* This denotes the `StoredKey` version.
*/
@ -117,12 +119,12 @@ export type EncryptedKey = number[]
export type PeerId = string
export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null }
export type GenerateThumbsForLocationArgs = { id: number; path: string }
export type LibraryConfigWrapped = { uuid: string; config: LibraryConfig }
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
/**
* These parameters define the password-hashing level.
*
@ -199,17 +201,13 @@ export type ObjectSearchArgs = { take?: number | null; tagId?: number | null; cu
export type SetNoteArgs = { id: number; note: string | null }
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
export type FilePathSearchOrdering = { name: boolean } | { sizeInBytes: boolean } | { dateCreated: boolean } | { dateModified: boolean } | { dateIndexed: boolean } | { object: ObjectSearchOrdering }
export type FileCopierJobInit = { source_location_id: number; source_path_id: number; target_location_id: number; target_path: string; target_file_name_suffix: string | null }
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
export type BuildInfo = { version: string; commit: string }
@ -222,12 +220,14 @@ export type Algorithm = "XChaCha20Poly1305" | "Aes256Gcm"
export type ObjectSearchOrdering = { dateAccessed: boolean }
export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string }
export type OwnedOperationItem = { id: any; data: OwnedOperationData }
export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null }
export type CRDTOperationType = SharedOperation | RelationOperation | OwnedOperation
export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string }
/**
* TODO: P2P event for the frontend
*/
@ -289,6 +289,8 @@ export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMa
export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null
export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string }
export type SearchData<T> = { cursor: number[] | null; items: T[] }
export type OptionalRange<T> = { from: T | null; to: T | null }
@ -301,6 +303,8 @@ export type TagAssignArgs = { object_id: number; tag_id: number; unassign: boole
export type ChangeNodeNameArgs = { name: string }
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
/**
* This defines all available password hashing algorithms.
*/
@ -321,14 +325,10 @@ export type AutomountUpdateArgs = { uuid: string; status: boolean }
export type Protected<T> = T
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" | "CompletedWithErrors"
export type RestoreBackupArgs = { password: Protected<string>; secret_key: Protected<string>; path: string }
export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string }
export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData }
/**