diff --git a/backend/src/file/file.controller.ts b/backend/src/file/file.controller.ts
index 40b90208..d861bb79 100644
--- a/backend/src/file/file.controller.ts
+++ b/backend/src/file/file.controller.ts
@@ -51,7 +51,7 @@ export class FileController {
const zip = this.fileService.getZip(shareId);
res.set({
"Content-Type": "application/zip",
- "Content-Disposition": `attachment ; filename="pingvin-share-${shareId}.zip"`,
+ "Content-Disposition": contentDisposition(`pingvin-share-${shareId}.zip`),
});
return new StreamableFile(zip);
@@ -62,14 +62,21 @@ export class FileController {
async getFile(
@Res({ passthrough: true }) res: Response,
@Param("shareId") shareId: string,
- @Param("fileId") fileId: string
+ @Param("fileId") fileId: string,
+ @Query("download") download = "true"
) {
const file = await this.fileService.get(shareId, fileId);
- res.set({
+
+ const headers = {
"Content-Type": file.metaData.mimeType,
"Content-Length": file.metaData.size,
- "Content-Disposition": contentDisposition(file.metaData.name),
- });
+ };
+
+ if (download === "true") {
+ headers["Content-Disposition"] = contentDisposition(file.metaData.name);
+ }
+
+ res.set(headers);
return new StreamableFile(file.file);
}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 84509316..bf72c167 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -21,6 +21,7 @@
"cookies-next": "^2.1.1",
"file-saver": "^2.0.5",
"jose": "^4.11.2",
+ "mime-types": "^2.1.35",
"moment": "^2.29.4",
"next": "^13.1.2",
"next-cookies": "^2.0.3",
@@ -33,6 +34,7 @@
"yup": "^0.32.11"
},
"devDependencies": {
+ "@types/mime-types": "^2.1.1",
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
@@ -2656,6 +2658,12 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz",
"integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag=="
},
+ "node_modules/@types/mime-types": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
+ "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
+ "dev": true
+ },
"node_modules/@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
@@ -9913,6 +9921,12 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz",
"integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag=="
},
+ "@types/mime-types": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
+ "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
+ "dev": true
+ },
"@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 022e3686..3fb4c1b0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -22,6 +22,7 @@
"cookies-next": "^2.1.1",
"file-saver": "^2.0.5",
"jose": "^4.11.2",
+ "mime-types": "^2.1.35",
"moment": "^2.29.4",
"next": "^13.1.2",
"next-cookies": "^2.0.3",
@@ -34,6 +35,7 @@
"yup": "^0.32.11"
},
"devDependencies": {
+ "@types/mime-types": "^2.1.1",
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
diff --git a/frontend/src/components/core/CenterLoader.tsx b/frontend/src/components/core/CenterLoader.tsx
new file mode 100644
index 00000000..9d99b381
--- /dev/null
+++ b/frontend/src/components/core/CenterLoader.tsx
@@ -0,0 +1,13 @@
+import { Center, Loader, Stack } from "@mantine/core";
+
+const CenterLoader = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default CenterLoader;
diff --git a/frontend/src/components/share/FileList.tsx b/frontend/src/components/share/FileList.tsx
index 5bc3d7a8..1cadbfc0 100644
--- a/frontend/src/components/share/FileList.tsx
+++ b/frontend/src/components/share/FileList.tsx
@@ -1,7 +1,9 @@
-import { ActionIcon, Loader, Skeleton, Table } from "@mantine/core";
-import { TbCircleCheck, TbDownload } from "react-icons/tb";
+import { ActionIcon, Group, Skeleton, Table } from "@mantine/core";
+import mime from "mime-types";
+import Link from "next/link";
+import { TbDownload, TbEye } from "react-icons/tb";
import shareService from "../../services/share.service";
-
+import { FileMetaData } from "../../types/File.type";
import { byteToHumanSizeString } from "../../utils/fileSize.util";
const FileList = ({
@@ -9,7 +11,7 @@ const FileList = ({
shareId,
isLoading,
}: {
- files?: any[];
+ files?: FileMetaData[];
shareId: string;
isLoading: boolean;
}) => {
@@ -28,15 +30,21 @@ const FileList = ({
: files!.map((file) => (
{file.name} |
- {byteToHumanSizeString(file.size)} |
+ {byteToHumanSizeString(parseInt(file.size))} |
- {file.uploadingState ? (
- file.uploadingState != "finished" ? (
-
- ) : (
-
- )
- ) : (
+
+ {shareService.doesFileSupportPreview(file.name) && (
+
+
+
+ )}
{
@@ -45,7 +53,7 @@ const FileList = ({
>
- )}
+
|
))}
diff --git a/frontend/src/pages/account/reverseShares.tsx b/frontend/src/pages/account/reverseShares.tsx
index 2993dd70..7be03197 100644
--- a/frontend/src/pages/account/reverseShares.tsx
+++ b/frontend/src/pages/account/reverseShares.tsx
@@ -4,7 +4,6 @@ import {
Button,
Center,
Group,
- LoadingOverlay,
Stack,
Table,
Text,
@@ -18,6 +17,7 @@ import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { TbInfoCircle, TbLink, TbPlus, TbTrash } from "react-icons/tb";
import showShareLinkModal from "../../components/account/showShareLinkModal";
+import CenterLoader from "../../components/core/CenterLoader";
import Meta from "../../components/Meta";
import showCreateReverseShareModal from "../../components/share/modals/showCreateReverseShareModal";
import useConfig from "../../hooks/config.hook";
@@ -50,7 +50,7 @@ const MyShares = () => {
if (!user) {
router.replace("/");
} else {
- if (!reverseShares) return ;
+ if (!reverseShares) return ;
return (
<>
diff --git a/frontend/src/pages/share/[shareId].tsx b/frontend/src/pages/share/[shareId]/index.tsx
similarity index 83%
rename from frontend/src/pages/share/[shareId].tsx
rename to frontend/src/pages/share/[shareId]/index.tsx
index a4526e88..e1c293fb 100644
--- a/frontend/src/pages/share/[shareId].tsx
+++ b/frontend/src/pages/share/[shareId]/index.tsx
@@ -2,13 +2,13 @@ import { Box, Group, Text, Title } from "@mantine/core";
import { useModals } from "@mantine/modals";
import { GetServerSidePropsContext } from "next";
import { useEffect, useState } from "react";
-import Meta from "../../components/Meta";
-import DownloadAllButton from "../../components/share/DownloadAllButton";
-import FileList from "../../components/share/FileList";
-import showEnterPasswordModal from "../../components/share/showEnterPasswordModal";
-import showErrorModal from "../../components/share/showErrorModal";
-import shareService from "../../services/share.service";
-import { Share as ShareType } from "../../types/share.type";
+import Meta from "../../../components/Meta";
+import DownloadAllButton from "../../../components/share/DownloadAllButton";
+import FileList from "../../../components/share/FileList";
+import showEnterPasswordModal from "../../../components/share/showEnterPasswordModal";
+import showErrorModal from "../../../components/share/showErrorModal";
+import shareService from "../../../services/share.service";
+import { Share as ShareType } from "../../../types/share.type";
export function getServerSideProps(context: GetServerSidePropsContext) {
return {
diff --git a/frontend/src/pages/share/[shareId]/preview/[fileId].tsx b/frontend/src/pages/share/[shareId]/preview/[fileId].tsx
new file mode 100644
index 00000000..e90a0f7d
--- /dev/null
+++ b/frontend/src/pages/share/[shareId]/preview/[fileId].tsx
@@ -0,0 +1,92 @@
+import { Center, Stack, Text, Title } from "@mantine/core";
+import { GetServerSidePropsContext } from "next";
+import { useState } from "react";
+
+export function getServerSideProps(context: GetServerSidePropsContext) {
+ const { shareId, fileId } = context.params!;
+
+ const mimeType = context.query.type as string;
+
+ return {
+ props: { shareId, fileId, mimeType },
+ };
+}
+
+const UnSupportedFile = () => {
+ return (
+
+
+ Preview not supported
+
+ A preview for thise file type is unsupported. Please download the file
+ to view it.
+
+
+
+ );
+};
+
+const FilePreview = ({
+ shareId,
+ fileId,
+ mimeType,
+}: {
+ shareId: string;
+ fileId: string;
+ mimeType: string;
+}) => {
+ const [isNotSupported, setIsNotSupported] = useState(false);
+
+ if (isNotSupported) return ;
+
+ if (mimeType == "application/pdf") {
+ window.location.href = `/api/shares/${shareId}/files/${fileId}?download=false`;
+ return null;
+ } else if (mimeType.startsWith("video/")) {
+ return (
+
+ );
+ } else if (mimeType.startsWith("image/")) {
+ return (
+ // eslint-disable-next-line @next/next/no-img-element
+ {
+ setIsNotSupported(true);
+ }}
+ src={`/api/shares/${shareId}/files/${fileId}?download=false`}
+ alt={`${fileId}_preview`}
+ width="100%"
+ />
+ );
+ } else if (mimeType.startsWith("audio/")) {
+ return (
+
+
+
+
+
+ );
+ } else {
+ return ;
+ }
+};
+
+export default FilePreview;
diff --git a/frontend/src/services/share.service.ts b/frontend/src/services/share.service.ts
index ebe72b32..a7e1dd56 100644
--- a/frontend/src/services/share.service.ts
+++ b/frontend/src/services/share.service.ts
@@ -1,5 +1,7 @@
import { setCookie } from "cookies-next";
+import mime from "mime-types";
import { FileUploadResponse } from "../types/File.type";
+
import {
CreateShare,
MyReverseShare,
@@ -47,7 +49,22 @@ const getShareToken = async (id: string, password?: string) => {
};
const isShareIdAvailable = async (id: string): Promise => {
- return (await api.get(`shares/isShareIdAvailable/${id}`)).data.isAvailable;
+ return (await api.get(`/shares/isShareIdAvailable/${id}`)).data.isAvailable;
+};
+
+const doesFileSupportPreview = (fileName: string) => {
+ const mimeType = mime.contentType(fileName);
+
+ if (!mimeType) return false;
+
+ const supportedMimeTypes = [
+ mimeType.startsWith("video/"),
+ mimeType.startsWith("image/"),
+ mimeType.startsWith("audio/"),
+ mimeType == "application/pdf",
+ ];
+
+ return supportedMimeTypes.some((isSupported) => isSupported);
};
const downloadFile = async (shareId: string, fileId: string) => {
@@ -114,6 +131,7 @@ export default {
get,
remove,
getMetaData,
+ doesFileSupportPreview,
getMyShares,
isShareIdAvailable,
downloadFile,
diff --git a/frontend/src/types/File.type.ts b/frontend/src/types/File.type.ts
index d5ccf635..7c0927af 100644
--- a/frontend/src/types/File.type.ts
+++ b/frontend/src/types/File.type.ts
@@ -1,3 +1,9 @@
export type FileUpload = File & { uploadingProgress: number };
export type FileUploadResponse = { id: string; name: string };
+
+export type FileMetaData = {
+ id: string;
+ name: string;
+ size: string;
+};