diff --git a/Dockerfile b/Dockerfile index 8141c603..ae4a27a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Use the Prisma binaries image as the first stage -FROM ghcr.io/diced/prisma-binaries:4.8.x as prisma +FROM ghcr.io/diced/prisma-binaries:4.10.x as prisma # Use Alpine Linux as the second stage FROM node:18-alpine3.16 as base diff --git a/package.json b/package.json index 81632a74..2a5436f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zipline", - "version": "3.7.0-rc3", + "version": "3.7.0-rc4", "license": "MIT", "scripts": { "dev": "npm-run-all build:server dev:run", @@ -14,81 +14,80 @@ "migrate:dev": "prisma migrate dev --create-only", "start": "node dist", "lint": "next lint", - "docker:run": "docker-compose up -d", + "docker:up": "docker-compose up", "docker:down": "docker-compose down", "docker:build-dev": "docker-compose --file docker-compose.dev.yml up --build", - "docker:run-dev": "docker-compose --file docker-compose.dev.yml up", + "docker:up-dev": "docker-compose --file docker-compose.dev.yml up", "docker:down-dev": "docker-compose --file docker-compose.dev.yml down", - "scripts:read-config": "node dist/scripts/read-config", - "scripts:import-dir": "node dist/scripts/import-dir", - "scripts:list-users": "node dist/scripts/list-users", - "scripts:set-user": "node dist/scripts/set-user" + "scripts:read-config": "node --enable-source-maps dist/scripts/read-config", + "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" }, "dependencies": { - "@dicedtomato/mantine-data-grid": "0.0.23", - "@emotion/react": "^11.10.5", + "@emotion/react": "^11.10.6", "@emotion/server": "^11.10.0", - "@mantine/core": "^5.9.2", - "@mantine/dropzone": "^5.9.2", - "@mantine/form": "^5.9.2", - "@mantine/hooks": "^5.9.2", - "@mantine/modals": "^5.9.2", - "@mantine/next": "^5.9.2", - "@mantine/notifications": "^5.9.2", - "@mantine/nprogress": "^5.9.2", - "@mantine/prism": "^5.9.2", - "@prisma/client": "^4.8.1", - "@prisma/internals": "^4.8.1", - "@prisma/migrate": "^4.8.1", - "@sapphire/shapeshift": "^3.7.1", - "@tanstack/react-query": "^4.19.1", - "argon2": "^0.30.2", - "colorette": "^2.0.19", + "@mantine/core": "^5.10.5", + "@mantine/dropzone": "^5.10.5", + "@mantine/form": "^5.10.5", + "@mantine/hooks": "^5.10.5", + "@mantine/modals": "^5.10.5", + "@mantine/next": "^5.10.5", + "@mantine/notifications": "^5.10.5", + "@mantine/prism": "^5.10.5", + "@prisma/client": "^4.10.1", + "@prisma/internals": "^4.10.1", + "@prisma/migrate": "^4.10.1", + "@sapphire/shapeshift": "^3.8.1", + "@tanstack/react-query": "^4.24.10", + "argon2": "^0.30.3", "cookie": "^0.5.0", "dayjs": "^1.11.7", "dotenv": "^16.0.3", - "dotenv-expand": "^9.0.0", - "exiftool-vendored": "^18.6.0", - "fastify": "^4.10.2", - "fastify-plugin": "^4.4.0", + "dotenv-expand": "^10.0.0", + "exiftool-vendored": "^21.2.0", + "fastify": "^4.13.0", + "fastify-plugin": "^4.5.0", "fflate": "^0.7.4", - "find-my-way": "^7.3.1", + "find-my-way": "^7.5.0", "katex": "^0.16.4", + "mantine-datatable": "^1.8.6", "minio": "^7.0.32", "ms": "canary", "multer": "^1.4.5-lts.1", - "next": "^13.0.6", + "next": "^13.2.1", "otplib": "^12.0.1", - "prisma": "^4.8.1", + "prisma": "^4.10.1", "prismjs": "^1.29.0", "qrcode": "^1.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-feather": "^2.0.10", - "react-markdown": "^8.0.4", - "recharts": "^2.3.2", + "react-markdown": "^8.0.5", + "recharts": "^2.4.3", "recoil": "^0.7.6", "remark-gfm": "^3.0.1", - "sharp": "^0.31.2" + "sharp": "^0.31.3" }, "devDependencies": { "@types/cookie": "^0.5.1", - "@types/katex": "^0.14.0", - "@types/minio": "^7.0.15", + "@types/katex": "^0.16.0", + "@types/minio": "^7.0.16", "@types/multer": "^1.4.7", - "@types/node": "^18.11.12", + "@types/node": "^18.14.2", "@types/qrcode": "^1.5.0", - "@types/react": "^18.0.26", - "@types/sharp": "^0.31.0", + "@types/react": "^18.0.28", + "@types/sharp": "^0.31.1", "cross-env": "^7.0.3", - "eslint": "^8.29.0", - "eslint-config-next": "^13.0.6", - "eslint-config-prettier": "^8.5.0", + "eslint": "^8.35.0", + "eslint-config-next": "^13.2.1", + "eslint-config-prettier": "^8.6.0", "eslint-plugin-prettier": "^4.2.1", "npm-run-all": "^4.1.5", - "prettier": "^2.8.1", - "tsup": "^6.5.0", - "typescript": "^4.9.4" + "prettier": "^2.8.4", + "tsup": "^6.6.3", + "typescript": "^4.9.5" }, "repository": { "type": "git", diff --git a/src/components/File/FileModal.tsx b/src/components/File/FileModal.tsx new file mode 100644 index 00000000..31dd8e45 --- /dev/null +++ b/src/components/File/FileModal.tsx @@ -0,0 +1,345 @@ +import { + ActionIcon, + Group, + LoadingOverlay, + Modal, + Select, + SimpleGrid, + Stack, + 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 { relativeTime } from 'lib/utils/client'; +import { useState } from 'react'; +import { FileMeta } from '.'; +import { + CalendarIcon, + ClockIcon, + CopyIcon, + CrossIcon, + DeleteIcon, + DownloadIcon, + ExternalLinkIcon, + EyeIcon, + FileIcon, + FolderMinusIcon, + FolderPlusIcon, + HashIcon, + ImageIcon, + InfoIcon, + StarIcon, +} from '../icons'; +import Type from '../Type'; + +export default function FileModal({ + open, + setOpen, + file, + loading, + refresh, + reducedActions = false, + exifEnabled, +}: { + open: boolean; + setOpen: (open: boolean) => void; + file: any; + loading: boolean; + refresh: () => void; + reducedActions?: boolean; + exifEnabled?: boolean; +}) { + const deleteFile = useFileDelete(); + const favoriteFile = useFileFavorite(); + const folders = useFolders(); + + const [overrideRender, setOverrideRender] = useState(false); + const clipboard = useClipboard(); + + const handleDelete = async () => { + deleteFile.mutate(file.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}${file.url}`); + setOpen(false); + if (!navigator.clipboard) + showNotification({ + title: 'Unable to copy to clipboard', + message: 'Zipline is unable to copy to clipboard due to security reasons.', + color: 'red', + }); + else + showNotification({ + title: 'Copied to clipboard', + message: '', + icon: , + }); + }; + + const handleFavorite = async () => { + favoriteFile.mutate( + { id: file.id, favorite: !file.favorite }, + { + onSuccess: () => { + showNotification({ + title: 'The file is now ' + (!file.favorite ? 'favorited' : 'unfavorited'), + message: '', + icon: , + }); + }, + + onError: (res: any) => { + showNotification({ + title: 'Failed to favorite file', + message: res.error, + color: 'red', + icon: , + }); + }, + } + ); + }; + + const inFolder = file.folderId; + + const removeFromFolder = async () => { + const res = await useFetch('/api/user/folders/' + file.folderId, 'DELETE', { + file: Number(file.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(file.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(file.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={{file.name}} size='xl'> + + + + + + + + {file.maxViews && ( + + )} + + {file.expiresAt && !reducedActions && ( + + )} + + + + + + + {exifEnabled && !reducedActions && ( + + window.open(`/dashboard/metadata/${file.id}`, '_blank')} + > + + + + )} + {reducedActions ? null : inFolder && !folders.isLoading ? ( + f.id === file.folderId)?.name ?? ''}"`} + > + + + + + ) : ( + + e.target.select()} type='text' value={token} /> + + + ), + color: 'red', + }); + else + showNotification({ + title: 'Token Copied', + message: 'Your token has been copied to your clipboard.', + color: 'green', + icon: , + }); modals.closeAll(); }, @@ -323,7 +341,16 @@ export default function Layout({ children, props }) { {title} - +