import { Component, type ReactNode, useEffect, useState } from "react"; import type { RetainedArtifactReference } from "@opengeni/sdk"; import { PierreFile } from "../../../components/pierre-file"; import { enablePierreDiffs } from "../../../diffs"; import { useAppContext } from "../../app-context"; import { useAppearance } from "../../lib/appearance"; import { Button } from "../../../ui/button"; import { decodeRetainedText, TEXT_PREVIEW_MAX_BYTES } from "./retained-text-preview-policy"; // Highlighted file views load @pierre/diffs lazily, only where they render. enablePierreDiffs(); class HighlightBoundary extends Component< { children: ReactNode; fallback: ReactNode }, { failed: boolean } > { state = { failed: false }; static getDerivedStateFromError() { return { failed: true }; } render() { return this.state.failed ? this.props.fallback : this.props.children; } } export default function RetainedTextPreview({ workspaceId, artifact: initialArtifact, filename, }: { workspaceId: string; artifact: RetainedArtifactReference; filename?: string | undefined; }) { // Receipt identity changes remount this component in RetainedFilePreview. const [artifact] = useState(initialArtifact); const { client } = useAppContext(); const [result, setResult] = useState<{ text?: string | undefined; error?: string | undefined; retry?: boolean | undefined; client: typeof client; } | null>(null); const [attempt, setAttempt] = useState(0); const [plain, setPlain] = useState(false); const theme = useAppearance().resolvedTheme; useEffect(() => { const controller = new AbortController(); setResult(null); if ( !Number.isSafeInteger(artifact.originalBytes) || artifact.originalBytes < 0 || artifact.originalBytes > TEXT_PREVIEW_MAX_BYTES ) { setResult({ error: "This file is too large for inline preview (256 KiB limit).", client }); return; } void client .downloadRetainedArtifact(workspaceId, artifact, { signal: controller.signal }) .then(({ bytes }) => { if (controller.signal.aborted) return; try { setResult({ text: decodeRetainedText(bytes), client }); } catch (error) { setResult({ error: (error as Error).message, client }); } }) .catch(() => { if (!controller.signal.aborted) setResult({ error: "Preview could not be loaded.", retry: true, client }); }); return () => controller.abort(); }, [client, workspaceId, artifact, attempt]); if (!result || result.client !== client) return (
Loading preview…
); if (result.error) return (This file is empty.
); const source = (
{result.text}
);
return (