mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Editor: view-scoped Extract-to-Dataset for inline and self-defined data
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Extract-to-Dataset as an editor action (spec §03F; docs/architecture/08 →
|
||||
* editor augmentation) — the data-facing counterpart to the wrap/config actions.
|
||||
*
|
||||
* Resolves the embedded data of the **view the cursor sits in** (the nearest
|
||||
* enclosing `data` binding, honoring Vega-Lite's parent→child inheritance), seeds
|
||||
* the Extract modal with it, and opens the modal. The cursor picks which view to
|
||||
* extract; a single-view spec resolves to the root binding from any cursor, so the
|
||||
* common case needs no thought. Two binding shapes are liftable — a view's inline
|
||||
* `data.values`, and a `{ name }` reference to the spec's own top-level `datasets`
|
||||
* (the data lives in the `datasets` map; we pre-fill the modal with its name).
|
||||
*
|
||||
* The toolbar offers the action whenever *some* view has extractable data
|
||||
* (`hasExtractableData`); when the focused view itself has none — it references a
|
||||
* library dataset, a url, a generator — there is nothing to lift here, so guide the
|
||||
* user to a view that does rather than silently extract the wrong one.
|
||||
*/
|
||||
|
||||
import type * as monaco from 'monaco-editor/esm/vs/editor/edcore.main';
|
||||
import { pathAtOffset } from '@core/spec-cursor';
|
||||
import { dataBindingAtPath } from '@core/spec-data';
|
||||
import { inlineValuesOf, selfDefinedPayloadOf } from '@core/spec-inline-data';
|
||||
import { openModal } from '../modals/ModalCoordinator';
|
||||
import { type ExtractTarget, useExtractStore } from '../stores/ExtractStore';
|
||||
import { notify } from '../stores/NotificationStore';
|
||||
|
||||
// TODO(ux-second-pass): toolbar-only — no F1-palette accelerator like the sibling
|
||||
// wrap/config actions, since this opens a modal rather than an in-place edit.
|
||||
/** Open Extract-to-Dataset scoped to the view at the cursor. */
|
||||
export function runExtract(editor: monaco.editor.IStandaloneCodeEditor): void {
|
||||
const model = editor.getModel();
|
||||
if (!model) return;
|
||||
const text = model.getValue();
|
||||
|
||||
let spec: unknown;
|
||||
try {
|
||||
spec = JSON.parse(text);
|
||||
} catch {
|
||||
notify({
|
||||
kind: 'info',
|
||||
title: 'Can’t extract yet',
|
||||
message: 'Fix the JSON so the spec parses, then extract.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const position = editor.getPosition();
|
||||
const offset = position ? model.getOffsetAt(position) : 0;
|
||||
const binding = dataBindingAtPath(spec, pathAtOffset(text, offset));
|
||||
|
||||
// Inline `values` → rewrite this view's data block; a self-defined `datasets`
|
||||
// reference → lift the named entry and pre-fill the modal with that name.
|
||||
const inline = binding && inlineValuesOf(binding.data);
|
||||
const selfDefined = binding && !inline && selfDefinedPayloadOf(spec, binding.data);
|
||||
let seed: {
|
||||
source: ReturnType<typeof inlineValuesOf>;
|
||||
target: ExtractTarget;
|
||||
name?: string;
|
||||
} | null = null;
|
||||
if (binding && inline) {
|
||||
seed = { source: inline, target: { kind: 'inline', anchorPath: binding.anchorPath } };
|
||||
} else if (binding && selfDefined) {
|
||||
const datasetName = (binding.data as { name: string }).name;
|
||||
seed = {
|
||||
source: selfDefined,
|
||||
target: { kind: 'self-defined', datasetName },
|
||||
name: datasetName,
|
||||
};
|
||||
}
|
||||
|
||||
if (!seed || !seed.source) {
|
||||
notify({
|
||||
kind: 'info',
|
||||
title: 'No data to extract here',
|
||||
message: 'Place the cursor in a view with inline or embedded data to extract it.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
useExtractStore.getState().begin({ source: seed.source, target: seed.target, name: seed.name });
|
||||
openModal('extract');
|
||||
}
|
||||
Reference in New Issue
Block a user