-
{title}
-
- onViewChange("markdown")}
- className='w-full rounded-r-none mobile:w-fit'
- >
- Markdown
-
- onViewChange("raw")}
- className='w-full rounded-l-none mobile:w-fit'
- >
- Raw
-
-
+
+ {/*
*/}
+
+ {title}
+
+ {/* */}
+ {["markdown", "code", "text"].includes(fileType) && (
+
+ onViewChange("markdown")}
+ className='w-full rounded-r-none mobile:w-fit'
+ >
+ Markdown
+
+ onViewChange("raw")}
+ className='w-full rounded-l-none mobile:w-fit'
+ >
+ Raw
+
+
+ )}
diff --git a/src/app/api/stream/[encryptedId]/helper.ts b/src/app/api/stream/[encryptedId]/helper.ts
new file mode 100644
index 0000000..d12e947
--- /dev/null
+++ b/src/app/api/stream/[encryptedId]/helper.ts
@@ -0,0 +1,24 @@
+import { Readable } from "stream";
+
+async function* nodeStreamToIterator(stream: any) {
+ for await (const chunk of stream) {
+ yield chunk;
+ }
+}
+
+function iteratorToStream(iterator: AsyncGenerator
) {
+ return new ReadableStream({
+ async pull(controller) {
+ const { done, value } = await iterator.next();
+ if (done) {
+ controller.close();
+ } else {
+ controller.enqueue(value);
+ }
+ },
+ });
+}
+export function streamFile(body: Readable): ReadableStream {
+ const data: ReadableStream = iteratorToStream(nodeStreamToIterator(body));
+ return data;
+}
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
new file mode 100644
index 0000000..f720c12
--- /dev/null
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -0,0 +1,101 @@
+import { NextRequest, NextResponse } from "next/server";
+
+import { CheckDownloadToken } from "~/app/actions";
+
+import { decryptData } from "~/utils/encryptionHelper/hash";
+import { gdriveNoCache as gdrive } from "~/utils/gdriveInstance";
+
+import config from "~/config/gIndex.config";
+
+import { streamFile } from "./helper";
+
+export const dynamic = "force-dynamic";
+
+export async function GET(
+ request: NextRequest,
+ {
+ params: { encryptedId },
+ }: {
+ params: {
+ encryptedId: string;
+ };
+ },
+) {
+ try {
+ const sp = new URL(request.nextUrl).searchParams;
+ const token = sp.get("token");
+ if (!token) throw new Error("Token not found");
+
+ // Only allow if referrer is from the same site
+ if (!request.headers.get("Referer")?.includes(config.basePath)) {
+ throw new Error("Invalid request");
+ }
+
+ const tokenValidity = await CheckDownloadToken(token);
+ if (!tokenValidity.success) throw new Error(tokenValidity.message);
+
+ const decryptedId = await decryptData(encryptedId);
+ const _fileMeta = gdrive.files.get({
+ fileId: decryptedId,
+ fields: "id, name, mimeType, size, fileExtension, webContentLink",
+ supportsAllDrives: config.apiConfig.isTeamDrive,
+ });
+ const _fileContent = gdrive.files.get(
+ {
+ fileId: decryptedId,
+ alt: "media",
+ supportsAllDrives: config.apiConfig.isTeamDrive,
+ },
+ {
+ responseType: "stream",
+ },
+ );
+
+ const [fileMeta, fileContent] = await Promise.all([
+ _fileMeta,
+ _fileContent,
+ ]);
+
+ const fileSize = Number(fileMeta.data.size || 0);
+ if (!fileMeta.data.webContentLink)
+ throw new Error("No download link found");
+
+ const stream: ReadableStream = streamFile(fileContent.data);
+ const ranges = request.headers.get("Range");
+ if (ranges) {
+ const [start, end] = ranges.replace(/bytes=/, "").split("-");
+ const startByte = parseInt(start, 10);
+ const endByte = end ? parseInt(end, 10) : fileSize - 1;
+ return new NextResponse(stream, {
+ status: 206,
+ headers: {
+ "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
+ "Content-Disposition": `attachment; filename="${encodeURIComponent(
+ fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
+ )}"`,
+ "Content-Length": (endByte - startByte + 1).toString(),
+ "Accept-Ranges": "bytes",
+ "Content-Range": `bytes ${startByte}-${endByte}/${fileSize}`,
+ },
+ });
+ }
+
+ return new NextResponse(stream, {
+ status: 200,
+ headers: {
+ "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
+ "Content-Disposition": `attachment; filename="${encodeURIComponent(
+ fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
+ )}"`,
+ "Content-Length": fileSize.toString(),
+ "Accept-Ranges": "bytes",
+ },
+ });
+ } catch (error) {
+ const e = error as Error;
+ console.error(e.message);
+ return new NextResponse(e.message, {
+ status: 500,
+ });
+ }
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 79c57fa..8050acd 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,5 +1,6 @@
import { Metadata } from "next";
import { JetBrains_Mono, Outfit, Source_Sans_3 } from "next/font/google";
+import "plyr-react/plyr.css";
import { cn } from "~/utils";
import { formatFooter } from "~/utils/footerFormatter";
diff --git a/yarn.lock b/yarn.lock
index de412a0..eeb60d9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2859,6 +2859,13 @@ __metadata:
languageName: node
linkType: hard
+"core-js@npm:^3.26.1":
+ version: 3.37.0
+ resolution: "core-js@npm:3.37.0"
+ checksum: 10c0/7e00331f346318ca3f595c08ce9e74ddae744715aef137486c1399163afd79792fb94c3161280863adfdc3e30f8026912d56bd3036f93cacfc689d33e185f2ee
+ languageName: node
+ linkType: hard
+
"core-util-is@npm:~1.0.0":
version: 1.0.3
resolution: "core-util-is@npm:1.0.3"
@@ -2918,6 +2925,13 @@ __metadata:
languageName: node
linkType: hard
+"custom-event-polyfill@npm:^1.0.7":
+ version: 1.0.7
+ resolution: "custom-event-polyfill@npm:1.0.7"
+ checksum: 10c0/b73c90d646d78f4acdff5453fa0f165f6d5506e32d074ca57d27f2bb7d9412356dab8cec7c00a0956b069e4987a8546a2c2c4b866d155e9fc4c27d223a225b78
+ languageName: node
+ linkType: hard
+
"damerau-levenshtein@npm:^1.0.8":
version: 1.0.8
resolution: "damerau-levenshtein@npm:1.0.8"
@@ -5181,6 +5195,13 @@ __metadata:
languageName: node
linkType: hard
+"loadjs@npm:^4.2.0":
+ version: 4.3.0
+ resolution: "loadjs@npm:4.3.0"
+ checksum: 10c0/8884520a7c5f3b0f6e4d3bc01d200c73b9c468986bea26acb54939d4a3f5da08f40f712812fadc1ff6030fca936e8c9eeb842aaafd287e32ca0ce6ae9f10e759
+ languageName: node
+ linkType: hard
+
"locate-path@npm:^6.0.0":
version: 6.0.0
resolution: "locate-path@npm:6.0.0"
@@ -6229,6 +6250,7 @@ __metadata:
next: "npm:^14.1.4"
next-themes: "npm:^0.3.0"
nextjs-toploader: "npm:^1.6.11"
+ plyr-react: "npm:^5.3.0"
postcss: "npm:^8.4.38"
prettier: "npm:3.0.0"
prettier-plugin-tailwindcss: "npm:0.5.12"
@@ -6750,6 +6772,37 @@ __metadata:
languageName: node
linkType: hard
+"plyr-react@npm:^5.3.0":
+ version: 5.3.0
+ resolution: "plyr-react@npm:5.3.0"
+ dependencies:
+ plyr: "npm:^3.7.7"
+ react-aptor: "npm:^2.0.0"
+ peerDependencies:
+ plyr: ^3.7.7
+ react: ">=16.8"
+ peerDependenciesMeta:
+ plyr:
+ optional: false
+ react:
+ optional: true
+ checksum: 10c0/b338c5f07277c124663aa4f820dffb0700a67ac6eab0015b77985bb70c308da96bdffff6d103033544de6516cade63027f4c077871a0fa0feba996dfd3b6f2c0
+ languageName: node
+ linkType: hard
+
+"plyr@npm:^3.7.7":
+ version: 3.7.8
+ resolution: "plyr@npm:3.7.8"
+ dependencies:
+ core-js: "npm:^3.26.1"
+ custom-event-polyfill: "npm:^1.0.7"
+ loadjs: "npm:^4.2.0"
+ rangetouch: "npm:^2.0.1"
+ url-polyfill: "npm:^1.1.12"
+ checksum: 10c0/75c3e070f7829f76409e0d34784bf8070b827ad99c4713a36338af7ce8dff7cf38998403f34a4a0b4d6e99efd1856ee056443c7e838db1dbb98ed38828110a97
+ languageName: node
+ linkType: hard
+
"possible-typed-array-names@npm:^1.0.0":
version: 1.0.0
resolution: "possible-typed-array-names@npm:1.0.0"
@@ -7001,6 +7054,25 @@ __metadata:
languageName: node
linkType: hard
+"rangetouch@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "rangetouch@npm:2.0.1"
+ checksum: 10c0/5f7947d1c5e95f50630ed1e0cbaeb4c32a6be37b66d864a68f7e70a4e86f782eb869df9fa2357b981b1301d2158eff50002a199b0fbbbf6e1746bc9d213914d9
+ languageName: node
+ linkType: hard
+
+"react-aptor@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "react-aptor@npm:2.0.0"
+ peerDependencies:
+ react: ">=16.8"
+ peerDependenciesMeta:
+ react:
+ optional: true
+ checksum: 10c0/f2f00b494a2cb93b0ee73949d2031c2f5a8233d169c3d16792e7c6a5177a75310155e85282209a6f74883b3564d1f3a46d5d4d03ee1c16224fbdb5d5665504f8
+ languageName: node
+ linkType: hard
+
"react-colorful@npm:^5.6.1":
version: 5.6.1
resolution: "react-colorful@npm:5.6.1"
@@ -8554,6 +8626,13 @@ __metadata:
languageName: node
linkType: hard
+"url-polyfill@npm:^1.1.12":
+ version: 1.1.12
+ resolution: "url-polyfill@npm:1.1.12"
+ checksum: 10c0/69633c42e3182271d01d2f2f4acd889a9f54b88fd7b2f45fd84fed19eca7cfa98fb6bfbd43d9cd9fad222a11a2dffb562b8435cdaa67fca5e25e3da638741679
+ languageName: node
+ linkType: hard
+
"url-template@npm:^2.0.8":
version: 2.0.8
resolution: "url-template@npm:2.0.8"
From eb0804826bea61b0f476cb242860053789717db7 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Sun, 5 May 2024 16:46:39 +0700
Subject: [PATCH 02/27] Remove error message when password is not set
---
src/app/actions.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/app/actions.ts b/src/app/actions.ts
index 97a075b..747cf34 100644
--- a/src/app/actions.ts
+++ b/src/app/actions.ts
@@ -268,7 +268,8 @@ export async function CheckPassword(
const currentFolder = paths[folderIndex];
if (!cookiesValue[currentFolder.id])
throw {
- message: `Password for '${currentFolder.path}' is not set, please enter the password`,
+ // message: `Password for '${currentFolder.path}' is not set, please enter the password`,
+ message: null,
path: currentFolder.id,
};
From b08d6cedde63d1082467f5b494b015335a547473 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Sun, 5 May 2024 16:50:46 +0700
Subject: [PATCH 03/27] Fix missing props for `RichHeader`
---
src/app/@readme.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/@readme.tsx b/src/app/@readme.tsx
index 31d4fb3..8990720 100644
--- a/src/app/@readme.tsx
+++ b/src/app/@readme.tsx
@@ -24,6 +24,7 @@ export default function Readme({ content, title }: Props) {
title={title}
view={view}
onViewChange={setView}
+ fileType={"markdown"}
/>
Date: Sun, 5 May 2024 23:47:17 +0700
Subject: [PATCH 04/27] Test new stream logic
---
src/app/@preview.video.tsx | 7 +-
src/app/api/stream/[encryptedId]/route.ts | 150 ++++++++++++++++------
2 files changed, 110 insertions(+), 47 deletions(-)
diff --git a/src/app/@preview.video.tsx b/src/app/@preview.video.tsx
index df6e10c..73f15aa 100644
--- a/src/app/@preview.video.tsx
+++ b/src/app/@preview.video.tsx
@@ -8,8 +8,6 @@ import { cn } from "~/utils";
import Icon from "~/components/Icon";
-import { decryptData } from "~/utils/encryptionHelper/hash";
-
import { CreateDownloadToken } from "./actions";
const Plyr = dynamic(() => import("plyr-react"), {
@@ -32,10 +30,6 @@ export default function PreviewVideo({ file }: Props) {
return;
}
const token = await CreateDownloadToken();
- const id = await decryptData(file.encryptedId);
- // setVideoSrc(
- // `https://drive.usercontent.google.com/download?id=${id}&export=download&authuser=0`,
- // );
setVideoSrc(`/api/stream/${file.encryptedId}?token=${token}`);
} catch (error) {
const e = error as Error;
@@ -85,6 +79,7 @@ export default function PreviewVideo({ file }: Props) {
},
],
}}
+ // crossOrigin='anonymous'
options={{
toggleInvert: true,
settings: ["quality", "speed"],
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
index f720c12..71448c0 100644
--- a/src/app/api/stream/[encryptedId]/route.ts
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -7,8 +7,6 @@ import { gdriveNoCache as gdrive } from "~/utils/gdriveInstance";
import config from "~/config/gIndex.config";
-import { streamFile } from "./helper";
-
export const dynamic = "force-dynamic";
export async function GET(
@@ -35,12 +33,33 @@ export async function GET(
if (!tokenValidity.success) throw new Error(tokenValidity.message);
const decryptedId = await decryptData(encryptedId);
- const _fileMeta = gdrive.files.get({
+ const fileMeta = await gdrive.files.get({
fileId: decryptedId,
fields: "id, name, mimeType, size, fileExtension, webContentLink",
supportsAllDrives: config.apiConfig.isTeamDrive,
});
- const _fileContent = gdrive.files.get(
+
+ const fileSize = Number(fileMeta.data.size || 0);
+ if (!fileMeta.data.webContentLink)
+ throw new Error("No download link found");
+
+ const ranges = request.headers.get("Range") || "bytes=0-";
+ let rangeStart = 0;
+ let rangeEnd = fileSize - 1;
+ const rangeRegex = /bytes=(\d+)-(\d+)?/;
+ const rangeSize = ranges.match(rangeRegex);
+ if (rangeSize) {
+ rangeStart = parseInt(rangeSize[1], 10);
+ rangeEnd = rangeSize ? parseInt(rangeSize[2], 10) : fileSize - 1;
+ }
+
+ const contentRange = `bytes=${rangeStart}-${Math.min(
+ rangeEnd,
+ fileSize - 1,
+ )}/${fileSize}`;
+ const contentLength = rangeEnd ? rangeEnd - rangeStart + 1 : fileSize;
+
+ const fileContent = await gdrive.files.get(
{
fileId: decryptedId,
alt: "media",
@@ -48,49 +67,98 @@ export async function GET(
},
{
responseType: "stream",
+ headers: {
+ "Accept-Ranges": "bytes",
+ "Range": ranges,
+ },
},
);
- const [fileMeta, fileContent] = await Promise.all([
- _fileMeta,
- _fileContent,
- ]);
+ const stream = fileContent.data as NodeJS.ReadableStream;
+ const fileRange = fileContent.headers["content-range"];
+ const fileLength = fileContent.headers["content-length"];
- const fileSize = Number(fileMeta.data.size || 0);
- if (!fileMeta.data.webContentLink)
- throw new Error("No download link found");
-
- const stream: ReadableStream = streamFile(fileContent.data);
- const ranges = request.headers.get("Range");
- if (ranges) {
- const [start, end] = ranges.replace(/bytes=/, "").split("-");
- const startByte = parseInt(start, 10);
- const endByte = end ? parseInt(end, 10) : fileSize - 1;
- return new NextResponse(stream, {
- status: 206,
- headers: {
- "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
- "Content-Disposition": `attachment; filename="${encodeURIComponent(
- fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
- )}"`,
- "Content-Length": (endByte - startByte + 1).toString(),
- "Accept-Ranges": "bytes",
- "Content-Range": `bytes ${startByte}-${endByte}/${fileSize}`,
- },
- });
- }
-
- return new NextResponse(stream, {
- status: 200,
- headers: {
- "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
- "Content-Disposition": `attachment; filename="${encodeURIComponent(
- fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
- )}"`,
- "Content-Length": fileSize.toString(),
- "Accept-Ranges": "bytes",
+ const readable = new ReadableStream({
+ start(controller) {
+ stream.on("data", (chunk) => {
+ controller.enqueue(chunk);
+ });
+ stream.on("end", () => {
+ controller.close();
+ });
+ stream.on("error", (error) => {
+ controller.error(error);
+ });
},
});
+
+ return new NextResponse(readable, {
+ status: 206,
+ headers: {
+ "Content-Range": fileRange || contentRange,
+ "Content-Length": fileLength || contentLength.toString(),
+ "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
+ "Accept-Ranges": "bytes",
+ // 'Content-Disposition': `attachment; filename="${encodeURIComponent(fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`)}"`,
+ },
+ });
+
+ // async function* readStream() {
+ // for await (const chunk of fileContent.data as any) {
+ // yield chunk;
+ // }
+ // }
+ // function iteratorToStream(iterator: AsyncGenerator) {
+ // return new ReadableStream({
+ // async pull(controller) {
+ // const { done, value } = await iterator.next();
+ // if (done) {
+ // controller.close();
+ // } else {
+ // controller.enqueue(value);
+ // }
+ // },
+ // });
+ // }
+ // const stream: ReadableStream = iteratorToStream(readStream());
+
+ // const res = new Response(stream, {
+ // status: 206,
+ // headers: {
+ // "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
+ // "Content-Disposition": `attachment; filename="${encodeURIComponent(
+ // fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
+ // )}"`,
+ // "Content-Length": contentLength.toString(),
+ // "Content-Range": ranges ? contentRange : "",
+ // },
+ // });
+ // NextResponse.next(res);
+ // new NextResponse(stream, {
+ // status: request.headers.get("Range") ? 206 : 200,
+ // headers: {
+ // "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
+ // "Content-Disposition": `attachment; filename="${encodeURIComponent(
+ // fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
+ // )}"`,
+ // "Content-Length": fileSize.toString(),
+ // "Accept-Ranges": "bytes",
+ // "Range": ranges ? `bytes ${rangeStart}-${rangeEnd}/${fileSize}` : "",
+ // },
+ // });
+
+ // return new NextResponse(stream, {
+ // status: 206,
+ // headers: {
+ // "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
+ // "Content-Disposition": `attachment; filename="${encodeURIComponent(
+ // fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
+ // )}"`,
+ // "Content-Length": fileSize.toString(),
+ // "Accept-Ranges": "bytes",
+ // "Range": ranges ? `bytes ${rangeStart}-${rangeEnd}/${fileSize}` : "",
+ // },
+ // });
} catch (error) {
const e = error as Error;
console.error(e.message);
From 04439d74efcfe31b1899679a9e1140058ce14a90 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Sun, 5 May 2024 23:47:55 +0700
Subject: [PATCH 05/27] Use `/thumbnail` endpoint and add long cache
---
src/app/api/thumb/[encryptedId]/route.ts | 92 +++++++-----------------
1 file changed, 27 insertions(+), 65 deletions(-)
diff --git a/src/app/api/thumb/[encryptedId]/route.ts b/src/app/api/thumb/[encryptedId]/route.ts
index ed0cd05..39eeccd 100644
--- a/src/app/api/thumb/[encryptedId]/route.ts
+++ b/src/app/api/thumb/[encryptedId]/route.ts
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
+import { z } from "zod";
import { decryptData } from "~/utils/encryptionHelper/hash";
import gdrive from "~/utils/gdriveInstance";
@@ -16,6 +17,14 @@ export async function GET(
{ params: { encryptedId } }: Props,
) {
try {
+ const searchParams = new URL(request.nextUrl).searchParams;
+ const size = searchParams.get("size") || "512";
+
+ const validSize = z.coerce.number().safeParse(size);
+ if (!validSize.success) {
+ throw new Error("Invalid size");
+ }
+
const defaultImage = NextResponse.redirect(
new URL("/og.png", config.basePath),
{
@@ -23,86 +32,39 @@ export async function GET(
},
);
const decryptedId = await decryptData(encryptedId);
- const _fileMeta = gdrive.files.get({
+
+ const fileMeta = await gdrive.files.get({
fileId: decryptedId,
fields:
"id, name, mimeType, fileExtension, webContentLink, thumbnailLink",
supportsAllDrives: config.apiConfig.isTeamDrive,
});
- const _fileContent = gdrive.files.get(
- {
- fileId: decryptedId,
- alt: "media",
- supportsAllDrives: config.apiConfig.isTeamDrive,
- },
- {
- responseType: "stream",
- },
- );
- const [fileMeta, fileContent] = await Promise.all([
- _fileMeta,
- _fileContent,
- ]);
- const fileSize = Number(fileMeta.data.size || 0);
-
- if (!fileMeta.data.webContentLink) return defaultImage;
- if (!fileMeta.data.thumbnailLink) return defaultImage;
if (
!fileMeta.data.mimeType?.startsWith("image") &&
!fileMeta.data.mimeType?.startsWith("video")
- )
- return defaultImage;
-
- // If svg, return actual image since there is no thumbnail for svg
- if (
- fileMeta.data.mimeType?.includes("svg") &&
- fileSize <= config.apiConfig.maxFileSize
) {
- const fileBuffer = await new Promise((resolve, reject) => {
- const chunks: Buffer[] = [];
- fileContent.data.on("data", (chunk) => {
- chunks.push(chunk);
- });
- fileContent.data.on("end", () => {
- resolve(Buffer.concat(chunks));
- });
- fileContent.data.on("error", (err) => {
- reject(err);
- });
- });
-
- return new NextResponse(fileBuffer, {
- headers: {
- "Cache-Control": "public, max-age=31536000, immutable",
- "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
- "Content-Length": fileBuffer.length.toString(),
- "Content-Disposition": `attachment; filename="${encodeURIComponent(
- fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
- )}"`,
- },
- });
+ return defaultImage;
}
- if (config.apiConfig.proxyThumbnail) {
- const downloadThumb = await fetch(fileMeta.data.thumbnailLink, {
- cache: "force-cache",
- });
- const buffer = await downloadThumb.arrayBuffer();
+ const url = `https://drive.google.com/thumbnail?id=${decryptedId}&sz=w${size}`;
- return new NextResponse(buffer, {
- headers: {
- "Cache-Control": "public, max-age=31536000, immutable",
- "Content-Type": fileMeta.data.mimeType || "application/octet-stream",
- "Content-Length": buffer.byteLength.toString(),
- "Content-Disposition": `attachment; filename="${encodeURIComponent(
- fileMeta.data.name || `Untitled.${fileMeta.data.fileExtension}`,
- )}"`,
- },
- });
+ if (!config.apiConfig.proxyThumbnail) {
+ return NextResponse.redirect(url);
}
- return NextResponse.redirect(fileMeta.data.thumbnailLink);
+ const downloadThumb = await fetch(url, {
+ cache: "force-cache",
+ });
+ const buffer = await downloadThumb.arrayBuffer();
+
+ return new NextResponse(buffer, {
+ headers: {
+ "Cache-Control": "public, max-age=31536000, immutable",
+ "Content-Type": "image/jpeg",
+ "Content-Length": buffer.byteLength.toString(),
+ },
+ });
} catch (error) {
const e = error as Error;
console.error(e.message);
From b4383c73364745652f25fc3bf96c216a143f81d9 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Sun, 5 May 2024 23:58:48 +0700
Subject: [PATCH 06/27] disable ref header
---
src/app/api/stream/[encryptedId]/route.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
index 71448c0..921f894 100644
--- a/src/app/api/stream/[encryptedId]/route.ts
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -25,14 +25,15 @@ export async function GET(
if (!token) throw new Error("Token not found");
// Only allow if referrer is from the same site
- if (!request.headers.get("Referer")?.includes(config.basePath)) {
- throw new Error("Invalid request");
- }
+ // if (!request.headers.get("Referer")?.includes(config.basePath)) {
+ // throw new Error("Invalid request");
+ // }
const tokenValidity = await CheckDownloadToken(token);
if (!tokenValidity.success) throw new Error(tokenValidity.message);
const decryptedId = await decryptData(encryptedId);
+
const fileMeta = await gdrive.files.get({
fileId: decryptedId,
fields: "id, name, mimeType, size, fileExtension, webContentLink",
From 50b1ccec964956daa67a41a1e0f76301094967c7 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 6 May 2024 01:41:06 +0700
Subject: [PATCH 07/27] Add range header to file request
---
src/app/api/stream/[encryptedId]/route.ts | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
index 921f894..f0b1ba6 100644
--- a/src/app/api/stream/[encryptedId]/route.ts
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -34,11 +34,19 @@ export async function GET(
const decryptedId = await decryptData(encryptedId);
- const fileMeta = await gdrive.files.get({
- fileId: decryptedId,
- fields: "id, name, mimeType, size, fileExtension, webContentLink",
- supportsAllDrives: config.apiConfig.isTeamDrive,
- });
+ const fileMeta = await gdrive.files.get(
+ {
+ fileId: decryptedId,
+ fields: "id, name, mimeType, size, fileExtension, webContentLink",
+ supportsAllDrives: config.apiConfig.isTeamDrive,
+ },
+ {
+ headers: {
+ "Accept-Ranges": "bytes",
+ "Range": request.headers.get("Range") || "bytes=0-",
+ },
+ },
+ );
const fileSize = Number(fileMeta.data.size || 0);
if (!fileMeta.data.webContentLink)
From e1c2e09c9ea6a4e750dfecbba027d36943608b50 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 6 May 2024 01:47:25 +0700
Subject: [PATCH 08/27] If there are no range, request first 1MB
---
src/app/api/stream/[encryptedId]/route.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
index f0b1ba6..486f534 100644
--- a/src/app/api/stream/[encryptedId]/route.ts
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -43,7 +43,7 @@ export async function GET(
{
headers: {
"Accept-Ranges": "bytes",
- "Range": request.headers.get("Range") || "bytes=0-",
+ "Range": request.headers.get("Range") || `bytes=0-${1024 * 1024 - 1}`,
},
},
);
From 3c0a1fdc8e15111cfb80ee6272932cddd2fd8f72 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 6 May 2024 01:53:31 +0700
Subject: [PATCH 09/27] Fix range header, added on the wrong request
---
src/app/api/stream/[encryptedId]/route.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
index 486f534..3039dc9 100644
--- a/src/app/api/stream/[encryptedId]/route.ts
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -78,7 +78,8 @@ export async function GET(
responseType: "stream",
headers: {
"Accept-Ranges": "bytes",
- "Range": ranges,
+ "Range":
+ ranges === "bytes=0-" ? `bytes=0-${1024 * 1024 - 1}` : ranges,
},
},
);
From cd20ad722297d22459627b92d1777ea5b5a96b9d Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 6 May 2024 02:02:46 +0700
Subject: [PATCH 10/27] Update the range end to load 1MB at a time instead of
requesting the whole file
---
src/app/api/stream/[encryptedId]/route.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
index 3039dc9..6da6d63 100644
--- a/src/app/api/stream/[encryptedId]/route.ts
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -59,7 +59,9 @@ export async function GET(
const rangeSize = ranges.match(rangeRegex);
if (rangeSize) {
rangeStart = parseInt(rangeSize[1], 10);
- rangeEnd = rangeSize ? parseInt(rangeSize[2], 10) : fileSize - 1;
+
+ const chunkSize = 1024 * 1024; // Load 1MB at a time
+ rangeEnd = Math.min(rangeStart + chunkSize, fileSize - 1);
}
const contentRange = `bytes=${rangeStart}-${Math.min(
From 40be06c79d7880f907a199a81cba6f937ac9791b Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 6 May 2024 02:12:53 +0700
Subject: [PATCH 11/27] Fix header to use calculated range instead of request
header range
---
src/app/api/stream/[encryptedId]/route.ts | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/app/api/stream/[encryptedId]/route.ts b/src/app/api/stream/[encryptedId]/route.ts
index 6da6d63..8843ab3 100644
--- a/src/app/api/stream/[encryptedId]/route.ts
+++ b/src/app/api/stream/[encryptedId]/route.ts
@@ -53,21 +53,18 @@ export async function GET(
throw new Error("No download link found");
const ranges = request.headers.get("Range") || "bytes=0-";
+ const chunkSize = 1 * 1024 * 1024; // Load 1MB at a time
let rangeStart = 0;
- let rangeEnd = fileSize - 1;
+ let rangeEnd = Math.min(chunkSize, fileSize - 1);
const rangeRegex = /bytes=(\d+)-(\d+)?/;
const rangeSize = ranges.match(rangeRegex);
if (rangeSize) {
rangeStart = parseInt(rangeSize[1], 10);
- const chunkSize = 1024 * 1024; // Load 1MB at a time
rangeEnd = Math.min(rangeStart + chunkSize, fileSize - 1);
}
- const contentRange = `bytes=${rangeStart}-${Math.min(
- rangeEnd,
- fileSize - 1,
- )}/${fileSize}`;
+ const contentRange = `bytes=${rangeStart}-${rangeEnd}/${fileSize}`;
const contentLength = rangeEnd ? rangeEnd - rangeStart + 1 : fileSize;
const fileContent = await gdrive.files.get(
@@ -80,8 +77,7 @@ export async function GET(
responseType: "stream",
headers: {
"Accept-Ranges": "bytes",
- "Range":
- ranges === "bytes=0-" ? `bytes=0-${1024 * 1024 - 1}` : ranges,
+ "Range": `bytes=${rangeStart}-${rangeEnd}`,
},
},
);
From 6be8f159962fab8d2b12e5c3b3ab77a63e8336dc Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Wed, 8 May 2024 08:50:11 +0700
Subject: [PATCH 12/27] Switch `jszip` with `fflate`
---
package.json | 2 +-
yarn.lock | 107 +++++----------------------------------------------
2 files changed, 11 insertions(+), 98 deletions(-)
diff --git a/package.json b/package.json
index 6822503..06ccf5e 100644
--- a/package.json
+++ b/package.json
@@ -46,10 +46,10 @@
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.0.2",
+ "fflate": "^0.8.2",
"googleapis": "^118.0.0",
"input-otp": "^1.2.3",
"jsonwebtoken": "^9.0.0",
- "jszip": "^3.10.1",
"lucide-react": "^0.363.0",
"next": "^14.1.4",
"next-themes": "^0.3.0",
diff --git a/yarn.lock b/yarn.lock
index eeb60d9..e71512b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2866,13 +2866,6 @@ __metadata:
languageName: node
linkType: hard
-"core-util-is@npm:~1.0.0":
- version: 1.0.3
- resolution: "core-util-is@npm:1.0.3"
- checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9
- languageName: node
- linkType: hard
-
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
@@ -3739,6 +3732,13 @@ __metadata:
languageName: node
linkType: hard
+"fflate@npm:^0.8.2":
+ version: 0.8.2
+ resolution: "fflate@npm:0.8.2"
+ checksum: 10c0/03448d630c0a583abea594835a9fdb2aaf7d67787055a761515bf4ed862913cfd693b4c4ffd5c3f3b355a70cf1e19033e9ae5aedcca103188aaff91b8bd6e293
+ languageName: node
+ linkType: hard
+
"fflate@npm:~0.6.9":
version: 0.6.10
resolution: "fflate@npm:0.6.10"
@@ -4470,13 +4470,6 @@ __metadata:
languageName: node
linkType: hard
-"immediate@npm:~3.0.5":
- version: 3.0.6
- resolution: "immediate@npm:3.0.6"
- checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039
- languageName: node
- linkType: hard
-
"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1":
version: 3.3.0
resolution: "import-fresh@npm:3.3.0"
@@ -4511,7 +4504,7 @@ __metadata:
languageName: node
linkType: hard
-"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:~2.0.3":
+"inherits@npm:2, inherits@npm:^2.0.3":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
@@ -4869,13 +4862,6 @@ __metadata:
languageName: node
linkType: hard
-"isarray@npm:~1.0.0":
- version: 1.0.0
- resolution: "isarray@npm:1.0.0"
- checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d
- languageName: node
- linkType: hard
-
"isexe@npm:^2.0.0":
version: 2.0.0
resolution: "isexe@npm:2.0.0"
@@ -5044,18 +5030,6 @@ __metadata:
languageName: node
linkType: hard
-"jszip@npm:^3.10.1":
- version: 3.10.1
- resolution: "jszip@npm:3.10.1"
- dependencies:
- lie: "npm:~3.3.0"
- pako: "npm:~1.0.2"
- readable-stream: "npm:~2.3.6"
- setimmediate: "npm:^1.0.5"
- checksum: 10c0/58e01ec9c4960383fb8b38dd5f67b83ccc1ec215bf74c8a5b32f42b6e5fb79fada5176842a11409c4051b5b94275044851814a31076bf49e1be218d3ef57c863
- languageName: node
- linkType: hard
-
"jwa@npm:^1.4.1":
version: 1.4.1
resolution: "jwa@npm:1.4.1"
@@ -5151,15 +5125,6 @@ __metadata:
languageName: node
linkType: hard
-"lie@npm:~3.3.0":
- version: 3.3.0
- resolution: "lie@npm:3.3.0"
- dependencies:
- immediate: "npm:~3.0.5"
- checksum: 10c0/56dd113091978f82f9dc5081769c6f3b947852ecf9feccaf83e14a123bc630c2301439ce6182521e5fbafbde88e88ac38314327a4e0493a1bea7e0699a7af808
- languageName: node
- linkType: hard
-
"lil-gui@npm:~0.17.0":
version: 0.17.0
resolution: "lil-gui@npm:0.17.0"
@@ -6242,10 +6207,10 @@ __metadata:
encoding: "npm:^0.1.13"
eslint: "npm:8.38.0"
eslint-config-next: "npm:^14.1.4"
+ fflate: "npm:^0.8.2"
googleapis: "npm:^118.0.0"
input-otp: "npm:^1.2.3"
jsonwebtoken: "npm:^9.0.0"
- jszip: "npm:^3.10.1"
lucide-react: "npm:^0.363.0"
next: "npm:^14.1.4"
next-themes: "npm:^0.3.0"
@@ -6615,13 +6580,6 @@ __metadata:
languageName: node
linkType: hard
-"pako@npm:~1.0.2":
- version: 1.0.11
- resolution: "pako@npm:1.0.11"
- checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe
- languageName: node
- linkType: hard
-
"papaparse@npm:^5.4.1":
version: 5.4.1
resolution: "papaparse@npm:5.4.1"
@@ -6989,13 +6947,6 @@ __metadata:
languageName: node
linkType: hard
-"process-nextick-args@npm:~2.0.0":
- version: 2.0.1
- resolution: "process-nextick-args@npm:2.0.1"
- checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367
- languageName: node
- linkType: hard
-
"promise-retry@npm:^2.0.1":
version: 2.0.1
resolution: "promise-retry@npm:2.0.1"
@@ -7341,21 +7292,6 @@ __metadata:
languageName: node
linkType: hard
-"readable-stream@npm:~2.3.6":
- version: 2.3.8
- resolution: "readable-stream@npm:2.3.8"
- dependencies:
- core-util-is: "npm:~1.0.0"
- inherits: "npm:~2.0.3"
- isarray: "npm:~1.0.0"
- process-nextick-args: "npm:~2.0.0"
- safe-buffer: "npm:~5.1.1"
- string_decoder: "npm:~1.1.1"
- util-deprecate: "npm:~1.0.1"
- checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa
- languageName: node
- linkType: hard
-
"readdirp@npm:~3.6.0":
version: 3.6.0
resolution: "readdirp@npm:3.6.0"
@@ -7673,13 +7609,6 @@ __metadata:
languageName: node
linkType: hard
-"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1":
- version: 5.1.2
- resolution: "safe-buffer@npm:5.1.2"
- checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21
- languageName: node
- linkType: hard
-
"safe-regex-test@npm:^1.0.3":
version: 1.0.3
resolution: "safe-regex-test@npm:1.0.3"
@@ -7760,13 +7689,6 @@ __metadata:
languageName: node
linkType: hard
-"setimmediate@npm:^1.0.5":
- version: 1.0.5
- resolution: "setimmediate@npm:1.0.5"
- checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49
- languageName: node
- linkType: hard
-
"shallowequal@npm:1.1.0":
version: 1.1.0
resolution: "shallowequal@npm:1.1.0"
@@ -8008,15 +7930,6 @@ __metadata:
languageName: node
linkType: hard
-"string_decoder@npm:~1.1.1":
- version: 1.1.1
- resolution: "string_decoder@npm:1.1.1"
- dependencies:
- safe-buffer: "npm:~5.1.0"
- checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e
- languageName: node
- linkType: hard
-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1":
version: 6.0.1
resolution: "strip-ansi@npm:6.0.1"
@@ -8680,7 +8593,7 @@ __metadata:
languageName: node
linkType: hard
-"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
+"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"
checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942
From 372d27b5b99d43d64b19fb3fa56173c3b971dfcb Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Wed, 8 May 2024 08:50:32 +0700
Subject: [PATCH 13/27] Add `streamMaxSize` to config
---
src/schema.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/schema.ts b/src/schema.ts
index 5aa1e31..c626c4f 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -95,6 +95,7 @@ export const Schema_Config_API = z
itemsPerPage: z.number().positive(),
searchResult: z.number().positive(),
proxyThumbnail: z.boolean(),
+ streamMaxSize: z.number(),
specialFile: z.object({
password: z.string(),
From cadddc1482b4dbe14e5fb29faff04b4c7c40f5b3 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Wed, 8 May 2024 08:51:16 +0700
Subject: [PATCH 14/27] Load low resolution thumbnail before full thumbnail
---
src/app/@file.grid.tsx | 17 +++++++++++++++--
src/app/@file.list.tsx | 25 +++++++++++++++++++------
2 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/src/app/@file.grid.tsx b/src/app/@file.grid.tsx
index 236a60d..ac5d9ba 100644
--- a/src/app/@file.grid.tsx
+++ b/src/app/@file.grid.tsx
@@ -51,6 +51,9 @@ export default function FileGrid({ data }: Props) {
return new URL(path, config.basePath).pathname;
}, [data, pathname]);
+ const [thumbnailURL, setThumbnailURL] = useState(
+ `/api/thumb/${data.encryptedId}?size=2`,
+ );
const [actionOpen, setActionOpen] = useState(false);
const isDesktop = useMediaQuery("(min-width: 768px)");
@@ -220,13 +223,23 @@ export default function FileGrid({ data }: Props) {
data.mimeType.startsWith("image")) ? (
<>
{
+ if (thumbnailURL.includes("size=2")) {
+ setThumbnailURL(`/api/thumb/${data.encryptedId}`);
+ }
+ }}
className='rounded-top-[var(--radius)] absolute -z-0 h-32 w-full flex-shrink-0 flex-grow-0 object-cover opacity-50'
/>
{
+ if (thumbnailURL.includes("size=2")) {
+ setThumbnailURL(`/api/thumb/${data.encryptedId}`);
+ }
+ }}
className='relative z-0 h-32 w-full flex-shrink-0 flex-grow-0 object-contain backdrop-blur'
/>
diff --git a/src/app/@file.list.tsx b/src/app/@file.list.tsx
index 87041dd..0513e51 100644
--- a/src/app/@file.list.tsx
+++ b/src/app/@file.list.tsx
@@ -29,6 +29,7 @@ import {
import useMediaQuery from "~/hooks/useMediaQuery";
import bytesToReadable from "~/utils/bytesFormat";
+import { durationToReadable } from "~/utils/durationFormat";
import { getPreviewIcon } from "~/utils/previewHelper";
import config from "~/config/gIndex.config";
@@ -42,14 +43,16 @@ export default function FileList({ data }: Props) {
const pathname = usePathname();
const filePath = useMemo(() => {
- // const currentPath = pathname.startsWith("/e") ? pathname : `/e${pathname}`;
- // Set to pathname to remove the /e prefix
const path = [pathname, encodeURIComponent(data.name)]
.join("/")
.replace(/\/+/g, "/");
return new URL(path, config.basePath).pathname;
}, [data, pathname]);
+
+ const [thumbnailURL, setThumbnailURL] = useState(
+ `/api/thumb/${data.encryptedId}?size=2`,
+ );
const [actionOpen, setActionOpen] = useState(false);
const isDesktop = useMediaQuery("(min-width: 768px)");
@@ -218,9 +221,14 @@ export default function FileList({ data }: Props) {
data.mimeType.startsWith("image")) ? (
<>
{
+ if (thumbnailURL.includes("size=2")) {
+ setThumbnailURL(`/api/thumb/${data.encryptedId}`);
+ }
+ }}
+ className='size-16 flex-shrink-0 flex-grow-0 rounded-[var(--radius)] object-cover tablet:size-20'
/>
{data.mimeType.startsWith("video") && (
@@ -230,6 +238,11 @@ export default function FileList({ data }: Props) {
className='absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2 rounded-full bg-muted-foreground fill-muted p-1.5 text-muted opacity-75'
size={24}
/>
+
+ {durationToReadable(
+ data.videoMediaMetadata?.durationMillis || 0,
+ )}
+
>
)}
>
@@ -240,13 +253,13 @@ export default function FileList({ data }: Props) {
? "Folder"
: getPreviewIcon(data.fileExtension || "", data.mimeType)
}
- className='size-16 flex-shrink-0 flex-grow-0 p-3 tablet:size-12'
+ className='size-16 flex-shrink-0 flex-grow-0 p-3 tablet:size-20'
/>
)}
{/* File data */}
-
+
{config.siteConfig.showFileExtension
? data.name
From 56639325aee6cb3a10c38ae027fd577a56bf8487 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Wed, 8 May 2024 08:52:04 +0700
Subject: [PATCH 15/27] Load low resolution thumbnail before full thumbnail
---
src/app/@header.button.tsx | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/app/@header.button.tsx b/src/app/@header.button.tsx
index c8e99f1..680dfc3 100644
--- a/src/app/@header.button.tsx
+++ b/src/app/@header.button.tsx
@@ -503,6 +503,9 @@ export default function HeaderButton({ children }: PropsWithChildren) {
}
function SearchResultItem({ data }: { data: z.infer }) {
+ const [thumbnailURL, setThumbnailURL] = useState(
+ `/api/thumb/${data.encryptedId}?size=2`,
+ );
const router = useRouter();
return (
}) {
data.mimeType.startsWith("image")) ? (
<>
{
+ if (thumbnailURL.includes("size=2")) {
+ setThumbnailURL(`/api/thumb/${data.encryptedId}`);
+ }
+ }}
+ className='size-16 flex-shrink-0 flex-grow-0 rounded-[var(--radius)] object-cover tablet:size-20'
/>
{data.mimeType.startsWith("video") && (
@@ -564,7 +572,7 @@ function SearchResultItem({ data }: { data: z.infer
}) {
? "Folder"
: getPreviewIcon(data.fileExtension || "", data.mimeType)
}
- className='size-16 flex-shrink-0 flex-grow-0 p-3 tablet:size-12'
+ className='size-16 flex-shrink-0 flex-grow-0 p-3 tablet:size-20'
/>
)}
From 0c721ecbf32c233c51a9f8a802f6546a190e5e0b Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Wed, 8 May 2024 08:52:30 +0700
Subject: [PATCH 16/27] Remove small text, and balance the head3
---
src/app/@password.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/app/@password.tsx b/src/app/@password.tsx
index 7bc2cac..06e285f 100644
--- a/src/app/@password.tsx
+++ b/src/app/@password.tsx
@@ -105,14 +105,14 @@ export default function Password({ path, checkPaths, errorMessage }: Props) {
className={cn("h-48 w-64 object-contain")}
/>
-
+
{path === "global"
? "This site are password protected"
: "The folder or file you are trying to access is password protected"}
-
+ {/*
Please enter the password to access the content
-
+ */}
) : (
-
{
- console.error(e);
- setError(
- "Could not preview this audio, try downloading the file",
- );
+ options={{
+ controls: [
+ "play-large",
+ "play",
+ "progress",
+ "current-time",
+ "duration",
+ "mute",
+ "volume",
+ "settings",
+ "fullscreen",
+ ],
+ volume: 0.5,
+ muted: false,
+ loop: {
+ active: false,
+ },
+ speed: {
+ selected: 1,
+ options: [0.5, 1, 1.5, 2],
+ },
+ keyboard: {
+ focused: true,
+ global: false,
+ },
}}
/>
diff --git a/src/app/@preview.doc.tsx b/src/app/@preview.doc.tsx
index 3b6380a..327370c 100644
--- a/src/app/@preview.doc.tsx
+++ b/src/app/@preview.doc.tsx
@@ -1,6 +1,6 @@
"use client";
-import DocViewer, { DocViewerRenderers } from "@cyntler/react-doc-viewer";
+import DocViewer, { DocRenderer } from "@cyntler/react-doc-viewer";
import { useEffect, useState } from "react";
import { z } from "zod";
import { Schema_File } from "~/schema";
@@ -8,15 +8,49 @@ import { cn } from "~/utils";
import Icon from "~/components/Icon";
+import config from "~/config/gIndex.config";
+
import { CreateDownloadToken } from "./actions";
+const GoogleDocsViewerRenderer: DocRenderer = ({
+ mainState: { currentDocument },
+}) => {
+ if (!currentDocument || !currentDocument.uri) return null;
+
+ const viewerUrl = new URL(`/gview`, "https://docs.google.com");
+ viewerUrl.searchParams.set("url", currentDocument.uri);
+ viewerUrl.searchParams.set("embedded", "true");
+
+ return (
+
+ );
+};
+GoogleDocsViewerRenderer.fileTypes = [
+ "application/vnd.google-apps.document",
+ "application/vnd.google-apps.presentation",
+ "application/vnd.google-apps.spreadsheet",
+ "application/msword",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "application/vnd.ms-excel",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "application/vnd.ms-powerpoint",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ "text/csv",
+ "application/pdf",
+ "application/vnd.oasis.opendocument.text",
+];
+GoogleDocsViewerRenderer.weight = 100;
+
type Props = {
file: z.infer
;
};
export default function PreviewDoc({ file }: Props) {
const [docSrc, setDocSrc] = useState("");
- const [docBuffer, setDocBuffer] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
@@ -28,11 +62,12 @@ export default function PreviewDoc({ file }: Props) {
return;
}
const token = await CreateDownloadToken();
- const buffer = await fetch(
- `/api/download/${file.encryptedId}?token=${token}`,
- ).then((res) => res.arrayBuffer());
- setDocBuffer(buffer);
- setDocSrc(`/api/download/${file.encryptedId}?token=${token}`);
+ const streamURL = new URL(
+ `/api/stream/${file.encryptedId}`,
+ config.basePath,
+ );
+ streamURL.searchParams.set("token", token);
+ setDocSrc(streamURL.toString());
} catch (error) {
const e = error as Error;
console.error(e);
@@ -74,12 +109,11 @@ export default function PreviewDoc({ file }: Props) {
documents={[
{
uri: docSrc,
- fileData: docBuffer,
fileName: file.name,
fileType: file.mimeType,
},
]}
- pluginRenderers={DocViewerRenderers}
+ pluginRenderers={[GoogleDocsViewerRenderer]}
config={{
header: {
disableHeader: true,
@@ -118,7 +152,8 @@ export default function PreviewDoc({ file }: Props) {
},
}}
className={cn(
- "h-full max-h-[70dvh] min-h-[70dvh] w-full rounded-[var(--radius)] border border-border !text-black",
+ "h-full max-h-[70dvh] min-h-[70dvh] w-full overflow-hidden rounded-[var(--radius)] !text-black",
+ "rounded-[var(--radius)] [&>div#proxy-renderer]:overflow-hidden ",
)}
theme={{
disableThemeScrollbar: true,
diff --git a/src/app/@preview.image.tsx b/src/app/@preview.image.tsx
index 88a9eeb..0d60d27 100644
--- a/src/app/@preview.image.tsx
+++ b/src/app/@preview.image.tsx
@@ -6,6 +6,9 @@ import { Schema_File } from "~/schema";
import { cn } from "~/utils";
import Icon from "~/components/Icon";
+import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
+
+import config from "~/config/gIndex.config";
import { CreateDownloadToken } from "./actions";
@@ -13,7 +16,10 @@ type Props = {
file: z.infer;
};
export default function PreviewImage({ file }: Props) {
- const [imgSrc, setImgSrc] = useState("");
+ const [imgSrc, setImgSrc] = useState(
+ `/api/thumb/${file.encryptedId}?size=4`,
+ );
+ const [imgLoaded, setImgLoaded] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
@@ -25,7 +31,31 @@ export default function PreviewImage({ file }: Props) {
return;
}
const token = await CreateDownloadToken();
- setImgSrc(`/api/download/${file.encryptedId}?token=${token}&media=1`);
+
+ const streamURL = new URL(
+ `/api/thumb/${file.encryptedId}?size=1000`,
+ config.basePath,
+ );
+ streamURL.searchParams.set("token", token);
+ fetch(streamURL, {
+ headers: {
+ Range: `bytes=0-${(file.size || 1) - 1}`,
+ },
+ })
+ .then((res) => {
+ if (!res.ok) {
+ throw new Error("Could not load image");
+ }
+ return res.blob();
+ })
+ .then((blob) => {
+ const urlobject = URL.createObjectURL(blob);
+ setImgSrc(urlobject);
+ const timeout = setTimeout(() => {
+ setImgLoaded(true);
+ clearTimeout(timeout);
+ }, 150); // Add a delay to show the image
+ });
} catch (error) {
const e = error as Error;
console.error(e);
@@ -62,15 +92,38 @@ export default function PreviewImage({ file }: Props) {
{error}
) : (
-
+
{
+ console.error(e);
+ setError(
+ "Could not preview this image, try downloading the file",
+ );
+ }}
+ />
+
+
+
+
+
+
Preview Only
+
+ This image is a preview and may not be the full resolution.
+ Please download the file for the full resolution.
+
+
+
+
+
)}
);
diff --git a/src/app/@preview.layout.tsx b/src/app/@preview.layout.tsx
index f34bf0d..01119cf 100644
--- a/src/app/@preview.layout.tsx
+++ b/src/app/@preview.layout.tsx
@@ -3,11 +3,15 @@
import { useState } from "react";
import { z } from "zod";
import { Schema_File } from "~/schema";
+import { cn } from "~/utils";
+import Icon from "~/components/Icon";
import { Card, CardContent } from "~/components/ui/card";
import { getFileType } from "~/utils/previewHelper";
+import config from "~/config/gIndex.config";
+
import PreviewAction from "./@preview.action";
import PreviewAudio from "./@preview.audio";
import PreviewDoc from "./@preview.doc";
@@ -35,39 +39,66 @@ export default function FilePreviewLayout({ data, fileType }: Props) {
fileType={fileType}
/>
-
- {fileType === "image" ? (
-
- ) : fileType === "audio" ? (
-
- ) : fileType === "video" ? (
-
- ) : fileType === "code" ? (
-
config.apiConfig.streamMaxSize ? (
+
+
- ) : fileType === "text" ? (
-
- ) : fileType === "markdown" ? (
-
- ) : fileType === "document" ? (
-
- ) : fileType === "pdf" ? (
-
- ) : fileType === "manga" ? (
-
- ) : (
-
- )}
-
+ Preview not available
+
+ Looks like this file size exceed the preview size limit
+
+
+ ) : (
+ /**
+ * TODO: Might need a better way to handle large files preview
+ * like manga, pdf, etc
+ *
+ * For now it's downloading the whole file and then previewing it
+ * which is not a good implementation
+ */
+
+ {fileType === "image" ? (
+
+ ) : fileType === "audio" ? (
+
+ ) : fileType === "video" ? (
+
+ ) : fileType === "code" ? (
+
+ ) : fileType === "text" ? (
+
+ ) : fileType === "markdown" ? (
+
+ ) : fileType === "document" ? (
+
+ ) : fileType === "pdf" ? (
+
+ ) : fileType === "manga" ? (
+
+ ) : (
+
+ )}
+
+ )}