mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-21 23:18:06 -04:00
[ENG-1167] Explorer path tags route (#1757)
* Tags explorer path * Update ExplorerPath.tsx
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { CaretRight } from '@phosphor-icons/react';
|
||||
import clsx from 'clsx';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useMatch } from 'react-router';
|
||||
import { ExplorerItem } from '@sd/client';
|
||||
import { SearchParamsSchema } from '~/app/route-schemas';
|
||||
import { ComponentProps, memo, useMemo } from 'react';
|
||||
import { useMatch, useNavigate } from 'react-router';
|
||||
import { ExplorerItem, FilePath, FilePathWithObject, useLibraryQuery } from '@sd/client';
|
||||
import { LibraryIdParamsSchema, SearchParamsSchema } from '~/app/route-schemas';
|
||||
import { Icon } from '~/components';
|
||||
import { useOperatingSystem, useZodSearchParams } from '~/hooks';
|
||||
import { useOperatingSystem, useZodRouteParams, useZodSearchParams } from '~/hooks';
|
||||
|
||||
import { useExplorerContext } from '../Context';
|
||||
import { FileThumb } from '../FilePath/Thumb';
|
||||
@@ -17,17 +17,50 @@ export const ExplorerPath = memo(() => {
|
||||
const isEphemeralLocation = useMatch('/:libraryId/ephemeral/:ephemeralId');
|
||||
const os = useOperatingSystem();
|
||||
const realOs = useOperatingSystem(true);
|
||||
const navigate = useNavigate();
|
||||
const libraryId = useZodRouteParams(LibraryIdParamsSchema).libraryId;
|
||||
const pathSlashOS = os === 'browser' ? '/' : realOs === 'windows' ? '\\' : '/';
|
||||
|
||||
const [data, setData] = useState<{ kind: string; name: string }[] | null>(null);
|
||||
const [selectedItem, setSelectedItem] = useState<ExplorerItem | undefined>(undefined);
|
||||
|
||||
const explorerContext = useExplorerContext();
|
||||
const fullPathOnClick = explorerContext.parent?.type === 'Tag';
|
||||
const [{ path }] = useExplorerSearchParams();
|
||||
const [_, setSearchParams] = useZodSearchParams(SearchParamsSchema);
|
||||
const selectedItem = useMemo(() => {
|
||||
if (explorerContext.selectedItems.size !== 1) return;
|
||||
return [...explorerContext.selectedItems][0];
|
||||
}, [explorerContext.selectedItems]);
|
||||
|
||||
const indexedPath =
|
||||
explorerContext.parent?.type === 'Location' && explorerContext.parent.location.path;
|
||||
const filePathData = () => {
|
||||
if (!selectedItem) return;
|
||||
let filePathData: FilePath | FilePathWithObject | null = null;
|
||||
const item = selectedItem as ExplorerItem;
|
||||
switch (item.type) {
|
||||
case 'Path': {
|
||||
filePathData = item.item;
|
||||
break;
|
||||
}
|
||||
case 'Object': {
|
||||
filePathData = item.item.file_paths[0] ?? null;
|
||||
break;
|
||||
}
|
||||
case 'SpacedropPeer': {
|
||||
// objectData = item.item as unknown as Object;
|
||||
// filePathData = item.item.file_paths[0] ?? null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return filePathData;
|
||||
};
|
||||
|
||||
//this is being used with tag page route - when clicking on an object
|
||||
//we get the full path of the object and use it to build the path bar
|
||||
const queriedFullPath = useLibraryQuery(['files.getPath', filePathData()?.id ?? -1], {
|
||||
enabled: selectedItem != null && fullPathOnClick
|
||||
});
|
||||
|
||||
const indexedPath = fullPathOnClick
|
||||
? queriedFullPath.data
|
||||
: explorerContext.parent?.type === 'Location' && explorerContext.parent.location.path;
|
||||
|
||||
//There are cases where the path ends with a '/' and cases where it doesn't
|
||||
const pathInfo = indexedPath
|
||||
@@ -46,12 +79,22 @@ export const ExplorerPath = memo(() => {
|
||||
};
|
||||
|
||||
const pathRedirectHandler = (pathName: string, index: number): void => {
|
||||
if (isEphemeralLocation) {
|
||||
let newPath: string | undefined;
|
||||
if (fullPathOnClick) {
|
||||
if (!explorerContext.selectedItems) return;
|
||||
const objectData = Array.from(explorerContext.selectedItems)[0];
|
||||
if (!objectData) return;
|
||||
if ('file_paths' in objectData.item && objectData) {
|
||||
newPath = pathBuilder(pathInfo as string, pathName);
|
||||
navigate(`/${libraryId}/ephemeral/0`);
|
||||
setSearchParams((params) => ({ ...params, path: newPath }), { replace: true });
|
||||
}
|
||||
} else if (isEphemeralLocation) {
|
||||
const currentPaths = data?.map((p) => p.name).join(pathSlashOS);
|
||||
const newPath = `${pathSlashOS}${pathBuilder(currentPaths as string, pathName)}`;
|
||||
newPath = `${pathSlashOS}${pathBuilder(currentPaths as string, pathName)}`;
|
||||
setSearchParams((params) => ({ ...params, path: newPath }), { replace: true });
|
||||
} else {
|
||||
const newPath = pathBuilder(path as string, pathName);
|
||||
newPath = pathBuilder(path as string, pathName);
|
||||
setSearchParams((params) => ({ ...params, path: index === 0 ? '' : newPath }), {
|
||||
replace: true
|
||||
});
|
||||
@@ -60,30 +103,56 @@ export const ExplorerPath = memo(() => {
|
||||
|
||||
const pathNameLocationName =
|
||||
explorerContext.parent?.type === 'Location' && explorerContext.parent?.location.name;
|
||||
const formatPathData = useCallback(() => {
|
||||
const data = useMemo(() => {
|
||||
if (!pathInfo) return;
|
||||
const splitPaths = pathInfo.replaceAll('/', pathSlashOS).split(pathSlashOS); //replace all '/' with '\' for windows
|
||||
const startIndex = isEphemeralLocation
|
||||
? 1
|
||||
: pathNameLocationName
|
||||
? splitPaths.indexOf(pathNameLocationName)
|
||||
: -1;
|
||||
const updatedPathData = splitPaths.slice(startIndex);
|
||||
const updatedData = updatedPathData.map((path) => ({
|
||||
kind: 'Folder',
|
||||
extension: '',
|
||||
name: path
|
||||
}));
|
||||
setData(updatedData);
|
||||
}, [pathInfo, pathSlashOS, isEphemeralLocation, pathNameLocationName]);
|
||||
const splitPaths = pathInfo?.replaceAll('/', pathSlashOS).split(pathSlashOS); //replace all '/' with '\' for windows
|
||||
|
||||
useEffect(() => {
|
||||
formatPathData();
|
||||
const [first] = explorerContext.selectedItems;
|
||||
if (explorerContext.selectedItems.size === 1) {
|
||||
setSelectedItem(first);
|
||||
} else setSelectedItem(undefined);
|
||||
}, [pathInfo, explorerContext.selectedItems, formatPathData]);
|
||||
//if the path is a full path
|
||||
if (fullPathOnClick && queriedFullPath.data) {
|
||||
if (!selectedItem) return;
|
||||
const selectedItemFilePaths =
|
||||
'file_paths' in selectedItem.item && selectedItem.item.file_paths[0];
|
||||
if (!selectedItemFilePaths) return;
|
||||
const updatedData = splitPaths
|
||||
.map((path) => ({
|
||||
kind: 'Folder',
|
||||
extension: '',
|
||||
name: path
|
||||
}))
|
||||
//remove duplicate path names upon selection + from the result of the full path query
|
||||
.filter(
|
||||
(path) =>
|
||||
path.name !==
|
||||
`${selectedItemFilePaths.name}.${selectedItemFilePaths.extension}` &&
|
||||
path.name !== '' &&
|
||||
path.name !== selectedItemFilePaths.name
|
||||
);
|
||||
return updatedData;
|
||||
|
||||
//handling ephemeral and location paths
|
||||
} else {
|
||||
const startIndex = isEphemeralLocation
|
||||
? 1
|
||||
: pathNameLocationName
|
||||
? splitPaths.indexOf(pathNameLocationName)
|
||||
: -1;
|
||||
const updatedPathData = splitPaths.slice(startIndex);
|
||||
const updatedData = updatedPathData.map((path) => ({
|
||||
kind: 'Folder',
|
||||
extension: '',
|
||||
name: path
|
||||
}));
|
||||
return updatedData;
|
||||
}
|
||||
}, [
|
||||
pathInfo,
|
||||
pathSlashOS,
|
||||
isEphemeralLocation,
|
||||
pathNameLocationName,
|
||||
fullPathOnClick,
|
||||
queriedFullPath.data,
|
||||
selectedItem
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -92,31 +161,58 @@ export const ExplorerPath = memo(() => {
|
||||
>
|
||||
{data?.map((p, index) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => pathRedirectHandler(p.name, index)}
|
||||
<Path
|
||||
key={(p.name + index).toString()}
|
||||
className={clsx(
|
||||
'flex items-center gap-1 transition-all duration-300',
|
||||
index !== data.length - 1 && ' cursor-pointer hover:brightness-125'
|
||||
)}
|
||||
>
|
||||
<Icon name="Folder" size={16} alt="Folder" />
|
||||
<span className="max-w-xs truncate">{p.name}</span>
|
||||
{index !== (data?.length as number) - 1 && (
|
||||
<CaretRight weight="bold" size={10} />
|
||||
)}
|
||||
</div>
|
||||
paths={data.map((p) => p.name)}
|
||||
path={p}
|
||||
index={index}
|
||||
fullPathOnClick={fullPathOnClick}
|
||||
onClick={() => pathRedirectHandler(p.name, index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{selectedItem && (
|
||||
<div className="pointer-events-none flex items-center gap-1">
|
||||
{data && data.length > 0 && <CaretRight weight="bold" size={10} />}
|
||||
<FileThumb size={16} frame frameClassName="!border" data={selectedItem} />
|
||||
{'name' in selectedItem.item && (
|
||||
<span className="max-w-xs truncate">{selectedItem.item.name}</span>
|
||||
)}
|
||||
<>
|
||||
<FileThumb size={16} frame frameClassName="!border" data={selectedItem} />
|
||||
{'name' in selectedItem.item ? (
|
||||
<span className="max-w-xs truncate">{selectedItem.item.name}</span>
|
||||
) : (
|
||||
<span className="max-w-xs truncate">
|
||||
{selectedItem.item.file_paths[0]?.name}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
interface Props extends ComponentProps<'div'> {
|
||||
paths: string[];
|
||||
path: {
|
||||
name: string;
|
||||
};
|
||||
fullPathOnClick: boolean;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const Path = ({ paths, path, fullPathOnClick, index, ...rest }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'flex items-center gap-1 transition-all duration-300',
|
||||
fullPathOnClick
|
||||
? 'cursor-pointer hover:brightness-125'
|
||||
: index !== paths.length - 1 && ' cursor-pointer hover:brightness-125'
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<Icon name="Folder" size={16} alt="Folder" />
|
||||
<span className="max-w-xs truncate">{path.name}</span>
|
||||
{index !== (paths?.length as number) - 1 && <CaretRight weight="bold" size={10} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,8 +37,7 @@ export const Component = () => {
|
||||
settings: explorerSettings,
|
||||
...(tag.data && {
|
||||
parent: { type: 'Tag', tag: tag.data }
|
||||
}),
|
||||
showPathBar: false
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user