mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-07 14:53:16 -04:00
bundle splitting and performance optimisations
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -65,4 +65,4 @@ examples/*/*.lock
|
||||
/core/src/prisma.rs
|
||||
|
||||
/sdserver_data
|
||||
.spacedrive
|
||||
.spacedrive
|
||||
@@ -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",
|
||||
|
||||
BIN
apps/mobile/pnpm-lock.yaml
generated
BIN
apps/mobile/pnpm-lock.yaml
generated
Binary file not shown.
@@ -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<()>>>> =
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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">•</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>
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
BIN
pnpm-lock.yaml
generated
Binary file not shown.
Reference in New Issue
Block a user