mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Per-chart export: copy/download spec and PNG/SVG from the preview header
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
* (M3) plugs into prepareSpecForRender without changing this component.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import type { VisualizationSpec } from 'vega-embed';
|
||||
import type { FitMode } from '@core/rendering';
|
||||
@@ -27,6 +27,7 @@ import { useDatasetStore } from '../stores/DatasetStore';
|
||||
import { usePreviewStore } from '../stores/PreviewStore';
|
||||
import { selectShownText, useSnippetStore } from '../stores/SnippetStore';
|
||||
import { useUserSettingsStore } from '../stores/UserSettingsStore';
|
||||
import { ChartExport } from './ChartExport';
|
||||
import { SegmentedControl, type SegmentedOption } from './SegmentedControl';
|
||||
import { RangeControl, SettingRow, SettingsPopover } from './SettingsPopover';
|
||||
import styles from './LivePreview.module.css';
|
||||
@@ -116,6 +117,11 @@ export function LivePreview() {
|
||||
const setError = usePreviewStore((s) => s.setError);
|
||||
const busy = usePreviewStore((s) => s.busy);
|
||||
const setBusy = usePreviewStore((s) => s.setBusy);
|
||||
// Mirrors whether `handleRef` currently holds a live view, so the per-chart
|
||||
// export's image actions (which need the view) can enable/disable reactively —
|
||||
// a ref change alone wouldn't re-render. Set true on a successful render, false
|
||||
// on clear/error/unmount.
|
||||
const [chartReady, setChartReady] = useState(false);
|
||||
|
||||
// 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
|
||||
@@ -198,6 +204,7 @@ export function LivePreview() {
|
||||
if (isEmpty) {
|
||||
handleRef.current?.destroy();
|
||||
handleRef.current = null;
|
||||
setChartReady(false);
|
||||
setError(null);
|
||||
clearBusy();
|
||||
return;
|
||||
@@ -218,10 +225,12 @@ export function LivePreview() {
|
||||
return;
|
||||
}
|
||||
handleRef.current = handle;
|
||||
setChartReady(true);
|
||||
setError(null);
|
||||
clearBusy();
|
||||
} catch (e) {
|
||||
if (mine === generationRef.current) {
|
||||
setChartReady(false);
|
||||
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:
|
||||
@@ -253,11 +262,31 @@ export function LivePreview() {
|
||||
datasets,
|
||||
setError,
|
||||
setBusy,
|
||||
setChartReady,
|
||||
bufferEpoch,
|
||||
editorView,
|
||||
renderDebounce,
|
||||
]);
|
||||
|
||||
// Rasterize/serialize the live view for the per-chart export (spec §08). Reads
|
||||
// `handleRef` (the view LivePreview owns); returns null when no view is live or
|
||||
// the export fails, so the export UI can report it. Stable identity (no deps).
|
||||
const getImageUrl = useCallback(
|
||||
async (
|
||||
format: 'png' | 'svg',
|
||||
options: { scale: number; background: string | null },
|
||||
): Promise<string | null> => {
|
||||
const handle = handleRef.current;
|
||||
if (!handle) return null;
|
||||
try {
|
||||
return await handle.toImageURL(format, options);
|
||||
} catch {
|
||||
return 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;
|
||||
@@ -280,6 +309,7 @@ export function LivePreview() {
|
||||
() => () => {
|
||||
handleRef.current?.destroy();
|
||||
handleRef.current = null;
|
||||
setChartReady(false);
|
||||
if (busyTimerRef.current !== null) clearTimeout(busyTimerRef.current);
|
||||
usePreviewStore.getState().setError(null);
|
||||
usePreviewStore.getState().setBusy(false);
|
||||
@@ -291,7 +321,11 @@ export function LivePreview() {
|
||||
<div className={styles.preview}>
|
||||
<div className={styles.header}>
|
||||
<FitControl />
|
||||
<PreviewSettings />
|
||||
{/* Right cluster: export this chart, then the preview settings gear. */}
|
||||
<div className={styles.headerEnd}>
|
||||
<ChartExport chartReady={chartReady} getImageUrl={getImageUrl} />
|
||||
<PreviewSettings />
|
||||
</div>
|
||||
</div>
|
||||
{/*
|
||||
* aria-busy on the chart region tells AT the area is being updated (arch §10.2;
|
||||
|
||||
Reference in New Issue
Block a user