mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-20 22:50:11 -04:00
* prototype * `.normalise` helper + only `String` keys * implement it for 'search.paths' * redux devtools * fix * refactor backend * wip: upgrade to rspc fork * mega cursed * Upgrade Specta-related stuff * Upgrade Typescript * Cache debug page * bruh * Fix optimistic library setting * Cache clearing * better timeout * Fix tags page * bit of cleanup --------- Co-authored-by: Brendan Allan <brendonovich@outlook.com>
220 lines
5.5 KiB
TypeScript
220 lines
5.5 KiB
TypeScript
import { X } from '@phosphor-icons/react';
|
|
import clsx from 'clsx';
|
|
import { useMatch, useNavigate, useResolvedPath } from 'react-router';
|
|
import { Link, NavLink } from 'react-router-dom';
|
|
import {
|
|
arraysEqual,
|
|
useBridgeQuery,
|
|
useCache,
|
|
useFeatureFlag,
|
|
useLibraryMutation,
|
|
useLibraryQuery,
|
|
useNodes,
|
|
useOnlineLocations
|
|
} from '@sd/client';
|
|
import { Button, Tooltip } from '@sd/ui';
|
|
import { AddLocationButton } from '~/app/$libraryId/settings/library/locations/AddLocationButton';
|
|
import { Folder, Icon, SubtleButton } from '~/components';
|
|
|
|
import SidebarLink from './Link';
|
|
import LocationsContextMenu from './LocationsContextMenu';
|
|
import Section from './Section';
|
|
import { SeeMore } from './SeeMore';
|
|
import TagsContextMenu from './TagsContextMenu';
|
|
|
|
export const LibrarySection = () => (
|
|
<>
|
|
<SavedSearches />
|
|
<Devices />
|
|
<Locations />
|
|
<Tags />
|
|
</>
|
|
);
|
|
|
|
function SavedSearches() {
|
|
const savedSearches = useLibraryQuery(['search.saved.list']);
|
|
|
|
const path = useResolvedPath('saved-search/:id');
|
|
const match = useMatch(path.pathname);
|
|
const currentSearchId = match?.params?.id;
|
|
|
|
const currentIndex = currentSearchId
|
|
? savedSearches.data?.findIndex((s) => s.id === Number(currentSearchId))
|
|
: undefined;
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const deleteSavedSearch = useLibraryMutation(['search.saved.delete'], {
|
|
onSuccess() {
|
|
if (currentIndex !== undefined && savedSearches.data) {
|
|
const nextIndex = Math.min(currentIndex + 1, savedSearches.data.length - 2);
|
|
|
|
const search = savedSearches.data[nextIndex];
|
|
|
|
if (search) navigate(`saved-search/${search.id}`);
|
|
else navigate(`./`);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!savedSearches.data || savedSearches.data.length < 1) return null;
|
|
|
|
return (
|
|
<Section
|
|
name="Saved Searches"
|
|
// actionArea={
|
|
// <Link to="settings/library/saved-searches">
|
|
// <SubtleButton />
|
|
// </Link>
|
|
// }
|
|
>
|
|
<SeeMore>
|
|
{savedSearches.data.map((search, i) => (
|
|
<SidebarLink
|
|
className="group/button relative w-full"
|
|
to={`saved-search/${search.id}`}
|
|
key={search.id}
|
|
>
|
|
<div className="relative -mt-0.5 mr-1 shrink-0 grow-0">
|
|
<Folder size={18} />
|
|
</div>
|
|
|
|
<span className="truncate">{search.name}</span>
|
|
|
|
<Button
|
|
className="absolute right-1 top-1/2 hidden -translate-y-1/2 rounded-full shadow group-hover/button:block"
|
|
size="icon"
|
|
variant="subtle"
|
|
onClick={(e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
deleteSavedSearch.mutate(search.id);
|
|
}}
|
|
>
|
|
<X size={10} weight="bold" className="text-ink-dull/50" />
|
|
</Button>
|
|
</SidebarLink>
|
|
))}
|
|
</SeeMore>
|
|
</Section>
|
|
);
|
|
}
|
|
|
|
function Devices() {
|
|
const node = useBridgeQuery(['nodeState']);
|
|
const isPairingEnabled = useFeatureFlag('p2pPairing');
|
|
|
|
return (
|
|
<Section
|
|
name="Devices"
|
|
actionArea={
|
|
isPairingEnabled && (
|
|
<Link to="settings/library/nodes">
|
|
<SubtleButton />
|
|
</Link>
|
|
)
|
|
}
|
|
>
|
|
{node.data && (
|
|
<SidebarLink
|
|
className="group relative w-full"
|
|
to={`node/${node.data.id}`}
|
|
key={node.data.id}
|
|
>
|
|
<Icon name="Laptop" size={20} className="mr-1" />
|
|
<span className="truncate">{node.data.name}</span>
|
|
</SidebarLink>
|
|
)}
|
|
<Tooltip
|
|
label="Coming soon! This alpha release doesn't include library sync, it will be ready very soon."
|
|
position="right"
|
|
>
|
|
<Button disabled variant="dotted" className="mt-1 w-full">
|
|
Add Device
|
|
</Button>
|
|
</Tooltip>
|
|
</Section>
|
|
);
|
|
}
|
|
|
|
function Locations() {
|
|
const locationsQuery = useLibraryQuery(['locations.list'], { keepPreviousData: true });
|
|
useNodes(locationsQuery.data?.nodes);
|
|
const locations = useCache(locationsQuery.data?.items);
|
|
const onlineLocations = useOnlineLocations();
|
|
|
|
return (
|
|
<Section
|
|
name="Locations"
|
|
actionArea={
|
|
<Link to="settings/library/locations">
|
|
<SubtleButton />
|
|
</Link>
|
|
}
|
|
>
|
|
<SeeMore>
|
|
{locations?.map((location) => (
|
|
<LocationsContextMenu key={location.id} locationId={location.id}>
|
|
<SidebarLink
|
|
className="borderradix-state-closed:border-transparent group relative w-full radix-state-open:border-accent"
|
|
to={`location/${location.id}`}
|
|
>
|
|
<div className="relative -mt-0.5 mr-1 shrink-0 grow-0">
|
|
<Icon name="Folder" size={18} />
|
|
<div
|
|
className={clsx(
|
|
'absolute bottom-0.5 right-0 h-1.5 w-1.5 rounded-full',
|
|
onlineLocations.some((l) => arraysEqual(location.pub_id, l))
|
|
? 'bg-green-500'
|
|
: 'bg-red-500'
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<span className="truncate">{location.name}</span>
|
|
</SidebarLink>
|
|
</LocationsContextMenu>
|
|
))}
|
|
</SeeMore>
|
|
<AddLocationButton className="mt-1" />
|
|
</Section>
|
|
);
|
|
}
|
|
|
|
function Tags() {
|
|
const result = useLibraryQuery(['tags.list'], { keepPreviousData: true });
|
|
useNodes(result.data?.nodes);
|
|
const tags = useCache(result.data?.items);
|
|
|
|
if (!tags?.length) return;
|
|
|
|
return (
|
|
<Section
|
|
name="Tags"
|
|
actionArea={
|
|
<NavLink to="settings/library/tags">
|
|
<SubtleButton />
|
|
</NavLink>
|
|
}
|
|
>
|
|
<SeeMore>
|
|
{tags?.map((tag) => (
|
|
<TagsContextMenu tagId={tag.id} key={tag.id}>
|
|
<SidebarLink
|
|
className="border radix-state-closed:border-transparent radix-state-open:border-accent"
|
|
to={`tag/${tag.id}`}
|
|
>
|
|
<div
|
|
className="h-[12px] w-[12px] shrink-0 rounded-full"
|
|
style={{ backgroundColor: tag.color || '#efefef' }}
|
|
/>
|
|
<span className="ml-1.5 truncate text-sm">{tag.name}</span>
|
|
</SidebarLink>
|
|
</TagsContextMenu>
|
|
))}
|
|
</SeeMore>
|
|
</Section>
|
|
);
|
|
}
|