mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Data inspector: input/resolved rows below the chart, with a resizable divider
A collapsible Data panel under the Live Preview and Chart Builder charts shows the rows the chart actually uses, switching between Input (parsed source) and Resolved (post-transform) views read from the live Vega view. Collapsed by default; the open-state and a draggable height divider persist. Rows come through a new RenderHandle.inspectData() accessor, so no component touches the view: core/result-data picks the most-upstream source and most-downstream result from the compiled dataflow, read lazily. The divider reuses the window-splitter pattern (horizontal variant). Consolidations: a shared DataTable primitive replaces the inspector's and the builder's duplicate read-only tables; useResizeDrag merges the col/row drag-gesture twins. Docs: spec 04/06 and arch 05/10 updated; the now-shipped exploration memo removed.
This commit is contained in:
@@ -37,6 +37,8 @@ import { usePreviewStore } from '../stores/PreviewStore';
|
||||
import { selectShownText, useSnippetStore } from '../stores/SnippetStore';
|
||||
import { useUserSettingsStore } from '../stores/UserSettingsStore';
|
||||
import { ChartExport } from './ChartExport';
|
||||
import { DataInspector } from './DataInspector';
|
||||
import { InspectorSplitHandle } from './InspectorSplitHandle';
|
||||
import { SegmentedControl, type SegmentedOption } from './SegmentedControl';
|
||||
import { SelectControl, type SelectControlOption } from './SelectControl';
|
||||
import { RangeControl, SettingRow, SettingsPopover } from './SettingsPopover';
|
||||
@@ -176,6 +178,9 @@ export function LivePreview() {
|
||||
const fitMode = useAppStore((s) => s.previewFitMode);
|
||||
const uiTheme = useAppStore((s) => s.uiTheme);
|
||||
const chartTheme = useAppStore((s) => s.chartTheme);
|
||||
// Data inspector: open-state gates the divider + table; its height is the divider's.
|
||||
const inspectorOpen = useAppStore((s) => s.dataInspectorOpen);
|
||||
const inspectorHeight = useAppStore((s) => s.dataInspectorHeight);
|
||||
// Custom themes feed `custom:<id>` selection resolution; re-rendering on a
|
||||
// change keeps the chart live while a selected theme is edited in the builder.
|
||||
const customThemes = useCustomThemeStore((s) => s.themes);
|
||||
@@ -201,6 +206,11 @@ export function LivePreview() {
|
||||
// a ref change alone wouldn't re-render. Set true on a successful render, false
|
||||
// on clear/error/unmount.
|
||||
const [chartReady, setChartReady] = useState(false);
|
||||
// Bumped whenever a render settles and changes the live view (success, clear,
|
||||
// error) so the data inspector re-reads the resolved rows. A monotonic counter,
|
||||
// not `chartReady` — consecutive successful renders keep `chartReady` true, but
|
||||
// each one is new data the inspector must pick up.
|
||||
const [renderEpoch, setRenderEpoch] = useState(0);
|
||||
|
||||
// Busy-indication timer ref: if a render exceeds ~1s we surface a non-blocking
|
||||
// overlay (arch §10.2 NN/g: >1s owes a busy indication; <1s shows nothing to
|
||||
@@ -284,6 +294,7 @@ export function LivePreview() {
|
||||
handleRef.current?.destroy();
|
||||
handleRef.current = null;
|
||||
setChartReady(false);
|
||||
setRenderEpoch((e) => e + 1);
|
||||
setError(null);
|
||||
clearBusy();
|
||||
return;
|
||||
@@ -306,11 +317,13 @@ export function LivePreview() {
|
||||
handleRef.current = handle;
|
||||
configRef.current = config;
|
||||
setChartReady(true);
|
||||
setRenderEpoch((e) => e + 1);
|
||||
setError(null);
|
||||
clearBusy();
|
||||
} catch (e) {
|
||||
if (mine === generationRef.current) {
|
||||
setChartReady(false);
|
||||
setRenderEpoch((e) => e + 1);
|
||||
clearBusy();
|
||||
// A missing dataset reference is not a JSON/spec problem, so it gets a
|
||||
// tailored, fixable message instead of the generic syntax hint (council:
|
||||
@@ -379,6 +392,11 @@ export function LivePreview() {
|
||||
[],
|
||||
);
|
||||
|
||||
// The input + resolved rows for the data inspector — reads the live view through
|
||||
// the handle (null when no chart is up). Stable identity; the inspector re-reads
|
||||
// on `renderEpoch`, so this need not depend on it (it always reads the latest handle).
|
||||
const getInspectData = useCallback(() => handleRef.current?.inspectData() ?? null, []);
|
||||
|
||||
// Re-fit the chart when its container resizes (e.g. a pane drag). Vega doesn't
|
||||
// observe the element, so we do: one observer on the stable host node for the
|
||||
// component's life. Only responsive fit modes depend on container size;
|
||||
@@ -450,6 +468,19 @@ export function LivePreview() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Data inspector — input vs. resolved rows, stacked below the chart (a
|
||||
collapsed disclosure by default); reads the live view through getInspectData.
|
||||
When open, a draggable divider sizes it (chart absorbs the change) and the
|
||||
stored height fills the table; collapsed, it's just the disclosure bar. */}
|
||||
{inspectorOpen && (
|
||||
<InspectorSplitHandle label="Resize data inspector" controls="preview-data-inspector" />
|
||||
)}
|
||||
<DataInspector
|
||||
id="preview-data-inspector"
|
||||
getData={getInspectData}
|
||||
renderEpoch={renderEpoch}
|
||||
heightPx={inspectorOpen ? inspectorHeight : undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user