diff --git a/CLAUDE.md b/CLAUDE.md index 8c38e14..39adf2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -158,7 +158,7 @@ The frontend lives in `ui/src/` and is built to `public/` via `npm run build` fr ### Pages - **Sources / Rules / Mappings / Records** — standard CRUD pages -- **Pivot** (`ui/src/pages/Pivot.jsx`) — interactive pivot/crosstab powered by Perspective (`@perspective-dev` v4.4.0, loaded from CDN). See `docs/perspective-pivot.md` for the full Perspective API reference. +- **Pivot** (`ui/src/pages/Pivot.jsx`) — interactive pivot/crosstab powered by Perspective (`@perspective-dev` v4.5.1, installed via npm). See `docs/perspective-pivot.md` for the full Perspective API reference. - **Stacks** — multi-source union views with running balance - **Log** — import audit trail diff --git a/ui/package.json b/ui/package.json index a6b2906..9dec769 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,6 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "@perspective-dev/client": "^4.5.1", + "@perspective-dev/viewer": "^4.5.1", + "@perspective-dev/viewer-d3fc": "^4.4.1", + "@perspective-dev/viewer-datagrid": "^4.5.1", "react": "^19.2.4", "react-dom": "^19.2.4", "react-router-dom": "^7.13.2", diff --git a/ui/src/pages/Pivot.jsx b/ui/src/pages/Pivot.jsx index 3ab60a7..6054a9e 100644 --- a/ui/src/pages/Pivot.jsx +++ b/ui/src/pages/Pivot.jsx @@ -1,34 +1,19 @@ import { useEffect, useRef, useState, useCallback } from 'react' import { api } from '../api' import useTheme from '../theme.jsx' +import perspective from '@perspective-dev/client/inline' +import '@perspective-dev/viewer/inline' +import '@perspective-dev/viewer-datagrid' +import '@perspective-dev/viewer-d3fc' +import '@perspective-dev/viewer/themes' async function fetchAllRows(source) { const res = await api.getViewData(source, 100000, 0) return res.rows || [] } -let perspectivePromise = null - function loadPerspective() { - if (perspectivePromise) return perspectivePromise - perspectivePromise = (async () => { - if (!document.getElementById('psp-theme')) { - const link = document.createElement('link') - link.id = 'psp-theme' - link.rel = 'stylesheet' - link.crossOrigin = 'anonymous' - link.href = 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer/dist/css/themes.css' - document.head.appendChild(link) - } - const [{ default: perspective }] = await Promise.all([ - import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/client@4.4.0/dist/cdn/perspective.js'), - import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer@4.4.0/dist/cdn/perspective-viewer.js'), - import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer-datagrid@4.4.0/dist/cdn/perspective-viewer-datagrid.js'), - import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer-d3fc@4.4.0/dist/cdn/perspective-viewer-d3fc.js'), - ]) - return perspective - })() - return perspectivePromise + return Promise.resolve(perspective) } function formatVal(v, decimals = 2) {