mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-05 22:03:16 -04:00
@@ -1,3 +1,7 @@
|
||||
import { ArrowLeft, ArrowRight, Info } from '@phosphor-icons/react';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { iconNames } from '@sd/assets/util';
|
||||
import clsx from 'clsx';
|
||||
import { memo, Suspense, useDeferredValue, useMemo } from 'react';
|
||||
import {
|
||||
ExplorerItem,
|
||||
@@ -5,9 +9,16 @@ import {
|
||||
useLibraryQuery,
|
||||
type EphemeralPathOrder
|
||||
} from '@sd/client';
|
||||
import { Tooltip } from '@sd/ui';
|
||||
import { Button, Tooltip } from '@sd/ui';
|
||||
import { PathParamsSchema, type PathParams } from '~/app/route-schemas';
|
||||
import { useOperatingSystem, useZodSearchParams } from '~/hooks';
|
||||
import { Icon } from '~/components';
|
||||
import {
|
||||
getDismissibleNoticeStore,
|
||||
useDismissibleNoticeStore,
|
||||
useIsDark,
|
||||
useOperatingSystem,
|
||||
useZodSearchParams
|
||||
} from '~/hooks';
|
||||
|
||||
import Explorer from './Explorer';
|
||||
import { ExplorerContextProvider } from './Explorer/Context';
|
||||
@@ -19,7 +30,125 @@ import {
|
||||
import { DefaultTopBarOptions } from './Explorer/TopBarOptions';
|
||||
import { useExplorer, useExplorerSettings } from './Explorer/useExplorer';
|
||||
import { AddLocationButton } from './settings/library/locations/AddLocationButton';
|
||||
import { TOP_BAR_HEIGHT } from './TopBar';
|
||||
import { TopBarPortal } from './TopBar/Portal';
|
||||
import TopBarButton from './TopBar/TopBarButton';
|
||||
|
||||
const NOTICE_ITEMS: { icon: keyof typeof iconNames; name: string }[] = [
|
||||
{
|
||||
icon: 'Folder',
|
||||
name: 'Documents'
|
||||
},
|
||||
{
|
||||
icon: 'Archive',
|
||||
name: 'Keep-Safe'
|
||||
},
|
||||
{
|
||||
icon: 'Executable',
|
||||
name: 'Spacedrive'
|
||||
},
|
||||
{
|
||||
icon: 'Folder',
|
||||
name: 'Music'
|
||||
}
|
||||
];
|
||||
|
||||
const EphemeralNotice = ({ path }: { path: string }) => {
|
||||
const isDark = useIsDark();
|
||||
const { ephemeral: dismissed } = useDismissibleNoticeStore();
|
||||
|
||||
const dismiss = () => (getDismissibleNoticeStore().ephemeral = true);
|
||||
|
||||
return (
|
||||
<Dialog.Root
|
||||
open={!dismissed}
|
||||
onOpenChange={(open) => (getDismissibleNoticeStore().ephemeral = !open)}
|
||||
>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-app/80 backdrop-blur-sm radix-state-closed:animate-out radix-state-closed:fade-out-0 radix-state-open:animate-in radix-state-open:fade-in-0" />
|
||||
<Dialog.Content className="fixed left-[50%] top-[50%] z-50 w-96 translate-x-[-50%] translate-y-[-50%] overflow-hidden rounded-md border border-app-line bg-app shadow-lg outline-none duration-200 radix-state-closed:animate-out radix-state-closed:fade-out-0 radix-state-closed:zoom-out-95 radix-state-closed:slide-out-to-left-1/2 radix-state-closed:slide-out-to-top-[48%] radix-state-open:animate-in radix-state-open:fade-in-0 radix-state-open:zoom-in-95 radix-state-open:slide-in-from-left-1/2 radix-state-open:slide-in-from-top-[48%]">
|
||||
<div className="relative flex aspect-video overflow-hidden border-b border-app-line/50 bg-gradient-to-b from-app-darkBox to-app to-80% pl-5 pt-5">
|
||||
<div
|
||||
className={clsx(
|
||||
'relative flex flex-1 flex-col overflow-hidden rounded-tl-lg border-l border-t border-app-line/75 bg-app shadow-lg',
|
||||
isDark ? 'shadow-app-shade/40' : 'shadow-app-shade/20'
|
||||
)}
|
||||
>
|
||||
<div className="absolute inset-0 z-50 bg-app/80 backdrop-blur-[2px]" />
|
||||
|
||||
<div
|
||||
style={{ height: TOP_BAR_HEIGHT }}
|
||||
className="flex items-center gap-3.5 border-b border-sidebar-divider px-3.5"
|
||||
>
|
||||
<div className="flex">
|
||||
<TopBarButton rounding="left">
|
||||
<ArrowLeft size={14} className="m-[4px]" weight="bold" />
|
||||
</TopBarButton>
|
||||
|
||||
<TopBarButton rounding="right" disabled>
|
||||
<ArrowRight size={14} className="m-[4px]" weight="bold" />
|
||||
</TopBarButton>
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
label="Add path as an indexed location"
|
||||
className="z-50 w-max min-w-0 shrink animate-pulse [animation-duration:_3000ms] hover:animate-none"
|
||||
>
|
||||
<AddLocationButton
|
||||
path={path}
|
||||
className="shadow-md"
|
||||
onClick={dismiss}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="relative flex-1">
|
||||
<div className="absolute inset-0 grid w-[115%] grid-cols-4 gap-3 pl-3 pt-3">
|
||||
{NOTICE_ITEMS.map((item) => (
|
||||
<div key={item.name} className="flex flex-col items-center">
|
||||
<Icon name={item.icon} draggable={false} />
|
||||
<span className="text-center text-xs font-medium text-ink">
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 z-50 h-4 bg-gradient-to-t from-app/70 to-transparent" />
|
||||
</div>
|
||||
|
||||
<div className="p-3 pt-0">
|
||||
<div className="py-4 text-center">
|
||||
<h2 className="text-lg font-semibold text-ink">Local Locations</h2>
|
||||
<p className="mt-px text-sm text-ink-dull">
|
||||
Browse your files and folders directly from your device.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center rounded-md border border-app-line bg-app-box px-3 py-2 text-ink-faint">
|
||||
<Info size={20} weight="light" className="mr-2.5 shrink-0" />
|
||||
<p className="text-sm font-light">
|
||||
Consider indexing your local locations for a faster and more
|
||||
efficient exploration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="accent"
|
||||
className="mt-3 w-full !rounded"
|
||||
size="md"
|
||||
onClick={dismiss}
|
||||
>
|
||||
Got it
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const EphemeralExplorer = memo((props: { args: PathParams }) => {
|
||||
const os = useOperatingSystem();
|
||||
@@ -106,6 +235,7 @@ export const Component = () => {
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<EphemeralNotice path={path.path ?? ''} />
|
||||
<EphemeralExplorer args={path} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -11,9 +11,10 @@ import { openDirectoryPickerDialog } from './openDirectoryPickerDialog';
|
||||
|
||||
interface AddLocationButton extends ButtonProps {
|
||||
path?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const AddLocationButton = ({ path, className, ...props }: AddLocationButton) => {
|
||||
export const AddLocationButton = ({ path, className, onClick, ...props }: AddLocationButton) => {
|
||||
const platform = usePlatform();
|
||||
const transition = {
|
||||
type: 'keyframes',
|
||||
@@ -50,6 +51,8 @@ export const AddLocationButton = ({ path, className, ...props }: AddLocationButt
|
||||
dialogManager.create((dp) => (
|
||||
<AddLocationDialog path={path ?? ''} {...dp} />
|
||||
));
|
||||
|
||||
onClick?.();
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -4,7 +4,8 @@ import { valtioPersist } from '@sd/client';
|
||||
export const dismissibleNoticeStore = valtioPersist('dismissible-notice', {
|
||||
mediaView: false,
|
||||
gridView: false,
|
||||
listView: false
|
||||
listView: false,
|
||||
ephemeral: false
|
||||
});
|
||||
|
||||
export function useDismissibleNoticeStore() {
|
||||
|
||||
Reference in New Issue
Block a user