mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-19 22:19:49 -04:00
281 lines
8.1 KiB
TypeScript
281 lines
8.1 KiB
TypeScript
import { CogIcon, LockClosedIcon } from '@heroicons/react/24/outline';
|
|
import { PlusIcon } from '@heroicons/react/24/solid';
|
|
import {
|
|
LocationCreateArgs,
|
|
useCurrentLibrary,
|
|
useLibraryMutation,
|
|
useLibraryQuery,
|
|
usePlatform
|
|
} from '@sd/client';
|
|
import { Button, CategoryHeading, Dropdown, OverlayPanel, cva, tw } from '@sd/ui';
|
|
import clsx from 'clsx';
|
|
import { CheckCircle, CirclesFour, Planet, ShareNetwork } from 'phosphor-react';
|
|
import { PropsWithChildren } from 'react';
|
|
import { NavLink, NavLinkProps, useNavigate } from 'react-router-dom';
|
|
|
|
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
|
|
import CreateLibraryDialog from '../dialog/CreateLibraryDialog';
|
|
import { Folder } from '../icons/Folder';
|
|
import { JobsManager } from '../jobs/JobManager';
|
|
import RunningJobsWidget from '../jobs/RunningJobsWidget';
|
|
import { MacTrafficLights } from '../os/TrafficLights';
|
|
|
|
const sidebarItemClass = cva(
|
|
'max-w mb-[2px] rounded px-2 py-1 gap-0.5 flex flex-row flex-grow items-center font-medium truncate text-sm',
|
|
{
|
|
variants: {
|
|
isActive: {
|
|
true: 'bg-sidebar-selected/40 text-ink',
|
|
false: 'text-ink-dull'
|
|
},
|
|
isTransparent: {
|
|
true: 'bg-opacity-90',
|
|
false: ''
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
export const SidebarLink = (props: PropsWithChildren<NavLinkProps>) => {
|
|
const os = useOperatingSystem();
|
|
return (
|
|
<NavLink {...props}>
|
|
{({ isActive }) => (
|
|
<span
|
|
className={clsx(
|
|
sidebarItemClass({ isActive, isTransparent: os === 'macOS' }),
|
|
props.className
|
|
)}
|
|
>
|
|
{props.children}
|
|
</span>
|
|
)}
|
|
</NavLink>
|
|
);
|
|
};
|
|
|
|
const Icon = ({ component: Icon, ...props }: any) => (
|
|
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
|
|
);
|
|
|
|
// cute little helper to decrease code clutter
|
|
const macOnly = (platform: string | undefined, classnames: string) =>
|
|
platform === 'macOS' ? classnames : '';
|
|
|
|
function WindowControls() {
|
|
const { platform } = usePlatform();
|
|
|
|
const showControls = window.location.search.includes('showControls');
|
|
if (platform === 'tauri' || showControls) {
|
|
return (
|
|
<div data-tauri-drag-region className="flex-shrink-0 h-7">
|
|
{/* We do not provide the onClick handlers for 'MacTrafficLights' because this is only used in demo mode */}
|
|
{showControls && <MacTrafficLights className="z-50 absolute top-[13px] left-[13px]" />}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const SidebarCategoryHeading = tw(CategoryHeading)`mt-5 mb-1 ml-1`;
|
|
|
|
function LibraryScopedSection() {
|
|
const platform = usePlatform();
|
|
const { data: locations } = useLibraryQuery(['locations.list'], { keepPreviousData: true });
|
|
const { data: tags } = useLibraryQuery(['tags.list'], { keepPreviousData: true });
|
|
const { mutate: createLocation } = useLibraryMutation('locations.create');
|
|
|
|
return (
|
|
<>
|
|
<div>
|
|
<SidebarCategoryHeading>Locations</SidebarCategoryHeading>
|
|
{locations?.map((location) => {
|
|
return (
|
|
<div key={location.id} className="flex flex-row items-center">
|
|
<NavLink
|
|
className="relative w-full group"
|
|
to={{
|
|
pathname: `location/${location.id}`
|
|
}}
|
|
>
|
|
{({ isActive }) => (
|
|
<span className={sidebarItemClass({ isActive })}>
|
|
<div className="-mt-0.5 mr-1 flex-grow-0 flex-shrink-0">
|
|
<Folder size={18} />
|
|
</div>
|
|
|
|
<span className="flex-grow flex-shrink-0">{location.name}</span>
|
|
</span>
|
|
)}
|
|
</NavLink>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{(locations?.length || 0) < 4 && (
|
|
<button
|
|
onClick={() => {
|
|
if (!platform.openFilePickerDialog) {
|
|
// TODO: Support opening locations on web
|
|
alert('Opening a dialogue is not supported on this platform!');
|
|
return;
|
|
}
|
|
platform.openFilePickerDialog().then((result) => {
|
|
// TODO: Pass indexer rules ids to create location
|
|
if (result)
|
|
createLocation({
|
|
path: result as string,
|
|
indexer_rules_ids: []
|
|
} as LocationCreateArgs);
|
|
});
|
|
}}
|
|
className={clsx(
|
|
'w-full px-2 py-1 mt-1 text-xs font-medium text-center',
|
|
'rounded border border-dashed border-sidebar-line hover:border-sidebar-selected',
|
|
'cursor-normal transition text-ink-faint'
|
|
)}
|
|
>
|
|
Add Location
|
|
</button>
|
|
)}
|
|
</div>
|
|
{tags?.length ? (
|
|
<div>
|
|
<SidebarCategoryHeading>Tags</SidebarCategoryHeading>
|
|
<div className="mt-1 mb-2">
|
|
{tags?.slice(0, 6).map((tag, index) => (
|
|
<SidebarLink key={index} to={`tag/${tag.id}`} className="">
|
|
<div
|
|
className="w-[12px] h-[12px] rounded-full"
|
|
style={{ backgroundColor: tag.color || '#efefef' }}
|
|
/>
|
|
<span className="ml-1.5 text-sm">{tag.name}</span>
|
|
</SidebarLink>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<></>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function Sidebar() {
|
|
const navigate = useNavigate();
|
|
const os = useOperatingSystem();
|
|
const { library, libraries, isLoading: isLoadingLibraries, switchLibrary } = useCurrentLibrary();
|
|
|
|
// const itemStyles = macOnly(os, 'dark:hover:bg-gray-550 dark:hover:bg-opacity-50');
|
|
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'flex relative flex-col flex-grow-0 flex-shrink-0 w-44 min-h-full px-2.5 overflow-x-hidden overflow-y-scroll border-r border-sidebar-divider no-scrollbar bg-sidebar',
|
|
macOnly(os, 'bg-opacity-[0.80]')
|
|
)}
|
|
>
|
|
<WindowControls />
|
|
|
|
<Dropdown.Root
|
|
className="mt-2"
|
|
button={
|
|
<Dropdown.Button
|
|
variant="gray"
|
|
className={clsx(
|
|
`w-full mb-1 mt-1 -mr-0.5`,
|
|
`border-sidebar-line/70 bg-sidebar-button active:border-sidebar-line text-ink`,
|
|
(library === null || isLoadingLibraries) && '!text-ink-faint'
|
|
// macOnly(os, '!bg-opacity-80 !border-opacity-40')
|
|
)}
|
|
>
|
|
<span className="truncate">
|
|
{isLoadingLibraries ? 'Loading...' : library ? library.config.name : ' '}
|
|
</span>
|
|
</Dropdown.Button>
|
|
}
|
|
// to support the transparent sidebar on macOS we use slightly adjusted styles
|
|
// itemsClassName={macOnly(os, 'bg-app/60')}
|
|
>
|
|
<Dropdown.Section>
|
|
{libraries?.map((lib) => (
|
|
<Dropdown.Item
|
|
selected={lib.uuid === library?.uuid}
|
|
key={lib.uuid}
|
|
onClick={() => switchLibrary(lib.uuid)}
|
|
>
|
|
{lib.config.name}
|
|
</Dropdown.Item>
|
|
))}
|
|
</Dropdown.Section>
|
|
<Dropdown.Section>
|
|
<Dropdown.Item icon={CogIcon} to="settings/library">
|
|
Library Settings
|
|
</Dropdown.Item>
|
|
<CreateLibraryDialog>
|
|
<Dropdown.Item icon={PlusIcon}>Add Library</Dropdown.Item>
|
|
</CreateLibraryDialog>
|
|
<Dropdown.Item icon={LockClosedIcon} onClick={() => alert('TODO: Not implemented yet!')}>
|
|
Lock
|
|
</Dropdown.Item>
|
|
</Dropdown.Section>
|
|
</Dropdown.Root>
|
|
<div className="pt-1">
|
|
<SidebarLink to="/overview">
|
|
<Icon component={Planet} />
|
|
Overview
|
|
</SidebarLink>
|
|
<SidebarLink to="photos">
|
|
<Icon component={ShareNetwork} />
|
|
Nodes
|
|
</SidebarLink>
|
|
<SidebarLink to="content">
|
|
<Icon component={CirclesFour} />
|
|
Spaces
|
|
</SidebarLink>
|
|
</div>
|
|
|
|
{library && <LibraryScopedSection />}
|
|
|
|
<div className="flex-grow " />
|
|
|
|
{library && <RunningJobsWidget />}
|
|
|
|
{/* <div className="fixed w-[174px] bottom-[2px] left-[2px] h-20 rounded-[8px] bg-gradient-to-t from-sidebar-box/90 via-sidebar-box/50 to-transparent" /> */}
|
|
|
|
<div className="fixed bottom-0 flex flex-col mt-2 mb-3">
|
|
<div className="flex flex-row">
|
|
<NavLink to="/settings/general">
|
|
{({ isActive }) => (
|
|
<Button forIcon className={clsx('hover:!bg-opacity-20')}>
|
|
<CogIcon className="w-5 h-5 mt-[1px]" />
|
|
</Button>
|
|
)}
|
|
</NavLink>
|
|
<OverlayPanel
|
|
className="focus:outline-none"
|
|
transformOrigin="bottom left"
|
|
disabled={!library}
|
|
trigger={
|
|
<Button
|
|
forIcon
|
|
className={clsx(
|
|
'!outline-none hover:!bg-opacity-20 disabled:opacity-50 disabled:cursor-not-allowed'
|
|
)}
|
|
>
|
|
<CheckCircle className="w-5 h-5" />
|
|
</Button>
|
|
}
|
|
>
|
|
<div className="block w-[500px] h-96">
|
|
<JobsManager />
|
|
</div>
|
|
</OverlayPanel>
|
|
</div>
|
|
<span className="w-full ml-1 mt-1 text-[7pt] text-ink-faint/50">Development Build</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|