Skip to content

Commit

Permalink
feat/refactor: create thumbnail component. reset detailed image src o…
Browse files Browse the repository at this point in the history
…n file tree change
  • Loading branch information
fredrikmonsen committed Dec 16, 2024
1 parent 2dd5e14 commit c8d95ad
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 101 deletions.
20 changes: 7 additions & 13 deletions src/features/detailed-image-view/detailed-image-view.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import React, { useEffect } from 'react';
import { X } from 'lucide-react';
import React, {useEffect} from 'react';

interface DetailedImageViewProps {
imageSrc?: string;
displayTitle: boolean;
onClose: () => void;
}

export default function DetailedImageView(props: DetailedImageViewProps) {

// Handle exit button click event
const handleExit = () => {
props.onClose();
}

// On Escape key press, close the detailed image view
export default function DetailedImageView({imageSrc, displayTitle, onClose}: DetailedImageViewProps) {
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
props.onClose();
onClose();
}
}

Expand All @@ -29,13 +22,14 @@ export default function DetailedImageView(props: DetailedImageViewProps) {

return (
<div className="relative bg-gray-200 bg-opacity-25 dark:bg-gray-700 dark:bg-opacity-25">
{displayTitle && <p className="text-xl text-center py-4">{imageSrc?.split('%2F').pop() ?? ''}</p>}
<button
onClick={handleExit}
onClick={onClose}
className="absolute top-0 right-0 m-4 w-10 h-10 flex items-center justify-center rounded-full bg-gray-800 text-white"
>
x
</button>
<img src={props.imageSrc} alt="Image in full size" className="max-h-screen w-full object-contain" />
<img src={imageSrc} alt="Image in full size" className="p-2.5 max-h-screen w-full object-contain" />
</div>
);
}
137 changes: 49 additions & 88 deletions src/features/files-container/files-container.tsx
Original file line number Diff line number Diff line change
@@ -1,105 +1,66 @@
import React from 'react';
import { convertFileSrc } from '@tauri-apps/api/core';
import { File, Folder } from 'lucide-react';
import { formatFileNames } from '../../util/file-utils';
import { useTrokkFiles } from '../../context/trokk-files-context.tsx';
import { FileTree } from '../../model/file-tree.ts';
import { sep } from '@tauri-apps/api/path';
import React, {useEffect} from 'react';
import {convertFileSrc} from '@tauri-apps/api/core';
import {Folder} from 'lucide-react';
import {useTrokkFiles} from '../../context/trokk-files-context.tsx';
import DetailedImageView from '../detailed-image-view/detailed-image-view.tsx';

const supportedFileTypes = ['jpeg', 'jpg', 'png', 'gif', 'webp'];
import Thumbnail from '../thumbnail/thumbnail.tsx';
import {FileTree} from '../../model/file-tree.ts';

