diff --git a/interface/app/$libraryId/Explorer/View/ExplorerPath.tsx b/interface/app/$libraryId/Explorer/View/ExplorerPath.tsx index 3935c7673..1a4bf22aa 100644 --- a/interface/app/$libraryId/Explorer/View/ExplorerPath.tsx +++ b/interface/app/$libraryId/Explorer/View/ExplorerPath.tsx @@ -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(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 (
{ > {data?.map((p, index) => { return ( -
pathRedirectHandler(p.name, index)} + - - {p.name} - {index !== (data?.length as number) - 1 && ( - - )} -
+ paths={data.map((p) => p.name)} + path={p} + index={index} + fullPathOnClick={fullPathOnClick} + onClick={() => pathRedirectHandler(p.name, index)} + /> ); })} {selectedItem && (
{data && data.length > 0 && } - - {'name' in selectedItem.item && ( - {selectedItem.item.name} - )} + <> + + {'name' in selectedItem.item ? ( + {selectedItem.item.name} + ) : ( + + {selectedItem.item.file_paths[0]?.name} + + )} +
)}
); }); + +interface Props extends ComponentProps<'div'> { + paths: string[]; + path: { + name: string; + }; + fullPathOnClick: boolean; + index: number; +} + +const Path = ({ paths, path, fullPathOnClick, index, ...rest }: Props) => { + return ( +
+ + {path.name} + {index !== (paths?.length as number) - 1 && } +
+ ); +}; diff --git a/interface/app/$libraryId/tag/$id.tsx b/interface/app/$libraryId/tag/$id.tsx index 70303eb35..e324522cb 100644 --- a/interface/app/$libraryId/tag/$id.tsx +++ b/interface/app/$libraryId/tag/$id.tsx @@ -37,8 +37,7 @@ export const Component = () => { settings: explorerSettings, ...(tag.data && { parent: { type: 'Tag', tag: tag.data } - }), - showPathBar: false + }) }); return (