--- name: integrate-file-viewer description: "MUST be used whenever integrating CogniteFileViewer into a Flows app to preview CDF files (PDFs, images, text). Do NOT manually wire up react-pdf or file resolution — this skill handles installation, Vite config, worker setup, and component usage. Triggers: file viewer, file preview, CogniteFileViewer, PDF viewer, view CDF files, document viewer, preview file." allowed-tools: Read, Glob, Grep, Edit, Write, Bash --- # Integrate CogniteFileViewer Add `CogniteFileViewer` to this Flows app to preview CDF files (PDF, image, text). ## Dependencies The file-viewer library files (copied in Step 2) require this npm package: | Package | Version | |---|---| | `react-pdf` | `^9.1.1` | `pdfjs-dist` ships as a dependency of `react-pdf` at the correct version — do not install it separately. `react` and `@cognite/sdk` are assumed to already be present in Flows apps. --- ## Your job Complete these steps in order. Read each file before modifying it. --- ## Step 1 — Understand the app Read these files before touching anything: - `package.json` — detect package manager (`packageManager` field or lock file) and existing deps - `vite.config.ts` — understand current Vite setup - The component where the viewer should be added --- ## Step 2 — Copy the file-viewer source files The file-viewer library lives in the `code/` directory next to this skill file. Read and copy **all** files from there into `src/cognite-file-viewer/` inside the app: - `code/types.ts` - `code/mimeTypes.ts` - `code/fileResolution.ts` - `code/useViewport.ts` - `code/useFileResolver.ts` - `code/useDocumentAnnotations.ts` - `code/DocumentAnnotationOverlay.tsx` - `code/CogniteFileViewer.tsx` - `code/index.ts` > The PDF.js worker is configured inside `CogniteFileViewer.tsx` — no separate consumer setup is needed. --- ## Step 3 — Install dependencies Install `react-pdf` (see **Dependencies** above) using the app's package manager: - pnpm → `pnpm add react-pdf@^9.1.1` - npm → `npm install react-pdf@^9.1.1` - yarn → `yarn add react-pdf@^9.1.1` > **pnpm users:** pnpm's strict linking may prevent the browser from resolving `pdfjs-dist`. Either add `pdfjs-dist` as a direct dependency (`pnpm add pdfjs-dist`), or add `public-hoist-pattern[]=pdfjs-dist` to `.npmrc`. --- ## Step 4 — Configure Vite Add `optimizeDeps.exclude: ['pdfjs-dist']` to `vite.config.ts` to prevent Vite from pre-bundling pdfjs-dist (which breaks the worker): ```ts export default defineConfig({ // ... existing config ... optimizeDeps: { exclude: ['pdfjs-dist'], }, }); ``` --- ## Step 5 — Use the component Import and render `CogniteFileViewer` from the locally copied files: ```tsx import { CogniteFileViewer } from './cognite-file-viewer'; ``` Get the `sdk` from the `useDune()` hook (already available in every Flows app): ```tsx import { useDune } from '@cognite/dune'; const { sdk } = useDune(); ``` ### Supported file types | Type | Formats | |---|---| | PDF | `.pdf` — page navigation, zoom, pan, diagram annotation overlay | | Office documents | Word, PowerPoint, Excel, ODS, ODP, ODT, RTF, TSV — converted to PDF via the CDF Document Preview API, then rendered identically to PDF | | Image | JPEG, PNG, WebP, SVG, TIFF — zoom, pan, rotation | | Text | `.txt`, `.csv`, `.json` — rendered as preformatted text | | Other | Falls back to `renderUnsupported` | ### Minimal usage This is all you need — zoom, pan, and touch gestures are handled internally: ```tsx ``` > **The component needs a defined height.** If the parent has no explicit height, the viewer will collapse to zero. Always set a `height` via `style`, `className`, or the parent container. ### File source Pass any of three source types: ```tsx // By instance ID (data-modelled file — enables annotations) // By CDF internal ID // By direct URL ``` **Prefer `instanceId` when available** — it's the only source type that enables the diagram annotation overlay. When listing files via `sdk.files.list()`, check `file.instanceId` first: ```tsx source={ file.instanceId ? { type: 'instanceId', space: file.instanceId.space, externalId: file.instanceId.externalId } : { type: 'internalId', id: file.id } } ``` ### Full props reference ```tsx setNumPages(numPages)} // Zoom & pan (works on PDF and images) zoom={zoom} // 1 = 100%; Ctrl/Cmd+wheel, pinch-to-zoom, and middle-click drag built in onZoomChange={setZoom} minZoom={0.25} // default maxZoom={5} // default panOffset={pan} // controlled pan offset; resets on page change onPanChange={setPan} // Fit mode fitMode="width" // 'width' fits to container width; 'page' fits entire page in container // Rotation (PDFs and images) rotation={rotation} // 0 | 90 | 180 | 270 // Diagram annotations (instanceId sources only) showAnnotations={true} // default onAnnotationClick={(annotation) => { /* annotation.linkedResource has space + externalId */ }} onAnnotationHover={(annotation) => {}} // Custom annotation tooltip (replaces native tooltip) renderAnnotationTooltip={(annotation, rect) => ( <div style={{ position: 'absolute', left: rect.x + rect.width, top: rect.y, zIndex: 11, }}> {annotation.text} </div> )} // Custom overlay (SVG paths, highlights, drawings — works on PDF and images) renderOverlay={({ width, height, originalWidth, originalHeight, pageNumber, rotation }) => ( <svg width={width} height={height} viewBox={`0 0 ${originalWidth} ${originalHeight}`} preserveAspectRatio="none" style={{ position: 'absolute', top: 0, left: 0, pointerEvents: 'all' }} > <path d="..." stroke="cyan" fill="none" /> </svg> )} // Custom renderers (all optional) renderLoading={() => <MySpinner />} renderError={(error) => <MyError message={error.message} />} renderUnsupported={(mimeType) => <div>Cannot preview {mimeType}</div>} // Layout className="..." style={{ width: '100%', height: '100%' }} /> ``` --- ## Tips & tricks **Reset page, zoom and rotation when the source changes.** The component does not reset these automatically when you switch files — do it yourself: ```ts const navigateToFile = (file: FileInfo) => { setSelectedFile(file); setPage(1); setZoom(1); setRotation(0); }; ``` **Gate pagination UI on `numPages > 0`.** `onDocumentLoad` only fires for PDFs. Don't render pagination controls until you know there are pages to paginate: ```tsx {numPages > 0 && ( <> <button disabled={page <= 1} onClick={() => setPage(p => p - 1)}>‹</button> <span>{page} / {numPages}</span> <button disabled={page >= numPages} onClick={() => setPage(p => p + 1)}>›</button> </> )} ``` **Annotation click → navigate to linked file.** `annotation.linkedResource` contains the `space` and `externalId` of the linked CDF instance. Match it against `file.instanceId` to navigate: ```ts onAnnotationClick={(annotation) => { if (!annotation.linkedResource) return; const { space, externalId } = annotation.linkedResource; const linked = files.find( f => f.instanceId?.space === space && f.instanceId?.externalId === externalId ); if (linked) navigateToFile(linked); }} ``` **Touch support is built in.** Two-finger pinch-to-zoom and two-finger drag-to-pan work on touch devices automatically. No configuration needed. **Pan is middle-click drag** (when zoomed in) on desktop. Left-click remains free for annotation clicks and text selection. **Ctrl/Cmd + wheel zooms toward the cursor** — also built in. Wire `zoom`/`onZoomChange` if you want programmatic zoom buttons or to persist zoom state; otherwise it works fully uncontrolled. **`renderOverlay` receives original page dimensions** (`originalWidth`, `originalHeight`) so you can set up an SVG `viewBox` in the original coordinate space. Paths drawn in PDF-point or image-pixel coordinates will map correctly to the rendered page at any zoom level. --- ## Common pitfalls | Problem | Cause | Fix | |---|---|---| | `Failed to resolve module specifier 'pdf.worker.mjs'` | pdfjs-dist not hoisted (pnpm) | Add `public-hoist-pattern[]=pdfjs-dist` to `.npmrc`, or `pnpm add pdfjs-dist` directly | | `API version does not match Worker version` | `pdfjs-dist` version mismatch between app and `react-pdf` | Do not install `pdfjs-dist` separately — let `react-pdf` provide it. If already installed, remove it | | Annotations never show | `instanceId` is `undefined` — annotation overlay is disabled without it | Use `instanceId` source, or fall back and accept no annotations for classic files | | Annotations show but are empty | File has no `CogniteDiagramAnnotation` edges in CDF | Expected — only P&ID/diagram files synced to the data model have annotations | | Viewer collapses to zero height | Parent has no explicit height | Set `height` via `style`, `className`, or parent CSS |