mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-02-20 07:37:26 -05:00
[ENG-1452] Default to Home (#1801)
* use React.children for SeeMore * use Home dir as default route when available
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
import { EjectSimple } from '@phosphor-icons/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useBridgeQuery, useLibraryQuery } from '@sd/client';
|
||||
import { Button, toast, tw } from '@sd/ui';
|
||||
import { Icon, IconName } from '~/components';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
import { useHomeDir } from '~/hooks/useHomeDir';
|
||||
|
||||
import SidebarLink from './Link';
|
||||
import Section from './Section';
|
||||
import SeeMore from './SeeMore';
|
||||
import { SeeMore } from './SeeMore';
|
||||
|
||||
const Name = tw.span`truncate`;
|
||||
|
||||
@@ -29,24 +28,14 @@ const SidebarIcon = ({ name }: { name: IconName }) => {
|
||||
};
|
||||
|
||||
export const EphemeralSection = () => {
|
||||
const platform = usePlatform();
|
||||
|
||||
const homeDir = useQuery(['userDirs', 'home'], () => {
|
||||
if (platform.userHomeDir) return platform.userHomeDir();
|
||||
else return null;
|
||||
});
|
||||
|
||||
const locations = useLibraryQuery(['locations.list']);
|
||||
|
||||
const homeDir = useHomeDir();
|
||||
const volumes = useBridgeQuery(['volumes.list']);
|
||||
|
||||
// this will return an array of location ids that are also volumes
|
||||
// { "/Mount/Point": 1, "/Mount/Point2": 2"}
|
||||
type LocationIdsMap = {
|
||||
[key: string]: number;
|
||||
};
|
||||
|
||||
const locationIdsForVolumes = useMemo<LocationIdsMap>(() => {
|
||||
const locationIdsForVolumes = useMemo(() => {
|
||||
if (!locations.data || !volumes.data) return {};
|
||||
|
||||
const volumePaths = volumes.data.map((volume) => volume.mount_points[0] ?? null);
|
||||
@@ -55,106 +44,82 @@ export const EphemeralSection = () => {
|
||||
volumePaths.includes(location.path)
|
||||
);
|
||||
|
||||
const locationIdsMap = matchedLocations.reduce((acc, location) => {
|
||||
if (location.path) {
|
||||
acc[location.path] = location.id;
|
||||
const locationIdsMap = matchedLocations.reduce(
|
||||
(acc, location) => {
|
||||
if (location.path) {
|
||||
acc[location.path] = location.id;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as {
|
||||
[key: string]: number;
|
||||
}
|
||||
return acc;
|
||||
}, {} as LocationIdsMap);
|
||||
);
|
||||
|
||||
return locationIdsMap;
|
||||
}, [locations.data, volumes.data]);
|
||||
|
||||
const items = [
|
||||
{ type: 'network' },
|
||||
homeDir.data ? { type: 'home', path: homeDir.data } : null,
|
||||
...(volumes.data || []).flatMap((volume, volumeIndex) =>
|
||||
volume.mount_points.map((mountPoint, index) =>
|
||||
mountPoint !== homeDir.data
|
||||
? { type: 'volume', volume, mountPoint, volumeIndex, index }
|
||||
: null
|
||||
)
|
||||
const mountPoints = (volumes.data || []).flatMap((volume, volumeIndex) =>
|
||||
volume.mount_points.map((mountPoint, index) =>
|
||||
mountPoint !== homeDir.data
|
||||
? { type: 'volume', volume, mountPoint, volumeIndex, index }
|
||||
: null
|
||||
)
|
||||
].filter(Boolean) as Array<{
|
||||
type: string;
|
||||
path?: string;
|
||||
volume?: any;
|
||||
mountPoint?: string;
|
||||
volumeIndex?: number;
|
||||
index?: number;
|
||||
}>;
|
||||
);
|
||||
|
||||
return (
|
||||
<Section name="Local">
|
||||
<SeeMore
|
||||
items={items}
|
||||
renderItem={(item, index) => {
|
||||
<SeeMore>
|
||||
<SidebarLink className="group relative w-full" to="network">
|
||||
<SidebarIcon name="Globe" />
|
||||
<Name>Network</Name>
|
||||
</SidebarLink>
|
||||
{homeDir.data && (
|
||||
<SidebarLink
|
||||
to={`ephemeral/0?path=${homeDir.data}`}
|
||||
className="group relative w-full border border-transparent"
|
||||
>
|
||||
<SidebarIcon name="Home" />
|
||||
<Name>Home</Name>
|
||||
</SidebarLink>
|
||||
)}
|
||||
{mountPoints.map((item) => {
|
||||
if (!item) return;
|
||||
|
||||
const locationId = locationIdsForVolumes[item.mountPoint ?? ''];
|
||||
|
||||
if (item?.type === 'network') {
|
||||
return (
|
||||
<SidebarLink
|
||||
className="group relative w-full"
|
||||
to="./network"
|
||||
key={index}
|
||||
>
|
||||
<SidebarIcon name="Globe" />
|
||||
<Name>Network</Name>
|
||||
</SidebarLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (item?.type === 'home') {
|
||||
return (
|
||||
<SidebarLink
|
||||
to={`ephemeral/0?path=${item.path}`}
|
||||
className="group relative w-full border border-transparent"
|
||||
key={index}
|
||||
>
|
||||
<SidebarIcon name="Home" />
|
||||
<Name>Home</Name>
|
||||
</SidebarLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (item?.type === 'volume') {
|
||||
const key = `${item.volumeIndex}-${item.index}`;
|
||||
const name =
|
||||
item.mountPoint === '/'
|
||||
? 'Root'
|
||||
: item.index === 0
|
||||
? item.volume.name
|
||||
: item.mountPoint;
|
||||
|
||||
const toPath =
|
||||
locationId !== undefined
|
||||
? `location/${locationId}`
|
||||
: `ephemeral/${key}?path=${item.mountPoint}`;
|
||||
|
||||
return (
|
||||
<SidebarLink
|
||||
to={toPath}
|
||||
key={key}
|
||||
className="group relative w-full border border-transparent"
|
||||
>
|
||||
<SidebarIcon
|
||||
name={
|
||||
item.volume.file_system === 'exfat'
|
||||
? 'SD'
|
||||
: item.volume.name === 'Macintosh HD'
|
||||
? 'HDD'
|
||||
: 'Drive'
|
||||
}
|
||||
/>
|
||||
<Name>{name}</Name>
|
||||
{item.volume.disk_type === 'Removable' && <EjectButton />}
|
||||
</SidebarLink>
|
||||
);
|
||||
}
|
||||
|
||||
return null; // This should never be reached, but is here to satisfy TypeScript
|
||||
}}
|
||||
/>
|
||||
const key = `${item.volumeIndex}-${item.index}`;
|
||||
const name =
|
||||
item.mountPoint === '/'
|
||||
? 'Root'
|
||||
: item.index === 0
|
||||
? item.volume.name
|
||||
: item.mountPoint;
|
||||
const toPath =
|
||||
locationId !== undefined
|
||||
? `location/${locationId}`
|
||||
: `ephemeral/${key}?path=${item.mountPoint}`;
|
||||
return (
|
||||
<SidebarLink
|
||||
to={toPath}
|
||||
key={key}
|
||||
className="group relative w-full border border-transparent"
|
||||
>
|
||||
<SidebarIcon
|
||||
name={
|
||||
item.volume.file_system === 'exfat'
|
||||
? 'SD'
|
||||
: item.volume.name === 'Macintosh HD'
|
||||
? 'HDD'
|
||||
: 'Drive'
|
||||
}
|
||||
/>
|
||||
<Name>{name}</Name>
|
||||
{item.volume.disk_type === 'Removable' && <EjectButton />}
|
||||
</SidebarLink>
|
||||
);
|
||||
})}
|
||||
</SeeMore>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useSavedSearches } from '../../Explorer/Search/SavedSearches';
|
||||
import SidebarLink from './Link';
|
||||
import LocationsContextMenu from './LocationsContextMenu';
|
||||
import Section from './Section';
|
||||
import SeeMore from './SeeMore';
|
||||
import { SeeMore } from './SeeMore';
|
||||
import TagsContextMenu from './TagsContextMenu';
|
||||
|
||||
type SidebarGroup = {
|
||||
@@ -142,9 +142,8 @@ export const LibrarySection = () => {
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<SeeMore
|
||||
items={locationsQuery.data || []}
|
||||
renderItem={(location, index) => (
|
||||
<SeeMore>
|
||||
{locationsQuery.data?.map((location) => (
|
||||
<LocationsContextMenu key={location.id} locationId={location.id}>
|
||||
<SidebarLink
|
||||
onContextMenu={() =>
|
||||
@@ -179,8 +178,8 @@ export const LibrarySection = () => {
|
||||
<span className="truncate">{location.name}</span>
|
||||
</SidebarLink>
|
||||
</LocationsContextMenu>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</SeeMore>
|
||||
<AddLocationButton className="mt-1" />
|
||||
</Section>
|
||||
{!!tags.data?.length && (
|
||||
@@ -192,9 +191,8 @@ export const LibrarySection = () => {
|
||||
</NavLink>
|
||||
}
|
||||
>
|
||||
<SeeMore
|
||||
items={tags.data}
|
||||
renderItem={(tag, index) => (
|
||||
<SeeMore>
|
||||
{tags.data?.map((tag, index) => (
|
||||
<TagsContextMenu tagId={tag.id} key={tag.id}>
|
||||
<SidebarLink
|
||||
onContextMenu={() =>
|
||||
@@ -219,8 +217,8 @@ export const LibrarySection = () => {
|
||||
<span className="ml-1.5 truncate text-sm">{tag.name}</span>
|
||||
</SidebarLink>
|
||||
</TagsContextMenu>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</SeeMore>
|
||||
</Section>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { Children, PropsWithChildren, useState } from 'react';
|
||||
|
||||
export const SEE_MORE_COUNT = 5;
|
||||
|
||||
interface SeeMoreProps<T> {
|
||||
items: T[];
|
||||
renderItem: (item: T, index: number) => ReactNode;
|
||||
interface Props extends PropsWithChildren {
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
const SeeMore = <T,>({ items, renderItem, limit = SEE_MORE_COUNT }: SeeMoreProps<T>) => {
|
||||
export function SeeMore({ children, limit = SEE_MORE_COUNT }: Props) {
|
||||
const [seeMore, setSeeMore] = useState(false);
|
||||
|
||||
const displayedItems = seeMore ? items : items.slice(0, limit);
|
||||
const childrenArray = Children.toArray(children);
|
||||
|
||||
return (
|
||||
<>
|
||||
{displayedItems.map((item, index) => renderItem(item, index))}
|
||||
{items.length > limit && (
|
||||
{childrenArray.map((child, index) => (seeMore || index < limit ? child : null))}
|
||||
{childrenArray.length > limit && (
|
||||
<div
|
||||
onClick={() => setSeeMore(!seeMore)}
|
||||
className="mb-1 ml-2 mt-0.5 cursor-pointer text-center text-tiny font-semibold text-ink-faint/50 transition hover:text-accent"
|
||||
@@ -26,6 +24,4 @@ const SeeMore = <T,>({ items, renderItem, limit = SEE_MORE_COUNT }: SeeMoreProps
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SeeMore;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import { Suspense, useEffect, useMemo, useRef } from 'react';
|
||||
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
|
||||
import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
ClientContextProvider,
|
||||
initPlausible,
|
||||
@@ -48,7 +48,7 @@ const Layout = () => {
|
||||
const firstLibrary = libraries.data[0];
|
||||
|
||||
if (firstLibrary) return <Navigate to={`/${firstLibrary.uuid}`} replace />;
|
||||
else return <Navigate to="./network" replace />;
|
||||
else return <Navigate to="./" replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { useHomeDir } from '~/hooks/useHomeDir';
|
||||
|
||||
import settingsRoutes from './settings';
|
||||
|
||||
@@ -37,6 +38,13 @@ export default [
|
||||
{
|
||||
index: true,
|
||||
Component: () => {
|
||||
const homeDir = useHomeDir();
|
||||
|
||||
if (homeDir.data)
|
||||
return (
|
||||
<Navigate to={`ephemeral/0?${new URLSearchParams({ path: homeDir.data })}`} />
|
||||
);
|
||||
|
||||
return <Navigate to="network" />;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ const Index = () => {
|
||||
|
||||
const libraryId = currentLibrary ? currentLibrary.uuid : libraries.data[0]?.uuid;
|
||||
|
||||
return <Navigate to={`${libraryId}/network`} replace />;
|
||||
return <Navigate to={`${libraryId}`} replace />;
|
||||
};
|
||||
|
||||
const Wrapper = () => {
|
||||
|
||||
15
interface/hooks/useHomeDir.ts
Normal file
15
interface/hooks/useHomeDir.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
|
||||
export function useHomeDir() {
|
||||
const platform = usePlatform();
|
||||
|
||||
return useQuery(
|
||||
['userDirs', 'home'],
|
||||
() => {
|
||||
if (platform.userHomeDir) return platform.userHomeDir();
|
||||
else return null;
|
||||
},
|
||||
{ suspense: true }
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user