[ENG-379] Explorer item resizer (#580)

* added item resizer
fixed explorer store bug
refactored file image component

* better sizing for videos

* fixed inspector width issue

* remove console.log

* added column titles to list view + extra details

* moved util

* remove imports

* Update packages/interface/src/components/explorer/FileColumns.tsx

Co-authored-by: Brendan Allan <brendonovich@outlook.com>

* address issues

* fix extension in file list name

* Update packages/interface/src/components/explorer/FileColumns.tsx

---------

Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
Jamie Pine 2023-02-24 22:16:57 -08:00 committed by GitHub
parent 0b9005fdef
commit 677e1b63e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 284 additions and 208 deletions

View file

@ -118,18 +118,6 @@ pub(crate) fn mount() -> impl RouterBuilderLike<Ctx> {
.exec()
.await?;
// library
// .queue_job(Job::new(
// ThumbnailJobInit {
// location_id: location.id,
// // recursive: false, // TODO: do this
// root_path: PathBuf::from(&directory.materialized_path),
// background: true,
// },
// ThumbnailJob {},
// ))
// .await;
let mut items = Vec::with_capacity(file_paths.len());
for file_path in file_paths {
@ -223,7 +211,7 @@ pub(crate) fn mount() -> impl RouterBuilderLike<Ctx> {
async_stream::stream! {
let online = location_manager.get_online().await;
dbg!(&online);
// dbg!(&online);
yield online;
while let Ok(locations) = rx.recv().await {

View file

@ -9,7 +9,7 @@ use tokio::fs::File;
use crate::job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext};
use super::{context_menu_fs_info, FsInfo, BYTES};
use super::{context_menu_fs_info, FsInfo, BYTES_EXT};
pub struct FileDecryptorJob;
#[derive(Serialize, Deserialize, Debug)]
pub struct FileDecryptorJobState {}
@ -74,7 +74,7 @@ impl StatefulJob for FileDecryptorJob {
|| {
let mut path = info.fs_path.clone();
let extension = path.extension().map_or("decrypted", |ext| {
if ext == BYTES {
if ext == BYTES_EXT {
""
} else {
"decrypted"

View file

@ -15,7 +15,7 @@ use specta::Type;
use tokio::{fs::File, io::AsyncReadExt};
use tracing::warn;
use super::{context_menu_fs_info, FsInfo};
use super::{context_menu_fs_info, FsInfo, BYTES_EXT};
pub struct FileEncryptorJob;
@ -108,7 +108,7 @@ impl StatefulJob for FileEncryptorJob {
"path contents when converted to string",
),
})?
.to_string() + ".bytes",
.to_string() + BYTES_EXT,
)
},
)?;

View file

@ -30,6 +30,8 @@ pub enum ObjectType {
Directory,
}
pub const BYTES_EXT: &str = ".bytes";
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FsInfo {
pub path_data: file_path_with_object::Data,

View file

@ -1,3 +1,4 @@
import clsx from 'clsx';
import {
ArrowBendUpRight,
Clipboard,
@ -209,11 +210,16 @@ export function ExplorerContextMenu(props: PropsWithChildren) {
);
}
export interface FileItemContextMenuProps extends PropsWithChildren {
export interface ExplorerItemContextMenuProps extends PropsWithChildren {
data: ExplorerItem;
className?: string;
}
export function FileItemContextMenu({ data, ...props }: FileItemContextMenuProps) {
export function ExplorerItemContextMenu({
data,
className,
...props
}: ExplorerItemContextMenuProps) {
const { library } = useLibraryContext();
const store = useExplorerStore();
const params = useExplorerParams();
@ -227,7 +233,7 @@ export function FileItemContextMenu({ data, ...props }: FileItemContextMenuProps
const copyFiles = useLibraryMutation('files.copyFiles');
return (
<div className="relative">
<div className={clsx('relative', className)}>
<CM.ContextMenu trigger={props.children}>
<CM.Item
label="Open"

View file

@ -1,5 +1,6 @@
import { PropsWithChildren, useState } from 'react';
import { Select, SelectOption } from '@sd/ui';
import { getExplorerStore, useExplorerStore } from '../../hooks/useExplorerStore';
import Slider from '../primitive/Slider';
function Heading({ children }: PropsWithChildren) {
@ -22,13 +23,23 @@ const sortOptions = {
export function ExplorerOptionsPanel() {
const [sortBy, setSortBy] = useState('name');
const [stackBy, setStackBy] = useState('kind');
const [size, setSize] = useState([50]);
const explorerStore = useExplorerStore();
return (
<div className="p-4 ">
{/* <Heading>Explorer Appearance</Heading> */}
<SubHeading>Item size</SubHeading>
<Slider defaultValue={size} step={10} />
<Slider
onValueChange={(value) => {
getExplorerStore().gridItemSize = value[0] || 100;
console.log({ value: value, gridItemSize: explorerStore.gridItemSize });
}}
defaultValue={[explorerStore.gridItemSize]}
max={200}
step={10}
min={60}
/>
<div className="my-2 mt-4 grid grid-cols-2 gap-2">
<div className="flex flex-col">
<SubHeading>Sort by</SubHeading>

View file

@ -37,7 +37,7 @@ export interface TopBarButtonProps {
// export const TopBarIcon = (icon: any) => tw(icon)`m-0.5 w-5 h-5 text-ink-dull`;
const topBarButtonStyle = cva(
'text-ink hover:text-ink text-md hover:bg-app-selected radix-state-open:bg-app-selected mr-[1px] flex border-none p-0.5 font-medium outline-none transition-colors duration-100',
'text-ink hover:text-ink text-md hover:bg-app-selected radix-state-open:bg-app-selected mr-[1px] flex border-none !p-0.5 font-medium outline-none transition-colors duration-100',
{
variants: {
active: {
@ -63,7 +63,12 @@ const TOP_BAR_ICON_STYLE = 'm-0.5 w-5 h-5 text-ink-dull';
const TopBarButton = forwardRef<HTMLButtonElement, TopBarButtonProps>(
({ active, rounding, className, ...props }, ref) => {
return (
<Button {...props} ref={ref} className={topBarButtonStyle({ active, rounding, className })}>
<Button
// size="sm"
{...props}
ref={ref}
className={topBarButtonStyle({ active, rounding, className })}
>
{props.children}
</Button>
);

View file

@ -0,0 +1,42 @@
export interface IColumn {
column: string;
key: string;
width: number;
}
export const LIST_VIEW_HEADER_HEIGHT = 40;
// Function ensure no types are lost, but guarantees that they are Column[]
export function ensureIsColumns<T extends IColumn[]>(data: T) {
return data;
}
export const columns = [
{ column: 'Name', key: 'name', width: 280 },
// { column: 'Size', key: 'size_in_bytes', width: 120 },
{ column: 'Type', key: 'extension', width: 150 },
{ column: 'Size', key: 'size', width: 100 },
{ column: 'Date Created', key: 'date_created', width: 150 },
{ column: 'Content ID', key: 'cas_id', width: 150 }
] as const satisfies Readonly<IColumn[]>;
export type ColumnKey = (typeof columns)[number]['key'];
export function ListViewHeader() {
return (
<div
style={{ height: LIST_VIEW_HEADER_HEIGHT }}
className="mr-2 flex w-full flex-row rounded-lg border-2 border-transparent"
>
{columns.map((col) => (
<div
key={col.column}
className="flex items-center px-4 py-2 pr-2"
style={{ width: col.width, marginTop: -LIST_VIEW_HEADER_HEIGHT * 2 }}
>
<span className="text-xs font-medium ">{col.column}</span>
</div>
))}
</div>
);
}

View file

@ -2,9 +2,9 @@ import clsx from 'clsx';
import { HTMLAttributes } from 'react';
import { ExplorerItem, ObjectKind, isObject } from '@sd/client';
import { cva, tw } from '@sd/ui';
import { getExplorerStore } from '~/hooks/useExplorerStore';
import { FileItemContextMenu } from './ExplorerContextMenu';
import FileThumb from './FileThumb';
import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
import { ExplorerItemContextMenu } from './ExplorerContextMenu';
import { FileThumb } from './FileThumb';
const NameArea = tw.div`flex justify-center`;
@ -30,8 +30,10 @@ function FileItem({ data, selected, index, ...rest }: Props) {
const isVid = ObjectKind[objectData?.kind || 0] === 'Video';
const item = data.item;
const explorerStore = useExplorerStore();
return (
<FileItemContextMenu data={data}>
<ExplorerItemContextMenu data={data}>
<div
onContextMenu={(e) => {
if (index != undefined) {
@ -40,40 +42,22 @@ function FileItem({ data, selected, index, ...rest }: Props) {
}}
{...rest}
draggable
className={clsx('mb-3 inline-block w-[100px]', rest.className)}
style={{ width: explorerStore.gridItemSize }}
className={clsx('mb-3 inline-block', rest.className)}
>
<div
style={{
width: getExplorerStore().gridItemSize,
height: getExplorerStore().gridItemSize
width: explorerStore.gridItemSize,
height: explorerStore.gridItemSize
}}
className={clsx(
'mb-1 rounded-lg border-2 border-transparent text-center active:translate-y-[1px]',
{
'bg-app-selected/30': selected
'bg-app-selected/20': selected
}
)}
>
<div
className={clsx(
'relative flex h-full shrink-0 items-center justify-center rounded border-2 border-transparent p-1'
)}
>
<FileThumb
className={clsx(
'border-app-line max-h-full w-auto max-w-full overflow-hidden rounded-sm border-2 object-cover shadow shadow-black/40',
isVid && 'rounded border-x-0 border-y-[7px] !border-black'
)}
data={data}
kind={ObjectKind[objectData?.kind || 0]}
size={getExplorerStore().gridItemSize}
/>
{item.extension && isVid && (
<div className="absolute bottom-4 right-2 rounded bg-black/60 py-0.5 px-1 text-[9px] font-semibold uppercase opacity-70">
{item.extension}
</div>
)}
</div>
<FileThumb data={data} size={explorerStore.gridItemSize} />
</div>
<NameArea>
<span className={nameContainerStyles({ selected })}>
@ -82,7 +66,7 @@ function FileItem({ data, selected, index, ...rest }: Props) {
</span>
</NameArea>
</div>
</FileItemContextMenu>
</ExplorerItemContextMenu>
);
}

View file

@ -1,7 +1,14 @@
import byteSize from 'byte-size';
import clsx from 'clsx';
import dayjs from 'dayjs';
import { HTMLAttributes } from 'react';
import { ExplorerItem } from '@sd/client';
import FileThumb from './FileThumb';
import { ExplorerItem, ObjectKind, isObject, isPath } from '@sd/client';
import { getExplorerStore } from '../../hooks/useExplorerStore';
import { ExplorerItemContextMenu } from './ExplorerContextMenu';
import { ColumnKey, columns } from './FileColumns';
import { FileThumb } from './FileThumb';
import { InfoPill } from './Inspector';
import { getExplorerItemData } from './util';
interface Props extends HTMLAttributes<HTMLDivElement> {
data: ExplorerItem;
@ -11,24 +18,26 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
function FileRow({ data, index, selected, ...props }: Props) {
return (
<div
{...props}
className={clsx(
'table-body-row mr-2 flex w-full flex-row rounded-lg border-2',
selected ? 'border-accent' : 'border-transparent',
index % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
)}
>
{columns.map((col) => (
<div
key={col.key}
className="table-body-cell flex items-center px-4 py-2 pr-2"
style={{ width: col.width }}
>
<RenderCell data={data} colKey={col.key} />
</div>
))}
</div>
<ExplorerItemContextMenu className="w-full" data={data}>
<div
{...props}
className={clsx(
'table-body-row mr-2 flex w-full flex-row rounded-lg border-2',
selected ? 'border-accent' : 'border-transparent',
index % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
)}
>
{columns.map((col) => (
<div
key={col.key}
className="table-body-cell flex items-center px-4 py-2 pr-2"
style={{ width: col.width }}
>
<RenderCell data={data} colKey={col.key} />
</div>
))}
</div>
</ExplorerItemContextMenu>
);
}
@ -36,32 +45,44 @@ const RenderCell: React.FC<{
colKey: ColumnKey;
data: ExplorerItem;
}> = ({ colKey, data }) => {
const objectData = data ? (isObject(data) ? data.item : data.item.object) : null;
const { cas_id } = getExplorerItemData(data);
switch (colKey) {
case 'name':
return (
<div className="flex flex-row items-center overflow-hidden">
<div className="mr-3 flex h-6 w-6 shrink-0 items-center justify-center">
<FileThumb data={data} size={0} />
<div className="mr-3 flex h-6 w-12 shrink-0 items-center justify-center">
<FileThumb data={data} size={35} />
</div>
{/* {colKey == 'name' &&
(() => {
switch (row.extension.toLowerCase()) {
case 'mov' || 'mp4':
return <FilmIcon className="flex-shrink-0 w-5 h-5 mr-3 text-gray-300" />;
default:
if (row.is_dir)
return <FolderIcon className="flex-shrink-0 w-5 h-5 mr-3 text-gray-300" />;
return <DocumentIcon className="flex-shrink-0 w-5 h-5 mr-3 text-gray-300" />;
}
})()} */}
<span className="truncate text-xs">{data.item[colKey]}</span>
<span className="truncate text-xs">
{data.item.name}
{data.item.extension && `.${data.item.extension}`}
</span>
</div>
);
// case 'size_in_bytes':
// return <span className="text-xs text-left">{byteSize(Number(value || 0))}</span>;
case 'size':
return (
<span className="text-ink-dull text-left text-xs font-medium">
{byteSize(Number(objectData?.size_in_bytes || 0)).toString()}
</span>
);
case 'date_created':
return (
<span className="text-ink-dull text-left text-xs font-medium">
{dayjs(data.item?.date_created).format('MMM Do YYYY')}
</span>
);
case 'cas_id':
return <span className="text-ink-dull truncate text-left text-xs font-medium">{cas_id}</span>;
case 'extension':
return <span className="text-left text-xs">{data.item[colKey]}</span>;
return (
<div className="flex flex-row items-center space-x-3">
<InfoPill className="bg-app-button/50">
{isPath(data) && data.item.is_dir ? 'Folder' : ObjectKind[objectData?.kind || 0]}
</InfoPill>
</div>
);
// case 'meta_integrity_hash':
// return <span className="truncate">{value}</span>;
// case 'tags':
@ -72,23 +93,4 @@ const RenderCell: React.FC<{
}
};
interface IColumn {
column: string;
key: string;
width: number;
}
// Function ensure no types are lost, but guarantees that they are Column[]
function ensureIsColumns<T extends IColumn[]>(data: T) {
return data;
}
const columns = ensureIsColumns([
{ column: 'Name', key: 'name', width: 280 } as const,
// { column: 'Size', key: 'size_in_bytes', width: 120 } as const,
{ column: 'Type', key: 'extension', width: 100 } as const
]);
type ColumnKey = (typeof columns)[number]['key'];
export default FileRow;

View file

@ -6,62 +6,114 @@ import Executable from '@sd/assets/images/Executable.png';
import File from '@sd/assets/images/File.png';
import Video from '@sd/assets/images/Video.png';
import clsx from 'clsx';
import { ExplorerItem, isObject, isPath } from '@sd/client';
import { useExplorerStore } from '~/hooks/useExplorerStore';
import { CSSProperties } from 'react';
import { ExplorerItem } from '@sd/client';
import { usePlatform } from '~/util/Platform';
import { Folder } from '../icons/Folder';
import { getExplorerItemData } from './util';
interface Props {
// const icons = import.meta.glob('../../../../assets/icons/*.svg');
interface FileItemProps {
data: ExplorerItem;
size: number;
className?: string;
style?: React.CSSProperties;
iconClassNames?: string;
kind?: string;
}
// const icons = import.meta.glob('../../../../assets/icons/*.svg');
export function FileThumb({ data, size, className }: FileItemProps) {
const { cas_id, isDir, kind, hasThumbnail, extension } = getExplorerItemData(data);
export default function FileThumb({ data, ...props }: Props) {
// 10 percent of the size
const videoBarsHeight = Math.floor(size / 10);
// calculate 16:9 ratio for height from size
const videoHeight = Math.floor((size * 9) / 16) + videoBarsHeight * 2;
return (
<div
className={clsx(
'relative flex h-full shrink-0 items-center justify-center border-2 border-transparent',
className
)}
>
<FileThumbImg
size={size}
hasThumbnail={hasThumbnail}
isDir={isDir}
cas_id={cas_id}
extension={extension}
kind={kind}
imgClassName={clsx(
hasThumbnail &&
'max-h-full w-auto max-w-full rounded-sm object-cover shadow shadow-black/30',
kind === 'Image' && size > 60 && 'border-app-line border-2',
kind === 'Video' && 'rounded border-x-0 !border-black'
)}
imgStyle={
kind === 'Video'
? {
borderTopWidth: videoBarsHeight,
borderBottomWidth: videoBarsHeight,
width: size,
height: videoHeight
}
: {}
}
/>
{extension && kind === 'Video' && size > 80 && (
<div className="absolute bottom-[22%] right-2 rounded bg-black/60 py-0.5 px-1 text-[9px] font-semibold uppercase opacity-70">
{extension}
</div>
)}
</div>
);
}
interface FileThumbImgProps {
isDir: boolean;
cas_id: string | null;
kind: string | null;
extension: string | null;
size: number;
hasThumbnail: boolean;
imgClassName?: string;
imgStyle?: CSSProperties;
}
export function FileThumbImg({
isDir,
cas_id,
kind,
size,
hasThumbnail,
extension,
imgClassName,
imgStyle
}: FileThumbImgProps) {
const platform = usePlatform();
// const Icon = useMemo(() => {
// const icon = icons[`../../../../assets/icons/${item.extension}.svg`];
// const Icon = icon
// ? lazy(() => icon().then((v) => ({ default: (v as any).ReactComponent })))
// : undefined;
// return Icon;
// }, [item.extension]);
if (isDir) return <Folder size={size * 0.7} />;
if (isPath(data) && data.item.is_dir) return <Folder size={props.size * 0.7} />;
if (!cas_id) return <div></div>;
const url = platform.getThumbnailUrlById(cas_id);
if (data.has_thumbnail) {
const cas_id = isObject(data) ? data.item.file_paths[0]?.cas_id : data.item.cas_id;
if (!cas_id) return <div></div>;
const url = platform.getThumbnailUrlById(cas_id);
if (url)
return (
<img
style={props.style}
decoding="async"
// width={props.size}
className={clsx('z-90 pointer-events-none', props.className)}
src={url}
/>
);
if (url && hasThumbnail) {
return (
<img
style={{ ...imgStyle, maxWidth: size, width: size - 10 }}
decoding="async"
className={clsx('z-90 pointer-events-none bg-black', imgClassName)}
src={url}
/>
);
}
let icon = File;
// Hacky (and temporary) way to integrate thumbnails
if (props.kind === 'Archive') icon = Archive;
else if (props.kind === 'Video') icon = Video;
else if (props.kind === 'Document' && data.item.extension === 'pdf') icon = DocumentPdf;
else if (props.kind === 'Executable') icon = Executable;
else if (props.kind === 'Encrypted') icon = Encrypted;
else if (props.kind === 'Compressed') icon = Compressed;
if (kind === 'Archive') icon = Archive;
else if (kind === 'Video') icon = Video;
else if (kind === 'Document' && extension === 'pdf') icon = DocumentPdf;
else if (kind === 'Executable') icon = Executable;
else if (kind === 'Encrypted') icon = Encrypted;
else if (kind === 'Compressed') icon = Compressed;
return <img src={icon} className={clsx('h-full overflow-hidden', props.iconClassNames)} />;
return <img src={icon} className={clsx('h-full overflow-hidden')} />;
}

View file

@ -14,7 +14,7 @@ import {
import { Button, tw } from '@sd/ui';
import { DefaultProps } from '../primitive/types';
import { Tooltip } from '../tooltip/Tooltip';
import FileThumb from './FileThumb';
import { FileThumb } from './FileThumb';
import { Divider } from './inspector/Divider';
import FavoriteButton from './inspector/FavoriteButton';
import Note from './inspector/Note';
@ -80,17 +80,10 @@ export const Inspector = ({ data, context, ...elementProps }: Props) => {
<>
<div
className={clsx(
'mb-[10px] flex h-52 w-full items-center justify-center overflow-hidden rounded-lg',
objectData?.kind === 7 && objectData?.has_thumbnail && 'bg-black'
'mb-[10px] flex h-52 w-full items-center justify-center overflow-hidden rounded-lg'
)}
>
<FileThumb
iconClassNames="my-3 max-h-[150px]"
size={230}
kind={ObjectKind[objectData?.kind || 0]}
className="flex shrink grow-0"
data={data}
/>
<FileThumb size={240} data={data} />
</div>
<div className="bg-app-box shadow-app-shade/10 border-app-line flex w-full select-text flex-col overflow-hidden rounded-lg border py-0.5">
<h3 className="truncate px-3 pt-2 pb-1 text-base font-bold">
@ -126,7 +119,7 @@ export const Inspector = ({ data, context, ...elementProps }: Props) => {
<MetaContainer>
<div className="flex flex-wrap gap-1">
<InfoPill>{isDir ? 'Folder' : ObjectKind[objectData?.kind || 0]}</InfoPill>
{item && <InfoPill>{item.extension}</InfoPill>}
{item?.extension && <InfoPill>{item.extension}</InfoPill>}
{tags?.data?.map((tag) => (
<InfoPill
className="!text-white"

View file

@ -4,11 +4,12 @@ import { useSearchParams } from 'react-router-dom';
import { useKey, useOnWindowResize } from 'rooks';
import { ExplorerContext, ExplorerItem, isPath } from '@sd/client';
import { ExplorerLayoutMode, getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
import { LIST_VIEW_HEADER_HEIGHT, ListViewHeader } from './FileColumns';
import FileItem from './FileItem';
import FileRow from './FileRow';
const TOP_BAR_HEIGHT = 46;
const GRID_TEXT_AREA_HEIGHT = 25;
// const GRID_TEXT_AREA_HEIGHT = 25;
interface Props {
context: ExplorerContext;
@ -36,7 +37,8 @@ export const VirtualizedList = memo(({ data, context, onScroll }: Props) => {
}, [explorerStore.showInspector]);
// sizing calculations
const amountOfColumns = Math.floor(width / explorerStore.gridItemSize) || 8,
const GRID_TEXT_AREA_HEIGHT = explorerStore.gridItemSize / 4;
const amountOfColumns = Math.floor(width / explorerStore.gridItemSize) || 4,
amountOfRows =
explorerStore.layoutMode === 'grid' ? Math.ceil(data.length / amountOfColumns) : data.length,
itemSize =
@ -92,28 +94,6 @@ export const VirtualizedList = memo(({ data, context, onScroll }: Props) => {
getExplorerStore().selectedRowIndex = explorerStore.selectedRowIndex + 1;
});
// const Header = () => (
// <div>
// {props.context.name && (
// <h1 className="pt-20 pl-4 text-xl font-bold ">{props.context.name}</h1>
// )}
// <div className="table-head">
// <div className="flex flex-row p-2 table-head-row">
// {columns.map((col) => (
// <div
// key={col.key}
// className="relative flex flex-row items-center pl-2 table-head-cell group"
// style={{ width: col.width }}
// >
// <EllipsisHorizontalIcon className="absolute hidden w-5 h-5 -ml-5 cursor-move group-hover:block drag-handle opacity-10" />
// <span className="text-sm font-medium text-gray-500">{col.column}</span>
// </div>
// ))}
// </div>
// </div>
// </div>
// );
return (
<div style={{ marginTop: -TOP_BAR_HEIGHT }} className="w-full cursor-default pl-4">
<div
@ -126,11 +106,12 @@ export const VirtualizedList = memo(({ data, context, onScroll }: Props) => {
<div
ref={innerRef}
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
marginTop: `${TOP_BAR_HEIGHT}px`
height: rowVirtualizer.getTotalSize(),
marginTop: TOP_BAR_HEIGHT + LIST_VIEW_HEADER_HEIGHT
}}
className="relative w-full"
>
<ListViewHeader />
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
<div
style={{
@ -140,34 +121,32 @@ export const VirtualizedList = memo(({ data, context, onScroll }: Props) => {
className="absolute top-0 left-0 flex w-full"
key={virtualRow.key}
>
{explorerStore.layoutMode === 'list' ? (
{explorerStore.layoutMode === 'list' && (
<WrappedItem
kind="list"
isSelected={getExplorerStore().selectedRowIndex === virtualRow.index}
isSelected={explorerStore.selectedRowIndex === virtualRow.index}
index={virtualRow.index}
item={data[virtualRow.index]!}
/>
) : (
)}
{explorerStore.layoutMode === 'grid' &&
[...Array(amountOfColumns)].map((_, i) => {
const index = virtualRow.index * amountOfColumns + i;
const item = data[index];
const isSelected = explorerStore.selectedRowIndex === index;
return (
<div key={index} className="">
<div className="flex">
{item && (
<WrappedItem
kind="grid"
isSelected={isSelected}
index={index}
item={item}
/>
)}
</div>
<div key={index} className="flex">
{item && (
<WrappedItem
kind="grid"
isSelected={isSelected}
index={index}
item={item}
/>
)}
</div>
);
})
)}
})}
</div>
))}
</div>
@ -194,7 +173,6 @@ const WrappedItem = memo(({ item, index, isSelected, kind }: WrappedItemProps) =
const onClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
getExplorerStore().selectedRowIndex = isSelected ? -1 : index;
},
[isSelected, index]

View file

@ -0,0 +1,13 @@
import { ExplorerItem, ObjectKind, isObject, isPath } from '@sd/client';
export function getExplorerItemData(data: ExplorerItem) {
const objectData = data ? (isObject(data) ? data.item : data.item.object) : null;
return {
cas_id: (isObject(data) ? data.item.file_paths[0]?.cas_id : data.item.cas_id) || null,
isDir: isPath(data) && data.item.is_dir,
kind: ObjectKind[objectData?.kind || 0] || null,
hasThumbnail: data.has_thumbnail,
extension: data.item.extension
};
}

View file

@ -52,11 +52,11 @@ const explorerStore = proxy({
});
export function useExplorerStore() {
const { library } = useLibraryContext();
// const { library } = useLibraryContext();
useEffect(() => {
explorerStore.reset();
}, [library.uuid]);
// useEffect(() => {
// explorerStore.reset();
// }, [library.uuid]);
return useSnapshot(explorerStore);
}