Editor: view-scoped Extract-to-Dataset for inline and self-defined data

This commit is contained in:
2026-06-29 02:08:59 +03:00
parent 8cad80f738
commit 6656811b8e
16 changed files with 910 additions and 180 deletions
+17 -7
View File
@@ -26,7 +26,7 @@ import '../infrastructure/monaco-env'; // side-effect: wire workers before creat
import { configureVegaLiteJson } from '../infrastructure/monaco-schema';
import { configureJsonFormatter, installFormatOnPaste } from '../infrastructure/monaco-format';
import { parseChartSpecText } from '@core/chart-builder';
import { openChartBuilderForEdit, openModal } from '../modals/ModalCoordinator';
import { openChartBuilderForEdit } from '../modals/ModalCoordinator';
import {
installSpecConfigActions,
runExtractConfig,
@@ -41,10 +41,11 @@ import {
runWrap,
} from '../services/spec-transform-actions';
import { configureSpecDatasetHints } from '../services/spec-dataset-hints';
import { runExtract } from '../services/extract-action';
import { useAppStore } from '../stores/AppStore';
import { confirm } from '../stores/ConfirmStore';
import { useDatasetStore } from '../stores/DatasetStore';
import { hasInlineData } from '../stores/ExtractStore';
import { hasExtractableData } from '../stores/ExtractStore';
import { publishActiveSnippet } from '../services/snippet-actions';
import { notify } from '../stores/NotificationStore';
import { usePreviewStore } from '../stores/PreviewStore';
@@ -222,10 +223,11 @@ function EditorToolbar({
const draft = s.editorView === 'draft' ? s.draftText : active.draftSpec;
return draft !== active.spec;
});
// Offer Extract only when the live draft carries top-level inline data to lift
// out (spec §03F → hidden when the spec has no inline data).
// Offer Extract only when the live draft carries data to lift out — inline
// `values` in any view, or a reference to a self-defined `datasets` entry (spec
// §03F → hidden when there is nothing extractable).
const canExtract = useSnippetStore(
(s) => s.activeSnippetId !== null && hasInlineData(s.draftText),
(s) => s.activeSnippetId !== null && hasExtractableData(s.draftText),
);
// Offer "Open in builder" only when the active snippet's published spec is
@@ -248,6 +250,14 @@ function EditorToolbar({
if (snippet) openChartBuilderForEdit(snippet);
};
// Extract is scoped to the view at the cursor (services/extract-action), so it
// goes through the editor handle like the wrap/config actions, not a bare
// openModal — the service captures the focused binding before opening the modal.
const handleExtract = () => {
const editor = editorRef.current;
if (editor) runExtract(editor);
};
// Publish + its success toast live in one place (services/snippet-actions) so
// the button and the Cmd/Ctrl+S shortcut (EventRouter) behave identically.
const handlePublish = publishActiveSnippet;
@@ -315,8 +325,8 @@ function EditorToolbar({
{canExtract && (
<Button
className={styles.collapsible}
onClick={() => openModal('extract')}
title="Extract inline data into a reusable dataset"
onClick={handleExtract}
title="Extract embedded data into a reusable dataset"
aria-label="Extract to Dataset"
>
<Icon name="dataset" className={styles.actionIcon} />