bundle splitting and performance optimisations

This commit is contained in:
Oscar Beaumont
2022-10-02 01:37:35 +08:00
parent fc1c6d0906
commit b250ab6861
25 changed files with 405 additions and 219 deletions

2
.gitignore vendored
View File

@@ -65,4 +65,4 @@ examples/*/*.lock
/core/src/prisma.rs
/sdserver_data
.spacedrive
.spacedrive

View File

@@ -23,7 +23,7 @@
"@tanstack/react-query": "^4.2.3",
"byte-size": "^8.1.0",
"class-variance-authority": "^0.2.3",
"date-fns": "^2.29.2",
"dayjs": "^1.11.5",
"expo": "~46.0.10",
"expo-linking": "~3.2.2",
"expo-splash-screen": "~0.16.2",

View File

Binary file not shown.

View File

@@ -11,9 +11,10 @@ use tokio::{
#[allow(dead_code)]
pub(crate) static RUNTIME: Lazy<Runtime> = Lazy::new(|| Runtime::new().unwrap());
type NodeType = Lazy<Mutex<Option<(Arc<Node>, Arc<Router>)>>>;
#[allow(dead_code)]
pub(crate) static NODE: Lazy<Mutex<Option<(Arc<Node>, Arc<Router>)>>> =
Lazy::new(|| Mutex::new(None));
pub(crate) static NODE: NodeType = Lazy::new(|| Mutex::new(None));
#[allow(dead_code)]
pub(crate) static SUBSCRIPTIONS: Lazy<Mutex<HashMap<RequestId, oneshot::Sender<()>>>> =

View File

@@ -1,5 +1,5 @@
import { BottomSheetModal, BottomSheetScrollView } from '@gorhom/bottom-sheet';
import { format } from 'date-fns';
import dayjs from 'dayjs';
import React, { useRef } from 'react';
import { Button, Pressable, Text, View } from 'react-native';
import { ChevronLeftIcon } from 'react-native-heroicons/outline';
@@ -98,12 +98,12 @@ export const FileModal = () => {
<Divider style={tw`my-4`} />
<MetaItem
title="Date Created"
value={format(new Date(data.date_created), 'MMMM Do yyyy, h:mm:ss aaa')}
value={dayjs(data.date_created).format('MMMM Do yyyy, h:mm:ss aaa')}
/>
<Divider style={tw`my-4`} />
<MetaItem
title="Date Indexed"
value={format(new Date(data.date_indexed), 'MMMM Do yyyy, h:mm:ss aaa')}
value={dayjs(data.date_indexed).format('MMMM Do yyyy, h:mm:ss aaa')}
/>
</>
</BottomSheetScrollView>

View File

@@ -1,65 +1,214 @@
import React from 'react';
import { FlatList, View } from 'react-native';
import Device from '~/components/device/Device';
import VirtualizedListWrapper from '~/components/layout/VirtualizedListWrapper';
import OverviewStats from '~/containers/OverviewStats';
import tw from '~/lib/tailwind';
import { OverviewStackScreenProps } from '~/navigation/tabs/OverviewStack';
import { ExclamationCircleIcon, PlusIcon } from '@heroicons/react/24/solid';
import { useBridgeQuery, useLibraryQuery, usePlatform } from '@sd/client';
import { Statistics } from '@sd/client';
import { Button, Input } from '@sd/ui';
import byteSize from 'byte-size';
import clsx from 'clsx';
import { useEffect } from 'react';
import Skeleton from 'react-loading-skeleton';
import 'react-loading-skeleton/dist/skeleton.css';
import create from 'zustand';
const placeholderOverviewStats = {
id: 1,
total_bytes_capacity: '8093333345230',
preview_media_bytes: '2304387532',
library_db_size: '83345230',
total_file_count: 20342345,
total_bytes_free: '89734502034',
total_bytes_used: '8093333345230',
total_unique_bytes: '9347397',
date_captured: '2020-01-01'
import { Device } from '../components/device/Device';
import Dialog from '../components/layout/Dialog';
import useCounter from '../hooks/useCounter';
interface StatItemProps {
title: string;
bytes: string;
isLoading: boolean;
}
const StatItemNames: Partial<Record<keyof Statistics, string>> = {
total_bytes_capacity: 'Total capacity',
preview_media_bytes: 'Preview media',
library_db_size: 'Index size',
total_bytes_free: 'Free space'
};
const placeholderDevices: any = [
{
name: "James' iPhone 12",
size: '47.9GB',
locations: [],
type: 'phone'
},
{
name: "James' MacBook Pro",
size: '1TB',
locations: [],
type: 'laptop'
},
{
name: "James' Toaster",
size: '1PB',
locations: [],
type: 'desktop'
},
{
name: 'Spacedrive Server',
size: '5GB',
locations: [],
type: 'server'
}
];
type OverviewStats = Partial<Record<keyof Statistics, string>>;
type OverviewState = {
overviewStats: OverviewStats;
setOverviewStat: (name: keyof OverviewStats, newValue: string) => void;
setOverviewStats: (stats: OverviewStats) => void;
};
export const useOverviewState = create<OverviewState>((set) => ({
overviewStats: {},
setOverviewStat: (name, newValue) =>
set((state) => ({
...state,
overviewStats: {
...state.overviewStats,
[name]: newValue
}
})),
setOverviewStats: (stats) =>
set((state) => ({
...state,
overviewStats: stats
}))
}));
const StatItem: React.FC<StatItemProps> = (props) => {
const { title, bytes = '0', isLoading } = props;
// const appProps = useContext(AppPropsContext);
const size = byteSize(+bytes);
const count = useCounter({
name: title,
end: +size.value
});
export default function OverviewScreen({ navigation }: OverviewStackScreenProps<'Overview'>) {
return (
<VirtualizedListWrapper>
<View style={tw`px-4 mt-4`}>
{/* Stats */}
<OverviewStats stats={placeholderOverviewStats} />
{/* Devices */}
<FlatList
data={placeholderDevices}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<Device locations={[]} name={item.name} size={item.size} type={item.type} />
)}
/>
</View>
</VirtualizedListWrapper>
<div
className={clsx(
'flex flex-col flex-shrink-0 w-32 px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600',
!+bytes && 'hidden'
)}
>
<span className="text-sm text-gray-400">{title}</span>
<span className="text-2xl font-bold">
{isLoading && (
<div>
<Skeleton enableAnimation={true} baseColor={'#21212e'} highlightColor={'#13131a'} />
</div>
)}
<div
className={clsx({
hidden: isLoading
})}
>
<span className="tabular-nums">{count}</span>
<span className="ml-1 text-[16px] text-gray-400">{size.unit}</span>
</div>
</span>
</div>
);
};
export default function OverviewScreen() {
const platform = usePlatform();
const { data: libraryStatistics, isLoading: isStatisticsLoading } = useLibraryQuery([
'library.getStatistics'
]);
const { data: nodeState } = useBridgeQuery(['getNode']);
const { overviewStats, setOverviewStats } = useOverviewState();
// get app props from context
useEffect(() => {
if (platform.demoMode === true) {
if (!Object.entries(overviewStats).length)
setOverviewStats({
total_bytes_capacity: '8093333345230',
preview_media_bytes: '2304387532',
library_db_size: '83345230',
total_file_count: '20342345',
total_bytes_free: '89734502034',
total_bytes_used: '8093333345230',
total_unique_bytes: '9347397'
});
} else {
const newStatistics: OverviewStats = {
total_bytes_capacity: '0',
preview_media_bytes: '0',
library_db_size: '0',
total_file_count: '0',
total_bytes_free: '0',
total_bytes_used: '0',
total_unique_bytes: '0'
};
Object.entries((libraryStatistics as Statistics) || {}).forEach(([key, value]) => {
newStatistics[key as keyof Statistics] = `${value}`;
});
setOverviewStats(newStatistics);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [platform, libraryStatistics]);
// useEffect(() => {
// setTimeout(() => {
// setOverviewStat('total_bytes_capacity', '4093333345230');
// }, 2000);
// }, [overviewStats]);
const displayableStatItems = Object.keys(StatItemNames) as unknown as keyof typeof StatItemNames;
return (
<div className="flex flex-col w-full h-screen overflow-x-hidden custom-scroll page-scroll">
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-5" />
{/* PAGE */}
<div className="flex flex-col w-full h-screen px-4">
{/* STAT HEADER */}
<div className="flex w-full">
{/* STAT CONTAINER */}
<div className="flex -mb-1 overflow-hidden">
{Object.entries(overviewStats).map(([key, value]) => {
if (!displayableStatItems.includes(key)) return null;
return (
<StatItem
key={key}
title={StatItemNames[key as keyof Statistics]!}
bytes={value}
isLoading={platform.demoMode === true ? false : isStatisticsLoading}
/>
);
})}
</div>
<div className="flex-grow" />
<div className="space-x-2 h-full flex items-center">
<div>
<Dialog
title="Add Device"
description="Connect a new device to your library. Either enter another device's code or copy this one."
// ctaAction={() => {}}
ctaLabel="Connect"
trigger={
<Button
size="sm"
icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 xl:mr-1" />}
variant="gray"
>
<span className="hidden xl:inline-block">Add Device</span>
</Button>
}
>
<div className="flex flex-col mt-2 space-y-3">
<div className="flex flex-col">
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
This Device
</span>
<Input readOnly disabled value="06ffd64309b24fb09e7c2188963d0207" />
</div>
<div className="flex flex-col">
<span className="mb-1 text-xs font-bold uppercase text-gray-450">
Enter a device code
</span>
<Input value="" />
</div>
</div>
</Dialog>
</div>
</div>
</div>
<div className="flex flex-col pb-4 mt-4 space-y-4">
<Device name={`James' MacBook Pro`} size="1TB" locations={[]} type="desktop" />
<Device name={`James' iPhone 12`} size="47.7GB" locations={[]} type="phone" />
<Device name={`Spacedrive Server`} size="5GB" locations={[]} type="server" />
</div>
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
functional.
</div>
<div className="flex flex-shrink-0 w-full h-4" />
</div>
</div>
);
}

View File

@@ -23,9 +23,11 @@
"@vitejs/plugin-react": "^2.1.0",
"autoprefixer": "^10.4.12",
"postcss": "^8.4.17",
"rollup-plugin-visualizer": "^5.8.2",
"tailwind": "^4.0.0",
"typescript": "^4.8.4",
"vite": "^3.1.4",
"vite-plugin-html": "^3.2.0",
"vite-plugin-svgr": "^2.2.1",
"vite-plugin-tsconfig-paths": "^1.2.0"
}

View File

@@ -1,5 +1,7 @@
import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';
import svg from 'vite-plugin-svgr';
import tsconfigPaths from 'vite-plugin-tsconfig-paths';
@@ -10,7 +12,18 @@ export default defineConfig({
server: {
port: 8002
},
plugins: [react(), svg({ svgrOptions: { icon: true } }), tsconfigPaths()],
plugins: [
react(),
svg({ svgrOptions: { icon: true } }),
tsconfigPaths(),
createHtmlPlugin({
minify: true
}),
visualizer({
gzipSize: true,
brotliSize: true
})
],
root: 'src',
publicDir: '../../packages/interface/src/assets',
define: {

View File

@@ -37,11 +37,10 @@
"autoprefixer": "^10.4.12",
"byte-size": "^8.1.0",
"clsx": "^1.2.1",
"date-fns": "^2.29.3",
"dayjs": "^1.11.5",
"immer": "^9.0.15",
"jotai": "^1.8.4",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"phosphor-react": "^1.4.1",
"pretty-bytes": "^6.0.0",
"react": "^18.2.0",

View File

@@ -2,6 +2,10 @@ import '@fontsource/inter/variable.css';
import { LibraryContextProvider, queryClient } from '@sd/client';
import { QueryClientProvider, defaultContext } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import { ErrorBoundary } from 'react-error-boundary';
import { MemoryRouter, useNavigate } from 'react-router-dom';
@@ -9,6 +13,10 @@ import { AppRouter } from './AppRouter';
import { ErrorFallback } from './ErrorFallback';
import './style.scss';
dayjs.extend(advancedFormat);
dayjs.extend(relativeTime);
dayjs.extend(duration);
export default function SpacedriveInterface() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>

View File

@@ -1,5 +1,6 @@
import { useCurrentLibrary } from '@sd/client';
import clsx from 'clsx';
import { Suspense } from 'react';
import { Outlet } from 'react-router-dom';
import { Sidebar } from './components/layout/Sidebar';
@@ -29,7 +30,9 @@ export function AppLayout() {
>
<Sidebar />
<div className="relative flex w-full h-screen max-h-screen bg-white dark:bg-gray-650">
<Outlet />
<Suspense fallback={<p>Loading...</p>}>
<Outlet />
</Suspense>
</div>
</div>
);

View File

@@ -1,38 +1,43 @@
import { useCurrentLibrary, useInvalidateQuery } from '@sd/client';
import { Suspense, lazy } from 'react';
import { Route, Routes } from 'react-router-dom';
import { AppLayout } from './AppLayout';
import { NotFound } from './NotFound';
import OnboardingScreen from './components/onboarding/Onboarding';
import { useKeybindHandler } from './hooks/useKeyboardHandler';
import { ContentScreen } from './screens/Content';
import { DebugScreen } from './screens/Debug';
import { LocationExplorer } from './screens/LocationExplorer';
import { OverviewScreen } from './screens/Overview';
import { PhotosScreen } from './screens/Photos';
import { RedirectPage } from './screens/Redirect';
import { TagExplorer } from './screens/TagExplorer';
import { SettingsScreen } from './screens/settings/Settings';
import AppearanceSettings from './screens/settings/client/AppearanceSettings';
import ExtensionSettings from './screens/settings/client/ExtensionsSettings';
import GeneralSettings from './screens/settings/client/GeneralSettings';
import KeybindingSettings from './screens/settings/client/KeybindingSettings';
import PrivacySettings from './screens/settings/client/PrivacySettings';
import AboutSpacedrive from './screens/settings/info/AboutSpacedrive';
import Changelog from './screens/settings/info/Changelog';
import Support from './screens/settings/info/Support';
import ContactsSettings from './screens/settings/library/ContactsSettings';
import KeysSettings from './screens/settings/library/KeysSetting';
import LibraryGeneralSettings from './screens/settings/library/LibraryGeneralSettings';
import LocationSettings from './screens/settings/library/LocationSettings';
import NodesSettings from './screens/settings/library/NodesSettings';
import SecuritySettings from './screens/settings/library/SecuritySettings';
import SharingSettings from './screens/settings/library/SharingSettings';
import SyncSettings from './screens/settings/library/SyncSettings';
import TagsSettings from './screens/settings/library/TagsSettings';
import ExperimentalSettings from './screens/settings/node/ExperimentalSettings';
import LibrarySettings from './screens/settings/node/LibrariesSettings';
import P2PSettings from './screens/settings/node/P2PSettings';
const DebugScreen = lazy(() => import('./screens/Debug'));
const SettingsScreen = lazy(() => import('./screens/settings/Settings'));
const TagExplorer = lazy(() => import('./screens/TagExplorer'));
const PhotosScreen = lazy(() => import('./screens/Photos'));
const OverviewScreen = lazy(() => import('./screens/Overview'));
const ContentScreen = lazy(() => import('./screens/Content'));
const LocationExplorer = lazy(() => import('./screens/LocationExplorer'));
const OnboardingScreen = lazy(() => import('./components/onboarding/Onboarding'));
const NotFound = lazy(() => import('./NotFound'));
const AppearanceSettings = lazy(() => import('./screens/settings/client/AppearanceSettings'));
const ExtensionSettings = lazy(() => import('./screens/settings/client/ExtensionsSettings'));
const GeneralSettings = lazy(() => import('./screens/settings/client/GeneralSettings'));
const KeybindingSettings = lazy(() => import('./screens/settings/client/KeybindingSettings'));
const PrivacySettings = lazy(() => import('./screens/settings/client/PrivacySettings'));
const AboutSpacedrive = lazy(() => import('./screens/settings/info/AboutSpacedrive'));
const Changelog = lazy(() => import('./screens/settings/info/Changelog'));
const Support = lazy(() => import('./screens/settings/info/Support'));
const ContactsSettings = lazy(() => import('./screens/settings/library/ContactsSettings'));
const KeysSettings = lazy(() => import('./screens/settings/library/KeysSetting'));
const LibraryGeneralSettings = lazy(
() => import('./screens/settings/library/LibraryGeneralSettings')
);
const LocationSettings = lazy(() => import('./screens/settings/library/LocationSettings'));
const NodesSettings = lazy(() => import('./screens/settings/library/NodesSettings'));
const SecuritySettings = lazy(() => import('./screens/settings/library/SecuritySettings'));
const SharingSettings = lazy(() => import('./screens/settings/library/SharingSettings'));
const SyncSettings = lazy(() => import('./screens/settings/library/SyncSettings'));
const TagsSettings = lazy(() => import('./screens/settings/library/TagsSettings'));
const ExperimentalSettings = lazy(() => import('./screens/settings/node/ExperimentalSettings'));
const LibrarySettings = lazy(() => import('./screens/settings/node/LibrariesSettings'));
const P2PSettings = lazy(() => import('./screens/settings/node/P2PSettings'));
export function AppRouter() {
const { library } = useCurrentLibrary();
@@ -41,56 +46,60 @@ export function AppRouter() {
useInvalidateQuery();
return (
<Routes>
<Route path="onboarding" element={<OnboardingScreen />} />
<Route element={<AppLayout />}>
{/* As we are caching the libraries in localStore so this *shouldn't* result is visual problems unless something else is wrong */}
{library === undefined ? (
<Route
path="*"
element={
<h1 className="text-white p-4">Please select or create a library in the sidebar.</h1>
}
/>
) : (
<>
<Route index element={<RedirectPage to="/overview" />} />
<Route path="overview" element={<OverviewScreen />} />
<Route path="content" element={<ContentScreen />} />
<Route path="photos" element={<PhotosScreen />} />
<Route path="debug" element={<DebugScreen />} />
<Route path={'settings'} element={<SettingsScreen />}>
<Route index element={<GeneralSettings />} />
<Route path="general" element={<GeneralSettings />} />
<Route path="appearance" element={<AppearanceSettings />} />
<Route path="keybindings" element={<KeybindingSettings />} />
<Route path="extensions" element={<ExtensionSettings />} />
<Route path="p2p" element={<P2PSettings />} />
<Route path="contacts" element={<ContactsSettings />} />
<Route path="experimental" element={<ExperimentalSettings />} />
<Route path="keys" element={<KeysSettings />} />
<Route path="libraries" element={<LibrarySettings />} />
<Route path="security" element={<SecuritySettings />} />
<Route path="locations" element={<LocationSettings />} />
<Route path="sharing" element={<SharingSettings />} />
<Route path="sync" element={<SyncSettings />} />
<Route path="tags" element={<TagsSettings />} />
<Route path="library" element={<LibraryGeneralSettings />} />
<Route path="locations" element={<LocationSettings />} />
<Route path="tags" element={<TagsSettings />} />
<Route path="nodes" element={<NodesSettings />} />
<Route path="keys" element={<KeysSettings />} />
<Route path="privacy" element={<PrivacySettings />} />
<Route path="about" element={<AboutSpacedrive />} />
<Route path="changelog" element={<Changelog />} />
<Route path="support" element={<Support />} />
</Route>
<Route path="location/:id" element={<LocationExplorer />} />
<Route path="tag/:id" element={<TagExplorer />} />
<Route path="*" element={<NotFound />} />
</>
)}
</Route>
</Routes>
<Suspense fallback={<p>Loading...</p>}>
<Routes>
<Route path="onboarding" element={<OnboardingScreen />} />
<Route element={<AppLayout />}>
{/* As we are caching the libraries in localStore so this *shouldn't* result is visual problems unless something else is wrong */}
{library === undefined ? (
<Route
path="*"
element={
<h1 className="text-white p-4">
Please select or create a library in the sidebar.
</h1>
}
/>
) : (
<>
<Route index element={<RedirectPage to="/overview" />} />
<Route path="overview" element={<OverviewScreen />} />
<Route path="content" element={<ContentScreen />} />
<Route path="photos" element={<PhotosScreen />} />
<Route path="debug" element={<DebugScreen />} />
<Route path={'settings'} element={<SettingsScreen />}>
<Route index element={<GeneralSettings />} />
<Route path="general" element={<GeneralSettings />} />
<Route path="appearance" element={<AppearanceSettings />} />
<Route path="keybindings" element={<KeybindingSettings />} />
<Route path="extensions" element={<ExtensionSettings />} />
<Route path="p2p" element={<P2PSettings />} />
<Route path="contacts" element={<ContactsSettings />} />
<Route path="experimental" element={<ExperimentalSettings />} />
<Route path="keys" element={<KeysSettings />} />
<Route path="libraries" element={<LibrarySettings />} />
<Route path="security" element={<SecuritySettings />} />
<Route path="locations" element={<LocationSettings />} />
<Route path="sharing" element={<SharingSettings />} />
<Route path="sync" element={<SyncSettings />} />
<Route path="tags" element={<TagsSettings />} />
<Route path="library" element={<LibraryGeneralSettings />} />
<Route path="locations" element={<LocationSettings />} />
<Route path="tags" element={<TagsSettings />} />
<Route path="nodes" element={<NodesSettings />} />
<Route path="keys" element={<KeysSettings />} />
<Route path="privacy" element={<PrivacySettings />} />
<Route path="about" element={<AboutSpacedrive />} />
<Route path="changelog" element={<Changelog />} />
<Route path="support" element={<Support />} />
</Route>
<Route path="location/:id" element={<LocationExplorer />} />
<Route path="tag/:id" element={<TagExplorer />} />
<Route path="*" element={<NotFound />} />
</>
)}
</Route>
</Routes>
</Suspense>
);
}

View File

@@ -1,7 +1,7 @@
import { Button } from '@sd/ui';
import { useNavigate } from 'react-router';
export function NotFound() {
export default function NotFound() {
const navigate = useNavigate();
return (
<div

View File

@@ -1,10 +1,8 @@
import { getExplorerStore, useExplorerStore, usePlatform } from '@sd/client';
import { useExplorerStore, usePlatform } from '@sd/client';
import { ExplorerItem } from '@sd/client';
import clsx from 'clsx';
import { useState } from 'react';
import { useSnapshot } from 'valtio';
import { Suspense, lazy, useMemo } from 'react';
import icons from '../../assets/icons';
import { Folder } from '../icons/Folder';
import { isObject, isPath } from './utils';
@@ -13,47 +11,48 @@ interface Props {
size: number;
className?: string;
style?: React.CSSProperties;
iconClassNames?: string;
}
const icons = import.meta.glob('../../../../assets/icons/*.svg');
export default function FileThumb({ data, ...props }: Props) {
const platform = usePlatform();
// const store = useExplorerStore();
const store = useExplorerStore();
if (isPath(data) && data.is_dir)
return <Folder className={props.iconClassNames} size={props.size * 0.7} />;
const Icon = useMemo(() => {
const icon = icons[`../../../../assets/icons/${data.extension as any}.svg`];
const Icon = icon
? lazy(() => icon().then((v) => ({ default: (v as any).ReactComponent })))
: undefined;
return Icon;
}, [data.extension]);
if (isPath(data) && data.is_dir) return <Folder size={props.size * 0.7} />;
const cas_id = isObject(data) ? data.cas_id : data.file?.cas_id;
if (cas_id) {
// this won't work
const new_thumbnail = !!getExplorerStore().newThumbnails[cas_id];
if (!cas_id) return <div></div>;
const has_thumbnail = isObject(data)
? data.has_thumbnail
: isPath(data)
? data.file?.has_thumbnail
: new_thumbnail;
const has_thumbnail = isObject(data)
? data.has_thumbnail
: isPath(data)
? data.file?.has_thumbnail
: !!store.newThumbnails[cas_id];
const url = platform.getThumbnailUrlById(cas_id);
if (has_thumbnail && url)
return (
<img
style={props.style}
// width={props.size}
className={clsx('pointer-events-none', props.className)}
src={url}
/>
);
}
const Icon = icons[data.extension as keyof typeof icons];
if (has_thumbnail)
return (
<img
// onLoad={}
style={props.style}
className={clsx('pointer-events-none z-90', props.className)}
src={platform.getThumbnailUrlById(cas_id)}
/>
);
return (
<div
style={{ width: props.size * 0.8, height: props.size * 0.8 }}
className={clsx('relative m-auto transition duration-200 ', props.iconClassNames)}
className="relative m-auto transition duration-200 "
>
<svg
// BACKGROUND
@@ -67,10 +66,12 @@ export default function FileThumb({ data, ...props }: Props) {
</svg>
{Icon && (
<div className="absolute flex flex-col items-center justify-center w-full h-full mt-0.5 ">
<Icon
className={clsx('w-full h-full ')}
style={{ width: props.size * 0.45, height: props.size * 0.45 }}
/>
<Suspense fallback={<></>}>
<Icon
className={clsx('w-full h-full ')}
style={{ width: props.size * 0.45, height: props.size * 0.45 }}
/>
</Suspense>
<span className="text-xs font-bold text-center uppercase cursor-default text-gray-450">
{data.extension}
</span>

View File

@@ -1,13 +1,14 @@
import { ShareIcon } from '@heroicons/react/24/solid';
import { useLibraryQuery } from '@sd/client';
import { ExplorerContext, ExplorerItem, File, FilePath, Location } from '@sd/client';
import { Button, TextArea } from '@sd/ui';
import { ExplorerContext, ExplorerItem } from '@sd/client';
import { Button } from '@sd/ui';
import { useQuery } from '@tanstack/react-query';
import clsx from 'clsx';
import moment from 'moment';
import dayjs from 'dayjs';
import { Link } from 'phosphor-react';
import { useEffect, useState } from 'react';
import types from '../../constants/file-types.json';
// import types from '../../constants/file-types.json';
import { Tooltip } from '../tooltip/Tooltip';
import FileThumb from './FileThumb';
import { Divider } from './inspector/Divider';
@@ -22,6 +23,11 @@ interface Props {
}
export const Inspector = (props: Props) => {
const { data: types } = useQuery(
['_file-types'],
() => import('../../constants/file-types.json')
);
const is_dir = props.data?.type === 'Path' ? props.data.is_dir : false;
const objectData = isObject(props.data) ? props.data : props.data.file;
@@ -41,18 +47,13 @@ export const Inspector = (props: Props) => {
});
return (
<div className="p-2 pt-0.5 pr-1 overflow-x-hidden custom-scroll inspector-scroll pb-[55px]">
<div className="p-2 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 className="flex bg-black items-center justify-center w-full h-64 mb-[10px] overflow-hidden rounded-lg ">
<FileThumb 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">
<div className="flex flex-col w-full pt-0.5 pb-4 overflow-hidden bg-white rounded-lg shadow select-text dark:shadow-gray-700 dark:bg-gray-550 dark:bg-opacity-40">
<h3 className="pt-3 pl-3 text-base font-bold">
{props.data?.name}
{props.data?.extension && `.${props.data.extension}`}
@@ -111,12 +112,12 @@ export const Inspector = (props: Props) => {
<Divider />
<MetaItem
title="Date Created"
value={moment(props.data?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
value={dayjs(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')}
value={dayjs(props.data?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
/>
{!is_dir && (
<>
@@ -128,7 +129,7 @@ export const Inspector = (props: Props) => {
</span>
)}
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
{props.data?.extension
{props.data?.extension && types !== undefined
? //@ts-ignore
types[props.data.extension.toUpperCase()]?.descriptions.join(' / ')
: 'Unknown'}

View File

@@ -2,7 +2,7 @@
import { useLibraryMutation } from '@sd/client';
import { File } from '@sd/client';
import { TextArea } from '@sd/ui';
import { debounce } from 'lodash';
import debounce from 'lodash/debounce';
import { useCallback, useState } from 'react';
import { Divider } from './Divider';

View File

@@ -3,7 +3,7 @@ import { useLibraryQuery } from '@sd/client';
import { JobReport } from '@sd/client';
import { Button } from '@sd/ui';
import clsx from 'clsx';
import { formatDistanceToNow, formatDuration } from 'date-fns';
import dayjs from 'dayjs';
import { ArrowsClockwise } from 'phosphor-react';
import { Tooltip } from '../tooltip/Tooltip';
@@ -70,12 +70,12 @@ export function JobsManager() {
<span className="text-xs opacity-60">
{job.status === 'Failed' ? 'Failed after' : 'Took'}{' '}
{job.seconds_elapsed
? formatDuration({ seconds: job.seconds_elapsed })
? dayjs.duration({ seconds: job.seconds_elapsed }).humanize()
: 'less than a second'}
</span>
<span className="mx-1 opacity-30">&#8226;</span>
<span className="text-xs opacity-60">
{formatDistanceToNow(new Date(job.date_created))} ago
{dayjs(job.date_created).toNow(true)} ago
</span>
</div>
<span className="text-xs opacity-60">{job.data}</span>

View File

@@ -1,4 +1,4 @@
export const ContentScreen: React.FC<unknown> = (props) => {
export default function ContentScreen() {
// const [address, setAddress] = React.useState('');
return <div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll"></div>;
};
}

View File

@@ -3,7 +3,7 @@ import { useBridgeQuery, useLibraryMutation, useLibraryQuery, usePlatform } from
import CodeBlock from '../components/primitive/Codeblock';
// TODO: Bring this back with a button in the sidebar near settings at the bottom
export const DebugScreen: React.FC<unknown> = (props) => {
export default function DebugScreen() {
const platform = usePlatform();
const { data: nodeState } = useBridgeQuery(['getNode']);
const { data: libraryState } = useBridgeQuery(['library.list']);
@@ -45,4 +45,4 @@ export const DebugScreen: React.FC<unknown> = (props) => {
</div>
</div>
);
};
}

View File

@@ -16,7 +16,7 @@ export function useExplorerParams() {
return { location_id, path, limit };
}
export const LocationExplorer: React.FC<unknown> = () => {
export default function LocationExplorer() {
const { location_id, path } = useExplorerParams();
const { library } = useCurrentLibrary();
@@ -39,4 +39,4 @@ export const LocationExplorer: React.FC<unknown> = () => {
{library!.uuid && explorerData.data && <Explorer data={explorerData.data} />}
</div>
);
};
}

View File

@@ -89,7 +89,7 @@ const StatItem: React.FC<StatItemProps> = (props) => {
);
};
export const OverviewScreen = () => {
export default function OverviewScreen() {
const platform = usePlatform();
const { data: libraryStatistics, isLoading: isStatisticsLoading } = useLibraryQuery([
'library.getStatistics'
@@ -211,4 +211,4 @@ export const OverviewScreen = () => {
</div>
</div>
);
};
}

View File

@@ -1,4 +1,4 @@
export const PhotosScreen: React.FC<unknown> = (props) => {
export default function PhotosScreen() {
return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
<div className="flex flex-col space-y-5 pb-7">
@@ -9,4 +9,4 @@ export const PhotosScreen: React.FC<unknown> = (props) => {
</div>
</div>
);
};
}

View File

@@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom';
import Explorer from '../components/explorer/Explorer';
export const TagExplorer: React.FC<unknown> = () => {
export default function TagExplorer() {
const { id } = useParams();
const { library } = useCurrentLibrary();
@@ -16,4 +16,4 @@ export const TagExplorer: React.FC<unknown> = () => {
)}
</div>
);
};
}

View File

@@ -17,7 +17,7 @@ import {
SettingsScreenContainer
} from '../../components/settings/SettingsScreenContainer';
export const SettingsScreen: React.FC = () => {
export default function SettingsScreen() {
return (
<SettingsScreenContainer>
<SettingsHeading className="!mt-0">Client</SettingsHeading>
@@ -100,4 +100,4 @@ export const SettingsScreen: React.FC = () => {
</SidebarLink>
</SettingsScreenContainer>
);
};
}

BIN
pnpm-lock.yaml generated
View File

Binary file not shown.