Object Kind (#397)

* add temp custom icons and tweak styles

* tweaks

* - added Object type structs
- added extension enums with variant macros
- improved explorer rendering
- added database migration hotfix

* - added dev only data folder
- improved explorer image loading

* macros do rule !

* remove baselining

Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
Jamie Pine
2022-10-04 20:34:41 -07:00
committed by GitHub
parent 9bf071e5d9
commit d27ff3cd80
24 changed files with 848 additions and 253 deletions

View File

@@ -6,7 +6,7 @@ import ExplorerContextMenu from './ExplorerContextMenu';
import { VirtualizedList } from './VirtualizedList';
interface Props {
data: ExplorerData;
data?: ExplorerData;
}
export default function Explorer(props: Props) {
@@ -24,16 +24,17 @@ export default function Explorer(props: Props) {
<ExplorerContextMenu>
<div className="relative flex flex-col w-full bg-gray-650">
<TopBar />
<div className="relative flex flex-row w-full max-h-full">
<VirtualizedList data={props.data?.items || []} context={props.data.context} />
<div className="relative flex flex-row w-full max-h-full ">
{props.data && (
<VirtualizedList data={props.data.items || []} context={props.data.context} />
)}
{expStore.showInspector && (
<div className="min-w-[260px] max-w-[260px]">
{props.data.items[expStore.selectedRowIndex]?.id && (
<Inspector
key={props.data.items[expStore.selectedRowIndex].id}
data={props.data.items[expStore.selectedRowIndex]}
/>
)}
<div className="flex min-w-[260px] max-w-[260px]">
<Inspector
key={props.data?.items[expStore.selectedRowIndex]?.id}
data={props.data?.items[expStore.selectedRowIndex]}
/>
</div>
)}
</div>

View File

@@ -1,4 +1,9 @@
import { useExplorerStore, useLibraryMutation, useLibraryQuery } from '@sd/client';
import {
getExplorerStore,
useExplorerStore,
useLibraryMutation,
useLibraryQuery
} from '@sd/client';
import { ContextMenu as CM } from '@sd/ui';
import {
ArrowBendUpRight,
@@ -59,7 +64,7 @@ interface Props {
}
export default function ExplorerContextMenu(props: Props) {
const store = useExplorerStore();
const store = getExplorerStore();
return (
<div className="relative">
@@ -101,18 +106,13 @@ export default function ExplorerContextMenu(props: Props) {
</CM.SubMenu>
)}
<CM.SubMenu label="More actions..." icon={Plus}>
<CM.SubMenu label="Move to library" icon={FilePlus}>
{/* {libraries.map(library => <CM.Item key={library.id} label={library.config.name} />)} */}
<CM.Item label="Remove from library" icon={FileX} />
</CM.SubMenu>
<CM.Separator />
<CM.Item label="Encrypt" icon={LockSimple} />
<CM.Item label="Compress" icon={Package} />
<CM.SubMenu label="Convert to" icon={ArrowBendUpRight}>
<CM.Item label="PNG" />
<CM.Item label="WebP" />
</CM.SubMenu>
<CM.Item label="Secure delete" icon={TrashSimple} />
<CM.Item variant="danger" label="Secure delete" icon={TrashSimple} />
</CM.SubMenu>
<CM.Separator />

View File

@@ -18,6 +18,8 @@ function FileItem({ data, selected, index, ...rest }: Props) {
// props.index === store.selectedRowIndex
const isVid = isVideo(data.extension || '');
return (
<div
onContextMenu={(e) => {
@@ -44,17 +46,23 @@ function FileItem({ data, selected, index, ...rest }: Props) {
>
<div
className={clsx(
'flex items-center justify-center h-full p-1 rounded border-transparent border-2 shrink-0'
'flex relative items-center justify-center h-full p-1 rounded border-transparent border-2 shrink-0'
)}
>
<FileThumb
className={clsx(
'border-4 border-gray-250 rounded-sm shadow-md shadow-gray-750 object-cover max-w-full max-h-full w-auto overflow-hidden',
isVideo(data.extension || '') && 'border-gray-950'
'border-4 border-gray-250 rounded shadow-md shadow-gray-750 object-cover max-w-full max-h-full w-auto overflow-hidden',
isVid && 'border-gray-950 border-x-0 border-y-[9px]'
)}
data={data}
kind={data.extension === 'zip' ? 'zip' : isVid ? 'video' : 'other'}
size={getExplorerStore().gridItemSize}
/>
{data?.extension && isVid && (
<div className="absolute bottom-4 font-semibold opacity-70 right-2 py-0.5 px-1 text-[9px] uppercase bg-gray-800 rounded">
{data.extension}
</div>
)}
</div>
</div>
<div className="flex justify-center">
@@ -99,6 +107,11 @@ function isVideo(extension: string) {
'wmv',
'mp4',
'webm',
'flv'
'flv',
'mpg',
'hevc',
'ogv',
'swf',
'wtv'
].includes(extension);
}

View File

@@ -1,3 +1,5 @@
import videoSvg from '@sd/assets/svgs/video.svg';
import zipSvg from '@sd/assets/svgs/zip.svg';
import { getExplorerStore, useExplorerStore, usePlatform } from '@sd/client';
import { ExplorerItem } from '@sd/client';
import clsx from 'clsx';
@@ -14,6 +16,7 @@ interface Props {
className?: string;
style?: React.CSSProperties;
iconClassNames?: string;
kind?: 'video' | 'image' | 'audio' | 'zip' | 'other';
}
export default function FileThumb({ data, ...props }: Props) {
@@ -41,11 +44,30 @@ export default function FileThumb({ data, ...props }: Props) {
return (
<img
style={props.style}
decoding="async"
// width={props.size}
className={clsx('pointer-events-none', props.className)}
src={url}
/>
);
if (props.kind === 'video') {
return (
<div className="">
<img
src={videoSvg}
className={clsx('w-full overflow-hidden h-full', props.iconClassNames)}
/>
</div>
);
}
if (props.kind === 'zip') {
return (
<div className="">
<img src={zipSvg} className={clsx('w-full overflow-hidden h-full')} />
</div>
);
}
}
const Icon = icons[data.extension as keyof typeof icons];

View File

@@ -18,13 +18,13 @@ import { isObject } from './utils';
interface Props {
context?: ExplorerContext;
data: ExplorerItem;
data?: ExplorerItem;
}
export const Inspector = (props: Props) => {
const is_dir = props.data?.type === 'Path' ? props.data.is_dir : false;
const objectData = isObject(props.data) ? props.data : props.data.object;
const objectData = props.data ? (isObject(props.data) ? props.data : props.data.object) : null;
// this prevents the inspector from fetching data when the user is navigating quickly
const [readyToFetch, setReadyToFetch] = useState(false);
@@ -33,7 +33,7 @@ export const Inspector = (props: Props) => {
setReadyToFetch(true);
}, 350);
return () => clearTimeout(timeout);
}, [props.data.id]);
}, [props.data?.id]);
// this is causing LAG
const { data: tags } = useLibraryQuery(['tags.getForFile', objectData?.id || -1], {
@@ -41,113 +41,115 @@ export const Inspector = (props: Props) => {
});
return (
<div className="p-2 pt-0.5 pr-1 overflow-x-hidden custom-scroll inspector-scroll pb-[55px]">
{!!props.data && (
<>
<div className="flex items-center justify-center w-full overflow-hidden bg-black rounded-md ">
<FileThumb
iconClassNames="!my-10"
size={230}
className="!m-0 flex flex-shrink flex-grow-0"
data={props.data}
/>
</div>
<div className="flex flex-col w-full pt-0.5 pb-4 overflow-hidden shadow select-text">
<h3 className="pt-3 pl-3 text-base font-bold">
{props.data?.name}
{props.data?.extension && `.${props.data.extension}`}
</h3>
{objectData && (
<div className="flex flex-row m-3 space-x-2">
<Tooltip label="Favorite">
<FavoriteButton data={objectData} />
</Tooltip>
<Tooltip label="Share">
<Button size="sm" noPadding>
<ShareIcon className="w-[18px] h-[18px]" />
</Button>
</Tooltip>
<Tooltip label="Link">
<Button size="sm" noPadding>
<Link className="w-[18px] h-[18px]" />
</Button>
</Tooltip>
</div>
)}
{!!tags?.length && (
<>
<Divider />
<MetaItem
// title="Tags"
value={
<div className="flex flex-wrap mt-1.5 gap-1.5">
{tags?.map((tag) => (
<div
// onClick={() => setSelectedTag(tag.id === selectedTag ? null : tag.id)}
key={tag.id}
className={clsx(
'flex items-center rounded px-1.5 py-0.5'
// selectedTag === tag.id && 'ring'
)}
style={{ backgroundColor: tag.color + 'CC' }}
>
<span className="text-xs text-white drop-shadow-md">{tag.name}</span>
</div>
))}
</div>
}
/>
</>
)}
{props.context?.type == 'Location' && props.data?.type === 'Path' && (
<>
<Divider />
<MetaItem
title="URI"
value={`${props.context.local_path}/${props.data.materialized_path}`}
/>
</>
)}
<Divider />
<MetaItem
title="Date Created"
value={moment(props.data?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
/>
<Divider />
<MetaItem
title="Date Indexed"
value={moment(props.data?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
/>
{!is_dir && (
<>
<Divider />
<div className="flex flex-row items-center px-3 py-2 meta-item">
{props.data?.extension && (
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
{props.data?.extension}
</span>
)}
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
{props.data?.extension
? //@ts-ignore
types[props.data.extension.toUpperCase()]?.descriptions.join(' / ')
: 'Unknown'}
</p>
<div className="p-2 pt-0.5 max-h-screen custom-scroll inspector-scroll pr-1 m-1 overflow-x-hidden">
<div className="">
{!!props.data && (
<>
<div className="flex items-center justify-center w-full overflow-hidden rounded-md ">
<FileThumb
iconClassNames="!my-10"
size={230}
className="!m-0 flex flex-shrink flex-grow-0"
data={props.data}
/>
</div>
<div className="flex flex-col w-full pt-0.5 pb-4 overflow-hidden select-text">
<h3 className="flex mt-3 ml-3 text-base font-bold">
{props.data?.name}
{props.data?.extension && `.${props.data.extension}`}
</h3>
{objectData && (
<div className="flex flex-row m-3 space-x-2">
<Tooltip label="Favorite">
<FavoriteButton data={objectData} />
</Tooltip>
<Tooltip label="Share">
<Button size="sm" noPadding>
<ShareIcon className="w-[18px] h-[18px]" />
</Button>
</Tooltip>
<Tooltip label="Link">
<Button size="sm" noPadding>
<Link className="w-[18px] h-[18px]" />
</Button>
</Tooltip>
</div>
{objectData && (
<>
<Note data={objectData} />
<Divider />
{objectData.cas_id && (
<MetaItem title="Unique Content ID" value={objectData.cas_id} />
)}
{!!tags?.length && (
<>
<Divider />
<MetaItem
// title="Tags"
value={
<div className="flex flex-wrap mt-1.5 gap-1.5">
{tags?.map((tag) => (
<div
// onClick={() => setSelectedTag(tag.id === selectedTag ? null : tag.id)}
key={tag.id}
className={clsx(
'flex items-center rounded px-1.5 py-0.5'
// selectedTag === tag.id && 'ring'
)}
style={{ backgroundColor: tag.color + 'CC' }}
>
<span className="text-xs text-white drop-shadow-md">{tag.name}</span>
</div>
))}
</div>
}
/>
</>
)}
{props.context?.type == 'Location' && props.data?.type === 'Path' && (
<>
<Divider />
<MetaItem
title="URI"
value={`${props.context.local_path}/${props.data.materialized_path}`}
/>
</>
)}
<Divider />
<MetaItem
title="Date Created"
value={moment(props.data?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
/>
<Divider />
<MetaItem
title="Date Indexed"
value={moment(props.data?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
/>
{!is_dir && (
<>
<Divider />
<div className="flex flex-row items-center px-3 py-2 meta-item">
{props.data?.extension && (
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
{props.data?.extension}
</span>
)}
</>
)}
</>
)}
</div>
</>
)}
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
{props.data?.extension
? //@ts-ignore
types[props.data.extension.toUpperCase()]?.descriptions.join(' / ')
: 'Unknown'}
</p>
</div>
{objectData && (
<>
<Note data={objectData} />
<Divider />
{objectData.cas_id && (
<MetaItem title="Unique Content ID" value={objectData.cas_id} />
)}
</>
)}
</>
)}
</div>
</>
)}
</div>
</div>
);
};

View File

@@ -1,7 +1,7 @@
import { ExplorerLayoutMode, getExplorerStore, useExplorerStore } from '@sd/client';
import { ExplorerContext, ExplorerItem, FilePath } from '@sd/client';
import { useVirtualizer } from '@tanstack/react-virtual';
import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useKey, useOnWindowResize, useWindowSize } from 'rooks';
import { useSnapshot } from 'valtio';
@@ -33,6 +33,9 @@ export const VirtualizedList: React.FC<Props> = ({ data, context }) => {
}
useOnWindowResize(handleWindowResize);
useLayoutEffect(() => handleWindowResize(), []);
useEffect(() => {
setWidth(innerRef.current?.offsetWidth || 0);
}, [explorerStore.showInspector]);
// sizing calculations
const amountOfColumns = Math.floor(width / explorerStore.gridItemSize) || 8,

View File

@@ -162,7 +162,7 @@ export function Sidebar() {
<div
className={clsx(
'flex flex-col flex-grow-0 flex-shrink-0 w-48 min-h-full px-2.5 overflow-x-hidden overflow-y-scroll border-r border-gray-100 no-scrollbar bg-gray-50 dark:bg-gray-850 dark:border-gray-750',
macOnly(os, 'dark:!bg-opacity-40')
macOnly(os, 'dark:!bg-opacity-30')
)}
>
<WindowControls />

View File

@@ -1,4 +1,4 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
import { ChevronLeftIcon, ChevronRightIcon, PhotoIcon } from '@heroicons/react/24/outline';
import {
OperatingSystem,
getExplorerStore,
@@ -8,10 +8,14 @@ import {
import { Dropdown } from '@sd/ui';
import clsx from 'clsx';
import {
Aperture,
ArrowsClockwise,
FilmStrip,
IconProps,
Image,
Key,
List,
MonitorPlay,
Rows,
SidebarSimple,
SquaresFour
@@ -49,7 +53,7 @@ const TopBarButton: React.FC<TopBarButtonProps> = ({
<button
{...props}
className={clsx(
'mr-[1px] py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 rounded-md transition-colors duration-100',
'mr-[1px] flex py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 rounded-md transition-colors duration-100',
{
'rounded-r-none rounded-l-none': group && !left && !right,
'rounded-r-none': group && left,
@@ -220,22 +224,31 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow">
<div className="flex mx-8">
<Tooltip label="List view">
<Tooltip label="Grid view">
<TopBarButton
group
left
active={store.layoutMode === 'grid'}
icon={SquaresFour}
onClick={() => (getExplorerStore().layoutMode = 'grid')}
/>
</Tooltip>
<Tooltip label="List view">
<TopBarButton
group
active={store.layoutMode === 'list'}
icon={Rows}
onClick={() => (getExplorerStore().layoutMode = 'list')}
/>
</Tooltip>
<Tooltip label="Grid view">
<Tooltip label="Media view">
<TopBarButton
group
right
active={store.layoutMode === 'grid'}
icon={SquaresFour}
onClick={() => (getExplorerStore().layoutMode = 'grid')}
active={store.layoutMode === 'media'}
icon={MonitorPlay}
onClick={() => (getExplorerStore().layoutMode = 'media')}
/>
</Tooltip>
</div>

View File

@@ -17,9 +17,9 @@ export const Tooltip = ({
</TooltipPrimitive.Trigger>
<TooltipPrimitive.Content
side={position}
className="text-sm rounded px-2 py-1 mb-[2px] bg-gray-300 dark:!bg-gray-500 dark:text-gray-100"
className="text-xs rounded px-2 py-1 mb-[2px] bg-gray-300 dark:!bg-gray-900 dark:text-gray-100"
>
<TooltipPrimitive.Arrow className="fill-gray-300 dark:!fill-gray-500" />
<TooltipPrimitive.Arrow className="fill-gray-300 dark:!fill-gray-900" />
{label}
</TooltipPrimitive.Content>
</TooltipPrimitive.Root>

View File

@@ -35,7 +35,7 @@ export const LocationExplorer: React.FC<unknown> = () => {
return (
<div className="relative flex flex-col w-full">
{library!.uuid && explorerData.data && <Explorer data={explorerData.data} />}
<Explorer data={explorerData.data} />
</div>
);
};

View File

@@ -1,4 +1,4 @@
import { useBridgeQuery } from '@sd/client';
import { useBridgeQuery, usePlatform } from '@sd/client';
import { Input } from '@sd/ui';
import { Database } from 'phosphor-react';
@@ -10,6 +10,8 @@ import { SettingsHeader } from '../../../components/settings/SettingsHeader';
export default function GeneralSettings() {
const { data: node } = useBridgeQuery(['getNode']);
const platform = usePlatform();
return (
<SettingsContainer>
<SettingsHeader
@@ -55,7 +57,14 @@ export default function GeneralSettings() {
<span className="text-sm text-gray-200">Run daemon when app closed</span>
</div>
<div className="mt-3">
<span className="text-xs font-medium text-gray-700 dark:text-gray-400">
<span
onClick={() => {
if (node && platform?.openLink) {
platform.openLink(node.data_path);
}
}}
className="text-xs font-medium text-gray-700 dark:text-gray-400"
>
<Database className="inline w-4 h-4 mr-2 -mt-[2px]" />
<b className="mr-2">Data Folder</b>
<span className="select-text">{node?.data_path}</span>

View File

@@ -5,14 +5,6 @@ export default function AboutSpacedrive() {
return (
<SettingsContainer>
<SettingsHeader title="About Spacedrive" description="The file manager from the future." />
<span>Version {}</span>
<div className="flex flex-col">
<span className="mb-1 text-sm font-bold">Created by</span>
<span className="max-w-md text-xs text-gray-400">
Jamie Pine, Brendan Allan, Oscar Beaumont, Haden Fletcher, Haris Mehrzad Benjamin Akar,
and many more.
</span>
</div>
</SettingsContainer>
);
}