Pivot: save/restore edit mode and expand depth in named layouts
- Default selection mode is now SELECT_REGION - plugin.save()/restore() used to capture and apply edit mode - expand_depth tracked in ref and included in layout config - applyExpandDepth helper restores depth on layout recall and page load - Save button overwrites active layout in place (no re-typing name) - captureConfig() helper shared by save-over and save-as flows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b88795b015
commit
7c07434049
@ -74,12 +74,15 @@ function filterRowsByConfig(allRows, filters) {
|
||||
}
|
||||
|
||||
const LAYOUT_KEY = (source) => `psp_layout_${source}`
|
||||
const DEFAULT_PLUGIN_CONFIG = { edit_mode: 'SELECT_ROW' }
|
||||
const DEFAULT_PLUGIN_CONFIG = { edit_mode: 'SELECT_REGION' }
|
||||
|
||||
|
||||
export default function Pivot({ source }) {
|
||||
const viewerRef = useRef()
|
||||
const workerRef = useRef()
|
||||
const tableRef = useRef()
|
||||
const allRowsRef = useRef([])
|
||||
const expandDepthRef = useRef(null)
|
||||
const [status, setStatus] = useState('idle')
|
||||
const [error, setError] = useState('')
|
||||
const [inspectedRows, setInspectedRows] = useState(null)
|
||||
@ -135,8 +138,9 @@ export default function Pivot({ source }) {
|
||||
if (cancelled) { worker.terminate(); return }
|
||||
workerRef.current = worker
|
||||
|
||||
await worker.table(rows, { name: source })
|
||||
const table = await worker.table(rows, { name: source })
|
||||
if (cancelled) return
|
||||
tableRef.current = table
|
||||
|
||||
const viewer = viewerRef.current
|
||||
|
||||
@ -147,18 +151,42 @@ export default function Pivot({ source }) {
|
||||
const eventFilters = (detail.config || {}).filter || []
|
||||
const config = await viewer.save()
|
||||
setClickDetail({ row, config, column_names, eventFilters })
|
||||
const matched = filterRowsByConfig(allRowsRef.current, eventFilters)
|
||||
setInspectedRows(matched)
|
||||
|
||||
// Use a Perspective view with the event filters + expressions so computed
|
||||
// columns (split_by) are evaluated and filtered correctly
|
||||
try {
|
||||
const view = await tableRef.current.view({
|
||||
filter: eventFilters,
|
||||
expressions: config.expressions || [],
|
||||
})
|
||||
const data = await view.to_json()
|
||||
await view.delete()
|
||||
// Strip expression columns — only show raw source columns
|
||||
const exprNames = new Set(Object.keys(config.expressions || {}))
|
||||
const cleaned = data.map(r =>
|
||||
Object.fromEntries(Object.entries(r).filter(([k]) => !exprNames.has(k)))
|
||||
)
|
||||
setInspectedRows(cleaned)
|
||||
} catch {
|
||||
setInspectedRows(filterRowsByConfig(allRowsRef.current, eventFilters))
|
||||
}
|
||||
})
|
||||
|
||||
await viewer.load(worker)
|
||||
|
||||
const plugin = await viewer.getPlugin()
|
||||
const savedLayout = localStorage.getItem(LAYOUT_KEY(source))
|
||||
if (savedLayout) {
|
||||
await viewer.restore(JSON.parse(savedLayout))
|
||||
const parsed = JSON.parse(savedLayout)
|
||||
await viewer.restore(parsed)
|
||||
await plugin.restore(parsed.plugin_config || DEFAULT_PLUGIN_CONFIG)
|
||||
if (parsed.expand_depth != null) await applyExpandDepth(viewer, parsed.expand_depth)
|
||||
} else {
|
||||
await viewer.restore({ table: source, settings: true, plugin_config: DEFAULT_PLUGIN_CONFIG })
|
||||
await plugin.restore(DEFAULT_PLUGIN_CONFIG)
|
||||
}
|
||||
await viewer.flush()
|
||||
|
||||
setStatus('ready')
|
||||
} catch (err) {
|
||||
if (!cancelled) { setStatus('error'); setError(err.message) }
|
||||
@ -169,21 +197,58 @@ export default function Pivot({ source }) {
|
||||
return () => { cancelled = true }
|
||||
}, [source])
|
||||
|
||||
async function applyExpandDepth(viewer, depth) {
|
||||
if (depth == null) return
|
||||
const view = await viewer.getView()
|
||||
await view.set_depth(depth)
|
||||
const plugin = await viewer.getPlugin()
|
||||
await plugin.draw(view)
|
||||
expandDepthRef.current = depth
|
||||
}
|
||||
|
||||
async function applyLayout(layout) {
|
||||
const viewer = viewerRef.current
|
||||
if (!viewer) return
|
||||
await viewer.restore(layout.config)
|
||||
if (layout.config.plugin_config) {
|
||||
const plugin = await viewer.getPlugin()
|
||||
await plugin.restore(layout.config.plugin_config)
|
||||
}
|
||||
await applyExpandDepth(viewer, layout.config.expand_depth ?? null)
|
||||
setActiveLayoutId(layout.id)
|
||||
// also persist to localStorage so it survives refresh
|
||||
localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(layout.config))
|
||||
}
|
||||
|
||||
async function captureConfig() {
|
||||
const viewer = viewerRef.current
|
||||
if (!viewer) return null
|
||||
const plugin = await viewer.getPlugin()
|
||||
const [viewerConfig, pluginConfig] = await Promise.all([viewer.save(), plugin.save()])
|
||||
return { ...viewerConfig, plugin_config: pluginConfig, expand_depth: expandDepthRef.current }
|
||||
}
|
||||
|
||||
async function handleSaveOver() {
|
||||
const layout = layouts.find(l => l.id === activeLayoutId)
|
||||
if (!layout) return
|
||||
const config = await captureConfig()
|
||||
if (!config) return
|
||||
try {
|
||||
const saved = await api.savePivotLayout(source, layout.layout_name, config)
|
||||
localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(config))
|
||||
await loadLayouts()
|
||||
setActiveLayoutId(saved.id)
|
||||
flashMsg('Saved!')
|
||||
} catch (err) {
|
||||
flashMsg(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveAs() {
|
||||
const name = saveAsName.trim()
|
||||
if (!name) return
|
||||
const viewer = viewerRef.current
|
||||
if (!viewer) return
|
||||
const config = await viewer.save()
|
||||
const config = await captureConfig()
|
||||
if (!config) return
|
||||
try {
|
||||
const saved = await api.savePivotLayout(source, name, config)
|
||||
localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(config))
|
||||
@ -257,6 +322,13 @@ export default function Pivot({ source }) {
|
||||
</div>
|
||||
))}
|
||||
|
||||
{activeLayoutId !== null && !showSaveAs && (
|
||||
<button onClick={handleSaveOver}
|
||||
className="text-xs text-blue-500 hover:text-blue-700 border border-blue-200 rounded px-2 py-0.5">
|
||||
Save
|
||||
</button>
|
||||
)}
|
||||
|
||||
{showSaveAs ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
@ -296,7 +368,8 @@ export default function Pivot({ source }) {
|
||||
await view.set_depth(d)
|
||||
const p = await v.getPlugin()
|
||||
await p.draw(view)
|
||||
}} className="text-xs border border-gray-200 rounded px-1.5 py-0.5 text-gray-500 hover:border-gray-400 hover:border-gray-400">
|
||||
expandDepthRef.current = d
|
||||
}} className="text-xs border border-gray-200 rounded px-1.5 py-0.5 text-gray-500 hover:border-gray-400">
|
||||
{d}
|
||||
</button>
|
||||
))}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user