
Integrate File Viewer
A solo builder uses this to integrate a reusable file viewer component that supports multiple file formats like PDFs, images, and other documents directly into their application.
Install
npx skills add https://github.com/cognitedata/builder-skills --skill integrate-file-viewerWhat is this skill?
- Multi-format file viewing (PDF, images, documents)
- React component with viewport optimization
- Built-in annotation and overlay support
Adoption & trust: 1k installs on skills.sh; 4 GitHub stars; 2/3 security scanners passed (skills.sh audits); trending (+100% hot-view momentum).
Recommended Skills
Journey fit
This tool belongs in the build stage because it provides a production-ready component for displaying files, which is essential during active feature development. This belongs in the frontend subphase as it's a React-based UI component for rendering and interacting with files in web applications.
Common Questions / FAQ
Is Integrate File Viewer safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Integrate File Viewer
import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; import type { PageCallback } from 'react-pdf/dist/shared/types.js'; import 'react-pdf/dist/Page/TextLayer.css'; import 'react-pdf/dist/Page/AnnotationLayer.css'; pdfjs.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url, ).toString(); import type { CogniteFileViewerProps } from './types'; import { getViewerType } from './mimeTypes'; import { useFileResolver } from './useFileResolver'; import { useDocumentAnnotations } from './useDocumentAnnotations'; import { DocumentAnnotationOverlay } from './DocumentAnnotationOverlay'; import { useViewport, computeBaseWidth } from './useViewport'; // ============================================================================ // Sub-renderers // ============================================================================ function DefaultLoading() { return <div style={{ padding: 16, color: '#666' }}>Loading file...</div>; } function DefaultError({ error }: { error: Error }) { return ( <div style={{ padding: 16, color: '#c00' }}> Failed to load file: {error.message} </div> ); } function DefaultUnsupported({ mimeType }: { mimeType: string | undefined }) { return ( <div style={{ padding: 16, color: '#666' }}> Unsupported file type{mimeType ? `: ${mimeType}` : ''} </div> ); } // ---------- Shared blob fetch hook ---------- function useBlobUrl(url: string) { const [blobUrl, setBlobUrl] = useState<string | null>(null); const [error, setError] = useState<Error | null>(null); const objectUrlRef = useRef<string | null>(null); useEffect(() => { let cancelled = false; // Reset state for new URL setBlobUrl(null); setError(null); // Revoke previous blob URL if (objectUrlRef.current) { URL.revokeObjectURL(objectUrlRef.current); objectUrlRef.current = null; } fetch(url) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.blob(); }) .then((blob) => { if (cancelled) return; const newUrl = URL.createObjectURL(blob); objectUrlRef.current = newUrl; setBlobUrl(newUrl); }) .catch((err) => { if (!cancelled) setError(err instanceof Error ? err : new Error(String(err))); }); return () => { cancelled = true; if (objectUrlRef.current) { URL.revokeObjectURL(objectUrlRef.current); objectUrlRef.current = null; } }; }, [url]); return { blobUrl, error }; } // ---------- Image ---------- interface ImageRendererProps extends Omit<CogniteFileViewerProps, 'source' | 'client' | 'className' | 'style'> { url: string; } function ImageRenderer(props: ImageRendererProps) { const { url, rotation = 0, fitMode, width: explicitWidth, renderLoading, renderError, renderOverlay } = props; const { currentZoom, effectivePan, containerDims, viewportRef, cursor, handleMouseDown } = useViewport(props); const { blobUrl, error } = useBlobUrl(url); const [naturalSize, setNaturalSize] = useState<{ width: number; height: number } | null>(null); // Reset natural size when URL changes const prevUrlRef = useRef(url); if (prevUrlRef.current !== url) { prevUrlRef.current = url; setNaturalSize(null); } const handleLoad = useCallback((e: React.SyntheticEvent<HTMLImageElement>) => { setNaturalSize({ width: e.currentTarget.naturalWidth, height: e.currentTarget.naturalHeight }); }, []); if (error) return renderError ? renderError(error) : <DefaultError error={error} />; if (!blobUrl) return renderLoading ? renderLoading() : <DefaultLoading />; const baseWidth = computeBaseWidth(fitMode, explicitWidth, containerDims, naturalSize); const imgWidth = baseWidth ?? naturalSize?.width; // Until we know image dimensions, render hidden to measure if (!imgWidth || !naturalSize