Add Photos UI

This commit is contained in:
Bruno Bernardino
2024-04-27 08:12:44 +01:00
parent 3f5422f8eb
commit 635ca90de0
12 changed files with 757 additions and 122 deletions

View File

@@ -1,37 +1,44 @@
interface FilesBreadcrumbProps {
path: string;
isShowingNotes?: boolean;
isShowingPhotos?: boolean;
}
export default function FilesBreadcrumb({ path, isShowingNotes }: FilesBreadcrumbProps) {
const routePath = isShowingNotes ? 'notes' : 'files';
export default function FilesBreadcrumb({ path, isShowingNotes, isShowingPhotos }: FilesBreadcrumbProps) {
let routePath = 'files';
let rootPath = '/';
if (!isShowingNotes && path === '/') {
return (
<h3 class='text-base font-semibold text-white whitespace-nowrap mr-2'>
All files
</h3>
);
if (isShowingNotes) {
routePath = 'notes';
rootPath = '/Notes/';
} else if (isShowingPhotos) {
routePath = 'photos';
rootPath = '/Photos/';
}
if (isShowingNotes && path === '/Notes/') {
const itemPluralLabel = routePath;
if (path === rootPath) {
return (
<h3 class='text-base font-semibold text-white whitespace-nowrap mr-2'>
All notes
All {itemPluralLabel}
</h3>
);
}
const pathParts = path.slice(1, -1).split('/');
if (isShowingNotes) {
pathParts.shift();
}
return (
<h3 class='text-base font-semibold text-white whitespace-nowrap mr-2'>
{isShowingNotes ? <a href={`/notes?path=/Notes/`}>All notes</a> : <a href={`/files?path=/`}>All files</a>}
{!isShowingNotes && !isShowingPhotos ? <a href={`/files?path=/`}>All files</a> : null}
{isShowingNotes ? <a href={`/notes?path=/Notes/`}>All notes</a> : null}
{isShowingPhotos ? <a href={`/photos?path=/Photos/`}>All photos</a> : null}
{pathParts.map((part, index) => {
// Ignore the first directory in special ones
if (index === 0 && (isShowingNotes || isShowingPhotos)) {
return null;
}
if (index === pathParts.length - 1) {
return (
<>

View File

@@ -8,9 +8,10 @@ interface ListFilesProps {
onClickOpenRenameFile?: (parentPath: string, name: string) => void;
onClickOpenMoveDirectory?: (parentPath: string, name: string) => void;
onClickOpenMoveFile?: (parentPath: string, name: string) => void;
onClickDeleteDirectory: (parentPath: string, name: string) => Promise<void>;
onClickDeleteFile: (parentPath: string, name: string) => Promise<void>;
onClickDeleteDirectory?: (parentPath: string, name: string) => Promise<void>;
onClickDeleteFile?: (parentPath: string, name: string) => Promise<void>;
isShowingNotes?: boolean;
isShowingPhotos?: boolean;
}
export default function ListFiles(
@@ -24,6 +25,7 @@ export default function ListFiles(
onClickDeleteDirectory,
onClickDeleteFile,
isShowingNotes,
isShowingPhotos,
}: ListFilesProps,
) {
const dateFormat = new Intl.DateTimeFormat('en-GB', {
@@ -35,9 +37,23 @@ export default function ListFiles(
minute: '2-digit',
});
const routePath = isShowingNotes ? 'notes' : 'files';
const itemSingleLabel = isShowingNotes ? 'note' : 'file';
const itemPluralLabel = routePath;
let routePath = 'files';
let itemSingleLabel = 'file';
let itemPluralLabel = 'files';
if (isShowingNotes) {
routePath = 'notes';
itemSingleLabel = 'note';
itemPluralLabel = 'notes';
} else if (isShowingPhotos) {
routePath = 'photos';
itemSingleLabel = 'photo';
itemPluralLabel = 'photos';
}
if (isShowingPhotos && directories.length === 0) {
return null;
}
return (
<section class='mx-auto max-w-7xl my-8'>
@@ -46,8 +62,10 @@ export default function ListFiles(
<tr class='border-b border-slate-600'>
<th scope='col' class='px-6 py-4 font-medium text-white'>Name</th>
<th scope='col' class='px-6 py-4 font-medium text-white w-56'>Last update</th>
{isShowingNotes ? null : <th scope='col' class='px-6 py-4 font-medium text-white w-32'>Size</th>}
<th scope='col' class='px-6 py-4 font-medium text-white w-20'></th>
{isShowingNotes || isShowingPhotos
? null
: <th scope='col' class='px-6 py-4 font-medium text-white w-32'>Size</th>}
{isShowingPhotos ? null : <th scope='col' class='px-6 py-4 font-medium text-white w-20'></th>}
</tr>
</thead>
<tbody class='divide-y divide-slate-600 border-t border-slate-600'>
@@ -75,59 +93,63 @@ export default function ListFiles(
<td class='px-6 py-4 text-slate-200'>
{dateFormat.format(new Date(directory.updated_at))}
</td>
{isShowingNotes ? null : (
{isShowingNotes || isShowingPhotos ? null : (
<td class='px-6 py-4 text-slate-200'>
-
</td>
)}
<td class='px-6 py-4'>
{(fullPath === TRASH_PATH || typeof onClickOpenRenameDirectory === 'undefined' ||
typeof onClickOpenMoveDirectory === 'undefined')
? null
: (
<section class='flex items-center justify-end w-20'>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenRenameDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/rename.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt='Rename directory'
title='Rename directory'
/>
</span>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenMoveDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/move.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt='Move directory'
title='Move directory'
/>
</span>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100'
onClick={() => onClickDeleteDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/delete.svg'
class='red drop-shadow-md'
width={20}
height={20}
alt='Delete directory'
title='Delete directory'
/>
</span>
</section>
)}
</td>
{isShowingPhotos ? null : (
<td class='px-6 py-4'>
{(fullPath === TRASH_PATH || typeof onClickOpenRenameDirectory === 'undefined' ||
typeof onClickOpenMoveDirectory === 'undefined')
? null
: (
<section class='flex items-center justify-end w-20'>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenRenameDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/rename.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt='Rename directory'
title='Rename directory'
/>
</span>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenMoveDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/move.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt='Move directory'
title='Move directory'
/>
</span>
{typeof onClickDeleteDirectory === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100'
onClick={() => onClickDeleteDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/delete.svg'
class='red drop-shadow-md'
width={20}
height={20}
alt='Delete directory'
title='Delete directory'
/>
</span>
)}
</section>
)}
</td>
)}
</tr>
);
})}
@@ -159,53 +181,57 @@ export default function ListFiles(
{humanFileSize(file.size_in_bytes)}
</td>
)}
<td class='px-6 py-4'>
<section class='flex items-center justify-end w-20'>
{typeof onClickOpenRenameFile === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenRenameFile(file.parent_path, file.file_name)}
>
<img
src='/images/rename.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt={`Rename ${itemSingleLabel}`}
title={`Rename ${itemSingleLabel}`}
/>
</span>
)}
{typeof onClickOpenMoveFile === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenMoveFile(file.parent_path, file.file_name)}
>
<img
src='/images/move.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt={`Move ${itemSingleLabel}`}
title={`Move ${itemSingleLabel}`}
/>
</span>
)}
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100'
onClick={() => onClickDeleteFile(file.parent_path, file.file_name)}
>
<img
src='/images/delete.svg'
class='red drop-shadow-md'
width={20}
height={20}
alt={`Delete ${itemSingleLabel}`}
title={`Delete ${itemSingleLabel}`}
/>
</span>
</section>
</td>
{isShowingPhotos ? null : (
<td class='px-6 py-4'>
<section class='flex items-center justify-end w-20'>
{typeof onClickOpenRenameFile === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenRenameFile(file.parent_path, file.file_name)}
>
<img
src='/images/rename.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt={`Rename ${itemSingleLabel}`}
title={`Rename ${itemSingleLabel}`}
/>
</span>
)}
{typeof onClickOpenMoveFile === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenMoveFile(file.parent_path, file.file_name)}
>
<img
src='/images/move.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt={`Move ${itemSingleLabel}`}
title={`Move ${itemSingleLabel}`}
/>
</span>
)}
{typeof onClickDeleteFile === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100'
onClick={() => onClickDeleteFile(file.parent_path, file.file_name)}
>
<img
src='/images/delete.svg'
class='red drop-shadow-md'
width={20}
height={20}
alt={`Delete ${itemSingleLabel}`}
title={`Delete ${itemSingleLabel}`}
/>
</span>
)}
</section>
</td>
)}
</tr>
))}
{directories.length === 0 && files.length === 0

View File

@@ -0,0 +1,226 @@
import { Directory, DirectoryFile } from '/lib/types.ts';
import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts';
interface ListFilesProps {
directories: Directory[];
files: DirectoryFile[];
onClickOpenRenameDirectory?: (parentPath: string, name: string) => void;
onClickOpenRenameFile?: (parentPath: string, name: string) => void;
onClickOpenMoveDirectory?: (parentPath: string, name: string) => void;
onClickOpenMoveFile?: (parentPath: string, name: string) => void;
onClickDeleteDirectory: (parentPath: string, name: string) => Promise<void>;
onClickDeleteFile: (parentPath: string, name: string) => Promise<void>;
isShowingNotes?: boolean;
}
export default function ListFiles(
{
directories,
files,
onClickOpenRenameDirectory,
onClickOpenRenameFile,
onClickOpenMoveDirectory,
onClickOpenMoveFile,
onClickDeleteDirectory,
onClickDeleteFile,
isShowingNotes,
}: ListFilesProps,
) {
const dateFormat = new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour12: false,
hour: '2-digit',
minute: '2-digit',
});
const routePath = isShowingNotes ? 'notes' : 'files';
const itemSingleLabel = isShowingNotes ? 'note' : 'file';
const itemPluralLabel = routePath;
return (
<section class='mx-auto max-w-7xl my-8'>
<table class='w-full border-collapse bg-gray-900 text-left text-sm text-slate-500 shadow-sm rounded-md'>
<thead>
<tr class='border-b border-slate-600'>
<th scope='col' class='px-6 py-4 font-medium text-white'>Name</th>
<th scope='col' class='px-6 py-4 font-medium text-white w-56'>Last update</th>
{isShowingNotes ? null : <th scope='col' class='px-6 py-4 font-medium text-white w-32'>Size</th>}
<th scope='col' class='px-6 py-4 font-medium text-white w-20'></th>
</tr>
</thead>
<tbody class='divide-y divide-slate-600 border-t border-slate-600'>
{directories.map((directory) => {
const fullPath = `${directory.parent_path}${directory.directory_name}/`;
return (
<tr class='bg-slate-700 hover:bg-slate-600 group'>
<td class='flex gap-3 px-6 py-4'>
<a
href={`/${routePath}?path=${fullPath}`}
class='flex items-center font-normal text-white'
>
<img
src={`/images/${fullPath === TRASH_PATH ? 'trash.svg' : 'directory.svg'}`}
class='white drop-shadow-md mr-2'
width={18}
height={18}
alt='Directory'
title='Directory'
/>
{directory.directory_name}
</a>
</td>
<td class='px-6 py-4 text-slate-200'>
{dateFormat.format(new Date(directory.updated_at))}
</td>
{isShowingNotes ? null : (
<td class='px-6 py-4 text-slate-200'>
-
</td>
)}
<td class='px-6 py-4'>
{(fullPath === TRASH_PATH || typeof onClickOpenRenameDirectory === 'undefined' ||
typeof onClickOpenMoveDirectory === 'undefined')
? null
: (
<section class='flex items-center justify-end w-20'>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenRenameDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/rename.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt='Rename directory'
title='Rename directory'
/>
</span>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenMoveDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/move.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt='Move directory'
title='Move directory'
/>
</span>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100'
onClick={() => onClickDeleteDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/delete.svg'
class='red drop-shadow-md'
width={20}
height={20}
alt='Delete directory'
title='Delete directory'
/>
</span>
</section>
)}
</td>
</tr>
);
})}
{files.map((file) => (
<tr class='bg-slate-700 hover:bg-slate-600 group'>
<td class='flex gap-3 px-6 py-4'>
<a
href={`/${routePath}/open/${file.file_name}?path=${file.parent_path}`}
class='flex items-center font-normal text-white'
target='_blank'
rel='noopener noreferrer'
>
<img
src='/images/file.svg'
class='white drop-shadow-md mr-2'
width={18}
height={18}
alt='File'
title='File'
/>
{file.file_name}
</a>
</td>
<td class='px-6 py-4 text-slate-200'>
{dateFormat.format(new Date(file.updated_at))}
</td>
{isShowingNotes ? null : (
<td class='px-6 py-4 text-slate-200'>
{humanFileSize(file.size_in_bytes)}
</td>
)}
<td class='px-6 py-4'>
<section class='flex items-center justify-end w-20'>
{typeof onClickOpenRenameFile === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenRenameFile(file.parent_path, file.file_name)}
>
<img
src='/images/rename.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt={`Rename ${itemSingleLabel}`}
title={`Rename ${itemSingleLabel}`}
/>
</span>
)}
{typeof onClickOpenMoveFile === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenMoveFile(file.parent_path, file.file_name)}
>
<img
src='/images/move.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt={`Move ${itemSingleLabel}`}
title={`Move ${itemSingleLabel}`}
/>
</span>
)}
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100'
onClick={() => onClickDeleteFile(file.parent_path, file.file_name)}
>
<img
src='/images/delete.svg'
class='red drop-shadow-md'
width={20}
height={20}
alt={`Delete ${itemSingleLabel}`}
title={`Delete ${itemSingleLabel}`}
/>
</span>
</section>
</td>
</tr>
))}
{directories.length === 0 && files.length === 0
? (
<tr>
<td class='flex gap-3 px-6 py-4 font-normal' colspan={4}>
<div class='text-md'>
<div class='font-medium text-slate-400'>No {itemPluralLabel} to show</div>
</div>
</td>
</tr>
)
: null}
</tbody>
</table>
</section>
);
}