Persist Perspective viewer state including column formatting

Save/restore went through both viewer and plugin, where the explicit
plugin.restore could stomp the column formatting the viewer had
already applied. Capture via viewer.save() alone (it includes
plugin_config) and restore via a single viewer.restore call with
edit_mode merged in. Added a perspective-config-update listener so
formatting, sort, and other in-place changes persist to the last-used
cache without an explicit Save.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-04-29 01:01:21 -04:00
parent a5e59f823a
commit 6a98c3f8fc

View File

@ -234,9 +234,8 @@ export default function Forecast({ sourceId, versionId }) {
const saved = localStorage.getItem(LAYOUT_KEY(vid)) const saved = localStorage.getItem(LAYOUT_KEY(vid))
if (saved) { if (saved) {
const cfg = cleanLayout(JSON.parse(saved), validCols) const cfg = cleanLayout(JSON.parse(saved), validCols)
cfg.plugin_config = { edit_mode: 'SELECT_REGION', ...(cfg.plugin_config || {}) }
await viewer.restore(cfg) await viewer.restore(cfg)
const plugin = await viewer.getPlugin()
await plugin.restore({ edit_mode: 'SELECT_REGION', ...(cfg.plugin_config || {}) })
if (cfg.expand_depth != null) await applyDepth(cfg.expand_depth) if (cfg.expand_depth != null) await applyDepth(cfg.expand_depth)
} else { } else {
const dims = meta.filter(c => c.role === 'dimension').map(c => c.cname) const dims = meta.filter(c => c.role === 'dimension').map(c => c.cname)
@ -245,10 +244,18 @@ export default function Forecast({ sourceId, versionId }) {
if (dims.length) cfg.group_by = dims.slice(0, 2) if (dims.length) cfg.group_by = dims.slice(0, 2)
if (dateCol) cfg.split_by = [dateCol] if (dateCol) cfg.split_by = [dateCol]
await viewer.restore(cfg) await viewer.restore(cfg)
const plugin = await viewer.getPlugin()
await plugin.restore({ edit_mode: 'SELECT_REGION' })
} }
// auto-persist viewer state (formatting, columns, etc.) to the last-used cache
if (viewer._pspUpdate) viewer.removeEventListener('perspective-config-update', viewer._pspUpdate)
viewer._pspUpdate = async () => {
try {
const cfg = await captureConfig()
if (cfg) await persistLayout(vid, cfg)
} catch {}
}
viewer.addEventListener('perspective-config-update', viewer._pspUpdate)
// click slice via event filters (Perspective encodes row position as [col,'==',val] triples) // click slice via event filters (Perspective encodes row position as [col,'==',val] triples)
if (viewer._pspClick) viewer.removeEventListener('perspective-click', viewer._pspClick) if (viewer._pspClick) viewer.removeEventListener('perspective-click', viewer._pspClick)
viewer._pspClick = async (e) => { viewer._pspClick = async (e) => {
@ -286,9 +293,8 @@ export default function Forecast({ sourceId, versionId }) {
async function captureConfig() { async function captureConfig() {
const viewer = viewerRef.current const viewer = viewerRef.current
if (!viewer) return null if (!viewer) return null
const plugin = await viewer.getPlugin() const cfg = await viewer.save()
const [cfg, pluginCfg] = await Promise.all([viewer.save(), plugin.save()]) return { ...cfg, expand_depth: expandDepthRef.current }
return { ...cfg, plugin_config: pluginCfg, expand_depth: expandDepthRef.current }
} }
async function persistLayout(vid, cfg) { async function persistLayout(vid, cfg) {
@ -328,11 +334,8 @@ export default function Forecast({ sourceId, versionId }) {
if (!viewer) return if (!viewer) return
const validCols = new Set(tableRef.current ? Object.keys(await tableRef.current.schema()) : []) const validCols = new Set(tableRef.current ? Object.keys(await tableRef.current.schema()) : [])
const cfg = cleanLayout(layout.config, validCols) const cfg = cleanLayout(layout.config, validCols)
cfg.plugin_config = { edit_mode: 'SELECT_REGION', ...(cfg.plugin_config || {}) }
await viewer.restore(cfg) await viewer.restore(cfg)
if (cfg.plugin_config) {
const plugin = await viewer.getPlugin()
await plugin.restore(cfg.plugin_config)
}
if (cfg.expand_depth != null) await applyDepth(cfg.expand_depth) if (cfg.expand_depth != null) await applyDepth(cfg.expand_depth)
setActiveLayoutId(layout.id) setActiveLayoutId(layout.id)
await persistLayout(versionId, cfg) await persistLayout(versionId, cfg)