From e5cb9aa848009ea22c1385e4d33b7edf372979fb Mon Sep 17 00:00:00 2001
From: Ahmad Mujahid <55625580+AhmadMuj@users.noreply.github.com>
Date: Mon, 17 Feb 2025 13:25:16 +0400
Subject: feat: Add PDF screenshot generation and display (#995)

* Updated pdf2json to 3.1.5

* Extract and store a screenshot from PDF files using pdf2pic

* Installing graphicsmagick and ghostscript

* Generate Missing PDF screenshot with tidyAssets worker for backward support

* Display PDF screenshot instead of the PDF in web if it exists.

* Display PDF screenshot in mobile app if exists.

* Updated pnpm-lock.yaml

* Removed console.log

* Revert the unnecessary changes in package.json

* Revert pnpm-lock changes

* Prevent rendering PDF files if the screenshot is not generated

* refactor: replace useEffect with useMemo for section initialization

* feat: show PDF file download button and handle large PDFs by defaulting to screenshot view

* feat: add file size to openapi spec

* feature: Add Assets preprocessing in fix mode to admin actions

* i18n: add reprocess_assets_fix_mode translation

* i18n: Add missing ar translations

* A bunch of fixes

* Fix openspec schema

---------

Co-authored-by: Mohamed Bassem <me@mbassem.com>
---
 .../dashboard/preview/AssetContentSection.tsx      | 131 ++++++++++++++++-----
 1 file changed, 103 insertions(+), 28 deletions(-)

(limited to 'apps/web/components/dashboard/preview/AssetContentSection.tsx')

diff --git a/apps/web/components/dashboard/preview/AssetContentSection.tsx b/apps/web/components/dashboard/preview/AssetContentSection.tsx
index 03ab8a43..8590d2ad 100644
--- a/apps/web/components/dashboard/preview/AssetContentSection.tsx
+++ b/apps/web/components/dashboard/preview/AssetContentSection.tsx
@@ -1,42 +1,117 @@
+import { useMemo, useState } from "react";
 import Image from "next/image";
 import Link from "next/link";
+import {
+  Select,
+  SelectContent,
+  SelectGroup,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { useTranslation } from "@/lib/i18n/client";
 
+import { getAssetUrl } from "@hoarder/shared-react/utils/assetUtils";
 import { BookmarkTypes, ZBookmark } from "@hoarder/shared/types/bookmarks";
 
-export function AssetContentSection({ bookmark }: { bookmark: ZBookmark }) {
+// 20 MB
+const BIG_FILE_SIZE = 20 * 1024 * 1024;
+
+function PDFContentSection({ bookmark }: { bookmark: ZBookmark }) {
   if (bookmark.content.type != BookmarkTypes.ASSET) {
     throw new Error("Invalid content type");
   }
+  const { t } = useTranslation();
 
-  switch (bookmark.content.assetType) {
-    case "image": {
-      return (
-        <div className="relative h-full min-w-full">
-          <Link
-            href={`/api/assets/${bookmark.content.assetId}`}
-            target="_blank"
-          >
-            <Image
-              alt="asset"
-              fill={true}
-              className="object-contain"
-              src={`/api/assets/${bookmark.content.assetId}`}
-            />
-          </Link>
-        </div>
-      );
+  const initialSection = useMemo(() => {
+    if (bookmark.content.type != BookmarkTypes.ASSET) {
+      throw new Error("Invalid content type");
     }
-    case "pdf": {
-      return (
-        <iframe
-          title={bookmark.content.assetId}
-          className="h-full w-full"
-          src={`/api/assets/${bookmark.content.assetId}`}
-        />
-      );
+
+    const screenshot = bookmark.assets.find(
+      (item) => item.assetType === "assetScreenshot",
+    );
+    const bigSize =
+      bookmark.content.size && bookmark.content.size > BIG_FILE_SIZE;
+    if (bigSize && screenshot) {
+      return "screenshot";
     }
-    default: {
+    return "pdf";
+  }, [bookmark]);
+  const [section, setSection] = useState(initialSection);
+
+  const screenshot = bookmark.assets.find(
+    (r) => r.assetType === "assetScreenshot",
+  )?.id;
+
+  const content =
+    section === "screenshot" && screenshot ? (
+      <div className="relative h-full min-w-full">
+        <Image
+          alt="screenshot"
+          src={getAssetUrl(screenshot)}
+          fill={true}
+          className="object-contain"
+        />
+      </div>
+    ) : (
+      <iframe
+        title={bookmark.content.assetId}
+        className="h-full w-full"
+        src={getAssetUrl(bookmark.content.assetId)}
+      />
+    );
+
+  return (
+    <div className="flex h-full flex-col items-center gap-2">
+      <div className="flex w-full items-center justify-center gap-4">
+        <Select onValueChange={setSection} value={section}>
+          <SelectTrigger className="w-fit">
+            <SelectValue />
+          </SelectTrigger>
+          <SelectContent>
+            <SelectGroup>
+              <SelectItem value="screenshot" disabled={!screenshot}>
+                {t("common.screenshot")}
+              </SelectItem>
+              <SelectItem value="pdf">PDF</SelectItem>
+            </SelectGroup>
+          </SelectContent>
+        </Select>
+      </div>
+      {content}
+    </div>
+  );
+}
+
+function ImageContentSection({ bookmark }: { bookmark: ZBookmark }) {
+  if (bookmark.content.type != BookmarkTypes.ASSET) {
+    throw new Error("Invalid content type");
+  }
+  return (
+    <div className="relative h-full min-w-full">
+      <Link href={getAssetUrl(bookmark.content.assetId)} target="_blank">
+        <Image
+          alt="asset"
+          fill={true}
+          className="object-contain"
+          src={getAssetUrl(bookmark.content.assetId)}
+        />
+      </Link>
+    </div>
+  );
+}
+
+export function AssetContentSection({ bookmark }: { bookmark: ZBookmark }) {
+  if (bookmark.content.type != BookmarkTypes.ASSET) {
+    throw new Error("Invalid content type");
+  }
+  switch (bookmark.content.assetType) {
+    case "image":
+      return <ImageContentSection bookmark={bookmark} />;
+    case "pdf":
+      return <PDFContentSection bookmark={bookmark} />;
+    default:
       return <div>Unsupported asset type</div>;
-    }
   }
 }
-- 
cgit v1.2.3-70-g09d2

