mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-12 22:42:13 +02:00
feat: enhance PDF viewer and report panel with public access handling and UI updates
This commit is contained in:
parent
e2cd0557a5
commit
706d5b9821
4 changed files with 37 additions and 23 deletions
|
|
@ -15,6 +15,7 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
|
||||||
interface PdfViewerProps {
|
interface PdfViewerProps {
|
||||||
pdfUrl: string;
|
pdfUrl: string;
|
||||||
|
isPublic?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ZOOM_STEP = 0.15;
|
const ZOOM_STEP = 0.15;
|
||||||
|
|
@ -22,7 +23,7 @@ const MIN_ZOOM = 0.5;
|
||||||
const MAX_ZOOM = 3;
|
const MAX_ZOOM = 3;
|
||||||
const PAGE_GAP = 12;
|
const PAGE_GAP = 12;
|
||||||
|
|
||||||
export function PdfViewer({ pdfUrl }: PdfViewerProps) {
|
export function PdfViewer({ pdfUrl, isPublic = false }: PdfViewerProps) {
|
||||||
const [numPages, setNumPages] = useState(0);
|
const [numPages, setNumPages] = useState(0);
|
||||||
const [scale, setScale] = useState(1);
|
const [scale, setScale] = useState(1);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
@ -192,7 +193,7 @@ export function PdfViewer({ pdfUrl }: PdfViewerProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{numPages > 0 && (
|
{numPages > 0 && (
|
||||||
<div className="flex items-center justify-center gap-2 px-4 py-2 border-b bg-sidebar shrink-0">
|
<div className={`flex items-center justify-center gap-2 px-4 py-2 border-b shrink-0 ${isPublic ? "bg-main-panel" : "bg-sidebar"}`}>
|
||||||
{numPages > 1 && (
|
{numPages > 1 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-xs text-muted-foreground tabular-nums min-w-[60px] text-center">
|
<span className="text-xs text-muted-foreground tabular-nums min-w-[60px] text-center">
|
||||||
|
|
@ -215,10 +216,10 @@ export function PdfViewer({ pdfUrl }: PdfViewerProps) {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={scrollContainerRef}
|
ref={scrollContainerRef}
|
||||||
className="relative flex-1 overflow-auto bg-sidebar"
|
className={`relative flex-1 overflow-auto ${isPublic ? "bg-main-panel" : "bg-sidebar"}`}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="absolute inset-0 flex items-center justify-center text-sidebar-foreground">
|
<div className={`absolute inset-0 flex items-center justify-center ${isPublic ? "text-foreground" : "text-sidebar-foreground"}`}>
|
||||||
<Spinner size="md" />
|
<Spinner size="md" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -286,22 +286,26 @@ export function ReportPanelContent({
|
||||||
}, [activeReportId, currentMarkdown]);
|
}, [activeReportId, currentMarkdown]);
|
||||||
|
|
||||||
const activeVersionIndex = versions.findIndex((v) => v.id === activeReportId);
|
const activeVersionIndex = versions.findIndex((v) => v.id === activeReportId);
|
||||||
|
const isPublic = !!shareToken;
|
||||||
|
const btnBg = isPublic ? "bg-main-panel" : "bg-sidebar";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Action bar — always visible; buttons are disabled while loading */}
|
{/* Action bar — always visible; buttons are disabled while loading */}
|
||||||
<div className="flex items-center justify-between px-4 py-2 shrink-0">
|
<div className="flex items-center justify-between px-4 py-2 shrink-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* Copy button */}
|
{/* Copy button — hidden for Typst (resume) */}
|
||||||
<Button
|
{reportContent?.content_type !== "typst" && (
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={handleCopy}
|
size="sm"
|
||||||
disabled={isLoading || !reportContent?.content}
|
onClick={handleCopy}
|
||||||
className="h-8 min-w-[80px] px-3.5 py-4 text-[15px] bg-sidebar select-none"
|
disabled={isLoading || !reportContent?.content}
|
||||||
>
|
className={`h-8 min-w-[80px] px-3.5 py-4 text-[15px] ${btnBg} select-none`}
|
||||||
{copied ? "Copied" : "Copy"}
|
>
|
||||||
</Button>
|
{copied ? "Copied" : "Copy"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Export dropdown */}
|
{/* Export dropdown */}
|
||||||
<DropdownMenu modal={insideDrawer ? false : undefined}>
|
<DropdownMenu modal={insideDrawer ? false : undefined}>
|
||||||
|
|
@ -310,7 +314,7 @@ export function ReportPanelContent({
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={isLoading || !reportContent?.content}
|
disabled={isLoading || !reportContent?.content}
|
||||||
className="h-8 px-3.5 py-4 text-[15px] gap-1.5 bg-sidebar select-none"
|
className={`h-8 px-3.5 py-4 text-[15px] gap-1.5 ${btnBg} select-none`}
|
||||||
>
|
>
|
||||||
Export
|
Export
|
||||||
<ChevronDownIcon className="size-3" />
|
<ChevronDownIcon className="size-3" />
|
||||||
|
|
@ -336,7 +340,7 @@ export function ReportPanelContent({
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 px-3.5 py-4 text-[15px] gap-1.5 bg-sidebar select-none"
|
className={`h-8 px-3.5 py-4 text-[15px] gap-1.5 ${btnBg} select-none`}
|
||||||
>
|
>
|
||||||
v{activeVersionIndex + 1}
|
v{activeVersionIndex + 1}
|
||||||
<ChevronDownIcon className="size-3" />
|
<ChevronDownIcon className="size-3" />
|
||||||
|
|
@ -381,6 +385,7 @@ export function ReportPanelContent({
|
||||||
) : reportContent.content_type === "typst" ? (
|
) : reportContent.content_type === "typst" ? (
|
||||||
<PdfViewer
|
<PdfViewer
|
||||||
pdfUrl={`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}${shareToken ? `/api/v1/public/${shareToken}/reports/${activeReportId}/preview` : `/api/v1/reports/${activeReportId}/preview`}`}
|
pdfUrl={`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}${shareToken ? `/api/v1/public/${shareToken}/reports/${activeReportId}/preview` : `/api/v1/reports/${activeReportId}/preview`}`}
|
||||||
|
isPublic={isPublic}
|
||||||
/>
|
/>
|
||||||
) : reportContent.content ? (
|
) : reportContent.content ? (
|
||||||
isReadOnly ? (
|
isReadOnly ? (
|
||||||
|
|
@ -432,10 +437,12 @@ function DesktopReportPanel() {
|
||||||
|
|
||||||
if (!panelState.isOpen || !panelState.reportId) return null;
|
if (!panelState.isOpen || !panelState.reportId) return null;
|
||||||
|
|
||||||
|
const isPublic = !!panelState.shareToken;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={panelRef}
|
ref={panelRef}
|
||||||
className="flex w-[50%] max-w-[700px] min-w-[380px] flex-col border-l bg-sidebar text-sidebar-foreground animate-in slide-in-from-right-4 duration-300 ease-out"
|
className={`flex w-[50%] max-w-[700px] min-w-[380px] flex-col border-l animate-in slide-in-from-right-4 duration-300 ease-out ${isPublic ? "bg-main-panel text-foreground" : "bg-sidebar text-sidebar-foreground"}`}
|
||||||
>
|
>
|
||||||
<ReportPanelContent
|
<ReportPanelContent
|
||||||
reportId={panelState.reportId}
|
reportId={panelState.reportId}
|
||||||
|
|
@ -456,6 +463,8 @@ function MobileReportDrawer() {
|
||||||
|
|
||||||
if (!panelState.reportId) return null;
|
if (!panelState.reportId) return null;
|
||||||
|
|
||||||
|
const isPublic = !!panelState.shareToken;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
open={panelState.isOpen}
|
open={panelState.isOpen}
|
||||||
|
|
@ -465,7 +474,7 @@ function MobileReportDrawer() {
|
||||||
shouldScaleBackground={false}
|
shouldScaleBackground={false}
|
||||||
>
|
>
|
||||||
<DrawerContent
|
<DrawerContent
|
||||||
className="h-[90vh] max-h-[90vh] z-80 bg-sidebar overflow-hidden"
|
className={`h-[90vh] max-h-[90vh] z-80 overflow-hidden ${isPublic ? "bg-main-panel" : "bg-sidebar"}`}
|
||||||
overlayClassName="z-80"
|
overlayClassName="z-80"
|
||||||
>
|
>
|
||||||
<DrawerHandle />
|
<DrawerHandle />
|
||||||
|
|
|
||||||
|
|
@ -96,8 +96,10 @@ function ReportErrorState({ title, error }: { title: string; error: string }) {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-5 h-px bg-border/50" />
|
<div className="mx-5 h-px bg-border/50" />
|
||||||
<div className="px-5 py-4">
|
<div className="px-5 py-4">
|
||||||
<p className="text-sm font-medium text-foreground line-clamp-2">{title}</p>
|
{title && title !== "Report" && (
|
||||||
<p className="text-sm text-muted-foreground mt-1">{error}</p>
|
<p className="text-sm font-medium text-foreground line-clamp-2">{title}</p>
|
||||||
|
)}
|
||||||
|
<p className={`text-sm text-muted-foreground${title && title !== "Report" ? " mt-1" : ""}`}>{error}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,10 @@ function ResumeErrorState({ title, error }: { title: string; error: string }) {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-5 h-px bg-border/50" />
|
<div className="mx-5 h-px bg-border/50" />
|
||||||
<div className="px-5 py-4">
|
<div className="px-5 py-4">
|
||||||
<p className="text-sm font-medium text-foreground line-clamp-2">{title}</p>
|
{title && title !== "Resume" && (
|
||||||
<p className="text-sm text-muted-foreground mt-1">{error}</p>
|
<p className="text-sm font-medium text-foreground line-clamp-2">{title}</p>
|
||||||
|
)}
|
||||||
|
<p className={`text-sm text-muted-foreground${title && title !== "Resume" ? " mt-1" : ""}`}>{error}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -231,7 +233,7 @@ function ResumeCard({
|
||||||
<div className="px-5 pt-3 pb-4">
|
<div className="px-5 pt-3 pb-4">
|
||||||
{thumbState === "loading" && <ThumbnailSkeleton />}
|
{thumbState === "loading" && <ThumbnailSkeleton />}
|
||||||
{thumbState === "error" && (
|
{thumbState === "error" && (
|
||||||
<p className="text-sm text-muted-foreground italic">Preview unavailable</p>
|
<p className="text-sm text-muted-foreground">Preview unavailable</p>
|
||||||
)}
|
)}
|
||||||
{pdfUrl && (
|
{pdfUrl && (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue