From 912e439645244a2dc75bc4511bcc57db486e69cf Mon Sep 17 00:00:00 2001 From: dicedtomato <35403473+diced@users.noreply.github.com> Date: Fri, 3 Mar 2023 20:40:28 -0800 Subject: [PATCH] feat: file size (#308) * feat: baseline support for file sizes * feat: script to add file sizes --- package.json | 3 +- .../20230226051016_file_size/migration.sql | 2 + prisma/schema.prisma | 1 + src/components/File.tsx | 398 ++++++++++++++++++ src/components/icons/HardDriveIcon.tsx | 5 + src/components/icons/index.tsx | 2 + src/pages/api/upload.ts | 1 + src/pages/api/user/files.ts | 2 + src/pages/api/user/paged.ts | 2 + src/pages/api/user/recent.ts | 1 + src/scripts/query-size.ts | 38 ++ tsconfig.json | 6 +- tsup.config.ts | 5 + 13 files changed, 460 insertions(+), 6 deletions(-) create mode 100644 prisma/migrations/20230226051016_file_size/migration.sql create mode 100644 src/components/File.tsx create mode 100644 src/components/icons/HardDriveIcon.tsx create mode 100644 src/scripts/query-size.ts diff --git a/package.json b/package.json index 2a5436f..aa6775e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "scripts:import-dir": "node --enable-source-maps dist/scripts/import-dir", "scripts:list-users": "node --enable-source-maps dist/scripts/list-users", "scripts:set-user": "node --enable-source-maps dist/scripts/set-user", - "scripts:clear-zero-byte": "node --enable-source-maps dist/scripts/clear-zero-byte" + "scripts:clear-zero-byte": "node --enable-source-maps dist/scripts/clear-zero-byte", + "scripts:query-size": "node --enable-source-maps dist/scripts/query-size" }, "dependencies": { "@emotion/react": "^11.10.6", diff --git a/prisma/migrations/20230226051016_file_size/migration.sql b/prisma/migrations/20230226051016_file_size/migration.sql new file mode 100644 index 0000000..b11ec84 --- /dev/null +++ b/prisma/migrations/20230226051016_file_size/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "File" ADD COLUMN "size" INTEGER NOT NULL DEFAULT 0; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1017b3e..ebf9340 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -53,6 +53,7 @@ model File { originalName String? mimetype String @default("image/png") createdAt DateTime @default(now()) + size Int @default(0) expiresAt DateTime? maxViews Int? views Int @default(0) diff --git a/src/components/File.tsx b/src/components/File.tsx new file mode 100644 index 0000000..1e9e06c --- /dev/null +++ b/src/components/File.tsx @@ -0,0 +1,398 @@ +import { + ActionIcon, + Card, + Group, + LoadingOverlay, + Modal, + Select, + SimpleGrid, + Stack, + Text, + Title, + Tooltip, +} from '@mantine/core'; +import { useClipboard } from '@mantine/hooks'; +import { showNotification } from '@mantine/notifications'; +import useFetch from 'hooks/useFetch'; +import { useFileDelete, useFileFavorite } from 'lib/queries/files'; +import { useFolders } from 'lib/queries/folders'; +import { bytesToHuman } from 'lib/utils/bytes'; +import { relativeTime } from 'lib/utils/client'; +import { useState } from 'react'; +import { + CalendarIcon, + ClockIcon, + CopyIcon, + CrossIcon, + DeleteIcon, + DownloadIcon, + ExternalLinkIcon, + EyeIcon, + HardDriveIcon, + FileIcon, + FolderMinusIcon, + FolderPlusIcon, + HashIcon, + ImageIcon, + InfoIcon, + StarIcon, +} from './icons'; +import MutedText from './MutedText'; +import Type from './Type'; + +export function FileMeta({ Icon, title, subtitle, ...other }) { + return other.tooltip ? ( + + + + + {title} + {subtitle} + + + + ) : ( + + + + {title} + {subtitle} + + + ); +} + +export default function File({ + image, + disableMediaPreview, + exifEnabled, + refreshImages, + reducedActions = false, +}) { + const [open, setOpen] = useState(false); + const [overrideRender, setOverrideRender] = useState(false); + const deleteFile = useFileDelete(); + const favoriteFile = useFileFavorite(); + const clipboard = useClipboard(); + + const folders = useFolders(); + + const loading = deleteFile.isLoading || favoriteFile.isLoading; + + const handleDelete = async () => { + deleteFile.mutate(image.id, { + onSuccess: () => { + showNotification({ + title: 'File Deleted', + message: '', + color: 'green', + icon: , + }); + }, + + onError: (res: any) => { + showNotification({ + title: 'Failed to delete file', + message: res.error, + color: 'red', + icon: , + }); + }, + + onSettled: () => { + setOpen(false); + }, + }); + }; + + const handleCopy = () => { + clipboard.copy(`${window.location.protocol}//${window.location.host}${image.url}`); + setOpen(false); + showNotification({ + title: 'Copied to clipboard', + message: '', + icon: , + }); + }; + + const handleFavorite = async () => { + favoriteFile.mutate( + { id: image.id, favorite: !image.favorite }, + { + onSuccess: () => { + showNotification({ + title: 'Image is now ' + (!image.favorite ? 'favorited' : 'unfavorited'), + message: '', + icon: , + }); + }, + + onError: (res: any) => { + showNotification({ + title: 'Failed to favorite file', + message: res.error, + color: 'red', + icon: , + }); + }, + } + ); + }; + + const inFolder = image.folderId; + + const refresh = () => { + refreshImages(); + folders.refetch(); + }; + + const removeFromFolder = async () => { + const res = await useFetch('/api/user/folders/' + image.folderId, 'DELETE', { + file: Number(image.id), + }); + + refresh(); + + if (!res.error) { + showNotification({ + title: 'Removed from folder', + message: res.name, + color: 'green', + icon: , + }); + } else { + showNotification({ + title: 'Failed to remove from folder', + message: res.error, + color: 'red', + icon: , + }); + } + }; + + const addToFolder = async (t) => { + const res = await useFetch('/api/user/folders/' + t, 'POST', { + file: Number(image.id), + }); + + refresh(); + + if (!res.error) { + showNotification({ + title: 'Added to folder', + message: res.name, + color: 'green', + icon: , + }); + } else { + showNotification({ + title: 'Failed to add to folder', + message: res.error, + color: 'red', + icon: , + }); + } + }; + + const createFolder = (t) => { + useFetch('/api/user/folders', 'POST', { + name: t, + add: [Number(image.id)], + }).then((res) => { + refresh(); + + if (!res.error) { + showNotification({ + title: 'Created & added to folder', + message: res.name, + color: 'green', + icon: , + }); + } else { + showNotification({ + title: 'Failed to create folder', + message: res.error, + color: 'red', + icon: , + }); + } + }); + return { value: t, label: t }; + }; + + return ( + <> + setOpen(false)} title={{image.name}} size='xl'> + + + + + + + + + {image.maxViews && ( + + )} + + {image.expiresAt && !reducedActions && ( + + )} + + + + + + + {exifEnabled && !reducedActions && ( + + window.open(`/dashboard/metadata/${image.id}`, '_blank')} + > + + + + )} + {reducedActions ? null : inFolder && !folders.isLoading ? ( + f.id === image.folderId)?.name ?? '' + }"`} + > + + + + + ) : ( + +