const FilesContainer: React.FC = () => {
const { state, dispatch } = useTrokkFiles();
const [selectedImgSrc, setselectedImgSrc] = React.useState<string | undefined>(undefined);

const getFileExtension = (path: string) => path?.split('.')?.pop() || '';

/* Thumbnail directories are generated where '.tif' files are located.
* Used to find corresponding '.webp' thumbnail for a tif, for example:
*
* folder
* ├── image_1.tif
* ├── image_2.tif
* ├── image_3.tif
* └── .thumbnails
* ├── image_1.webp
* ├── image_2.webp
* └── image_3.webp
*/
function getThumbnailFromTree(tree: FileTree): FileTree | undefined {
const thumbnailPath = tree.path.substring(
0,
tree.path.length - tree.name.length
) + '.thumbnails' + sep() + tree.name.split('.')[0] + '.webp';
return state.treeIndex.get(thumbnailPath);
}


const getThumbnailExtensionFromTree = (tree: FileTree) => {
const foundThumbnail = getThumbnailFromTree(tree);
return foundThumbnail ? getFileExtension(foundThumbnail.name) : undefined;
};

const getThumbnailURIFromTree = (tree: FileTree) => {
const foundThumbnail = getThumbnailFromTree(tree);
return foundThumbnail ? convertFileSrc(foundThumbnail.path) : undefined;
const handleDispatch = (child: FileTree) => {
dispatch({ type: 'SET_CURRENT_AND_EXPAND_PARENTS', payload: child });
setselectedImgSrc(undefined);
};

const truncateMiddle = (str: string, frontLen: number, backLen: number) => {
if (str.length <= frontLen + backLen) return str;
return str.slice(0, frontLen) + '...' + str.slice(str.length - backLen);
};
useEffect(() => {
setselectedImgSrc(undefined);
}, [state.current]);

return (
<div className="flex flex-wrap overflow-y-auto h-[calc(96%)] justify-start content-start ml-4">
{
selectedImgSrc && (
<DetailedImageView
imageSrc={selectedImgSrc}
onClose={() => setselectedImgSrc(undefined)}
/>
)
}

{state.current && state.current.children ? (
state.current.children.length !== 0 ? (
state.current.children.map((child) => (
!child.name.startsWith('.thumbnails') && child.isDirectory ? (
<button
key={child.path}
className="max-w-[150px]"
onClick={() => dispatch({ type: 'SET_CURRENT_AND_EXPAND_PARENTS', payload: child })}
>
<Folder size="96" />
<i>{child.name}</i>
</button>
<>
{
selectedImgSrc && (
<DetailedImageView
imageSrc={selectedImgSrc}
displayTitle={true}
onClose={() => setselectedImgSrc(undefined)}
/>
)
}
{
state.current.children.length !== 0 ? (
state.current.children.map((child) => (
!child.name.startsWith('.thumbnails') && child.isDirectory ? (
<button
key={child.path}
className="max-w-[150px]"
onClick={() => handleDispatch(child)}
>
<Folder size="96" />
<i>{child.name}</i>
</button>
) : (
<Thumbnail
key={child.name}
onClick={() => setselectedImgSrc(convertFileSrc(child.path))}
fileTree={child}
/>
)
))
) : (
supportedFileTypes.includes(getFileExtension(child.path)) ? (
<div key={child.path} className="border-2 border-stone-500 max-w-[150px] mr-2 mb-2"
onClick={() => setselectedImgSrc(convertFileSrc(child.path))}>
<img src={convertFileSrc(child.path)} alt={child.name}/>
<i>{formatFileNames(child.name)}</i>
</div>
) : getThumbnailExtensionFromTree(child) === 'webp' ? (
<div key={child.path} className="border-2 border-stone-500 max-w-[150px] mr-2 mb-2"
onClick={() => setselectedImgSrc(getThumbnailURIFromTree(child))}>
<img src={getThumbnailURIFromTree(child)} alt={child.name}/>
<i>{truncateMiddle(formatFileNames(child.name), 7, 10)}</i>
</div>
) : child.name !== '.thumbnails' && (
<div key={child.path} className="max-w-[150px] mr-2 mb-2 flex flex-col items-center"
onClick={() => setselectedImgSrc(undefined)}>
<File size="96" color="gray"/>
<i>{truncateMiddle(child.name, 7, 10)}</i>
</div>
)
<p className="m-8 font-bold break-words">
Ingen filer i mappen.
</p>
)
))
) : (
<p className="m-8 font-bold break-words">
Ingen filer i mappen.
</p>
)
}
</>

) : (
<p className="m-8 font-bold break-words">
Velg en mappe i listen til venstre. <br />
Expand Down
78 changes: 78 additions & 0 deletions src/features/thumbnail/thumbnail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import {formatFileNames} from '../../util/file-utils.ts';
import {FileTree} from '../../model/file-tree.ts';
import {convertFileSrc} from '@tauri-apps/api/core';
import {File} from 'lucide-react';
import {sep} from '@tauri-apps/api/path';
import {useTrokkFiles} from '../../context/trokk-files-context.tsx';

interface ThumbnailProps {
fileTree: FileTree;
onClick: () => void;
}

const supportedFileTypes = ['jpeg', 'jpg', 'png', 'gif', 'webp'];

export default function Thumbnail({ fileTree, onClick }: ThumbnailProps) {
const {state} = useTrokkFiles();

const getFileExtension = (path?: string) => path?.split('.')?.pop() || '';

/* Thumbnail directories are generated where '.tif' files are located.
* Used to find corresponding '.webp' thumbnail for a tif, for example:
*
* folder
* ├── image_1.tif
* ├── image_2.tif
* ├── image_3.tif
* └── .thumbnails
* ├── image_1.webp
* ├── image_2.webp
* └── image_3.webp
*/
function getThumbnailFromTree(tree: FileTree): FileTree | undefined {
const thumbnailPath = tree.path.substring(
0,
tree.path.length - tree.name.length
) + '.thumbnails' + sep() + tree.name.split('.')[0] + '.webp';
return state.treeIndex.get(thumbnailPath);
}

const truncateMiddle = (str: string, frontLen: number, backLen: number) => {
if (str.length <= frontLen + backLen) return str;
return str.slice(0, frontLen) + '...' + str.slice(str.length - backLen);
};

const getThumbnailExtensionFromTree = (tree: FileTree) => {
const foundThumbnail = getThumbnailFromTree(tree);
return foundThumbnail ? getFileExtension(foundThumbnail.name) : undefined;
};

const getThumbnailURIFromTree = (tree: FileTree) => {
const foundThumbnail = getThumbnailFromTree(tree);
return foundThumbnail ? convertFileSrc(foundThumbnail.path) : undefined;
};

return (
supportedFileTypes.includes(getFileExtension(fileTree?.path)) ? (
<div key={fileTree.path} className="border-2 border-gray-300 dark:border-gray-600 rounded-sm max-w-[150px] mr-2 mb-2 hover:bg-gray-300 hover:dark:bg-gray-600"
onClick={onClick}>
<img src={convertFileSrc(fileTree.path)} alt={fileTree.name}/>
<i>{truncateMiddle(formatFileNames(fileTree.name), 7, 10)}</i>
</div>
) : getThumbnailExtensionFromTree(fileTree) === 'webp' ? (
<div key={fileTree.path} className="border-2 border-gray-300 dark:border-gray-600 max-w-[150px] mr-2 mb-2 hover:bg-gray-300 hover:dark:bg-gray-600"
onClick={onClick}>
<img src={getThumbnailURIFromTree(fileTree)} alt={fileTree.name}/>
<i>{truncateMiddle(formatFileNames(fileTree.name), 7, 10)}</i>
</div>
) : fileTree.name !== '.thumbnails' && (
<div key={fileTree.path} className="max-w-[150px] mr-2 mb-2 flex flex-col items-center"
onClick={onClick}>
<File size="96" color="gray"/>
<i>{truncateMiddle(fileTree.name, 7, 10)}</i>
</div>
)
);

}

0 comments on commit c8d95ad

Please sign in to comment.