mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Editor: Extract Config to New Theme as a third Config action
This commit is contained in:
@@ -79,7 +79,8 @@ When a snippet's spec embeds its data inline, the user can lift that data out in
|
||||
|
||||
## G. Spec ↔ Config Actions
|
||||
|
||||
Two editor actions make the injected chart theme portable (see _Live Preview → Chart theme_). Their visible home is a **Config** menu in the editor toolbar (a value-select disclosure listing both actions with a one-line description each), disabled when no snippet is active or the read-only published view is shown; the editor's right-click context menu and F1 command palette offer the same actions as expert accelerators. Both actions replace the document as a single undoable edit (⌘/Ctrl+Z restores), reformatted in the app's JSON style, and both refuse with a clear toast when the document is not a valid JSON object.
|
||||
Three editor actions make the injected chart theme portable (see _Live Preview → Chart theme_). Their visible home is a **Config** menu in the editor toolbar (a value-select disclosure listing the actions with a one-line description each), disabled when no snippet is active or the read-only published view is shown; the editor's right-click context menu and F1 command palette offer the same actions as expert accelerators. All replace the document as a single undoable edit (⌘/Ctrl+Z restores), reformatted in the app's JSON style, and all refuse with a clear toast when the document is not a valid JSON object.
|
||||
|
||||
- **Merge Chart Theme into Spec** — bakes the currently selected chart theme into the spec's own `config` block, deep-merging under any existing `config` so the spec's own keys win and the rendered result is unchanged. Use it before publishing a spec somewhere the app's theme won't follow. When the selected theme injects nothing (Stock Vega-Lite), the action explains there is nothing to merge.
|
||||
- **Extract Config from Spec** — removes the spec's `config` block and copies it to the clipboard, for cleaning baked-in styling out of a pasted spec. The clipboard copy happens **before** the removal; if the copy fails, the spec is left unchanged so the config is never lost. A spec with no config block reports that and changes nothing.
|
||||
- **Extract Config to New Theme** — removes the spec's `config` block and saves it as a new custom chart theme (see _Live Preview → Theme Builder_), named after the snippet and auto-suffixed if taken, then selects that theme as the active chart theme so the styling keeps applying to this chart from the injected side. The success toast names the created theme and points at Edit themes… for renaming or refining. A spec with no config block reports that and changes nothing. (If a non-stock theme was active before, any of its keys the extracted config didn't cover stop applying — the new theme replaces it wholesale.)
|
||||
|
||||
@@ -28,6 +28,7 @@ import { openModal } from '../modals/ModalCoordinator';
|
||||
import {
|
||||
installSpecConfigActions,
|
||||
runExtractConfig,
|
||||
runExtractConfigToTheme,
|
||||
runMergeChartTheme,
|
||||
} from '../services/spec-config-actions';
|
||||
import { useAppStore } from '../stores/AppStore';
|
||||
@@ -160,6 +161,11 @@ const CONFIG_ACTIONS = [
|
||||
label: 'Extract config from spec',
|
||||
detail: 'Remove the config block and copy it to the clipboard',
|
||||
},
|
||||
{
|
||||
value: 'extract-theme',
|
||||
label: 'Extract config to new theme',
|
||||
detail: 'Save the config block as a custom chart theme and select it',
|
||||
},
|
||||
] as const;
|
||||
|
||||
type ConfigActionId = (typeof CONFIG_ACTIONS)[number]['value'];
|
||||
@@ -194,7 +200,8 @@ function EditorToolbar({
|
||||
const editor = editorRef.current;
|
||||
if (!editor) return;
|
||||
if (action === 'merge') runMergeChartTheme(editor);
|
||||
else void runExtractConfig(editor);
|
||||
else if (action === 'extract') void runExtractConfig(editor);
|
||||
else runExtractConfigToTheme(editor);
|
||||
};
|
||||
|
||||
const handleRevert = async () => {
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
* it to the clipboard — for cleaning baked-in styling out of a pasted spec.
|
||||
* The clipboard write happens *before* the edit, so a clipboard failure
|
||||
* never destroys the only copy.
|
||||
* - **Extract config to new theme** removes the block and saves it as a custom
|
||||
* chart theme instead, selecting it — the one-step "this pasted spec's
|
||||
* styling should be a reusable theme" path.
|
||||
*
|
||||
* Both replace the document via `executeEdits`, so ⌘Z restores the previous
|
||||
* text. Both no-op (with a toast naming the reason) on invalid JSON; Monaco's
|
||||
@@ -26,10 +29,16 @@
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/edcore.main';
|
||||
import { formatJson } from '@core/json-format';
|
||||
import { extractConfigFromSpec, isJsonObject, mergeConfigIntoSpec } from '@core/spec-config';
|
||||
import { CHART_THEME_OPTIONS, chartConfigForSelection } from '@core/vega-themes';
|
||||
import {
|
||||
CHART_THEME_OPTIONS,
|
||||
chartConfigForSelection,
|
||||
customThemeSelection,
|
||||
} from '@core/vega-themes';
|
||||
import { copyText } from '../infrastructure/file-transfer';
|
||||
import { useAppStore } from '../stores/AppStore';
|
||||
import { useCustomThemeStore } from '../stores/CustomThemeStore';
|
||||
import { notify } from '../stores/NotificationStore';
|
||||
import { selectActiveSnippet, useSnippetStore } from '../stores/SnippetStore';
|
||||
|
||||
/** Parse the model's JSON, or toast (and return null) when it isn't a JSON object. */
|
||||
function parseSpecObject(model: monaco.editor.ITextModel): Record<string, unknown> | null {
|
||||
@@ -140,7 +149,47 @@ export async function runExtractConfig(editor: monaco.editor.IStandaloneCodeEdit
|
||||
}
|
||||
|
||||
/**
|
||||
* Register both actions on the editor (context menu + F1 palette — the expert
|
||||
* Turn the draft's `config` block into a saved custom theme: create the theme
|
||||
* (named after the snippet, auto-suffixed if taken), remove the block from the
|
||||
* spec, and select the new theme as the active chart theme — so the styling
|
||||
* keeps applying to this chart, now from the injected side of the boundary.
|
||||
* (Not pixel-identical when another theme was active: that theme's
|
||||
* non-overlapping keys stop applying once the new theme replaces it.)
|
||||
*/
|
||||
export function runExtractConfigToTheme(editor: monaco.editor.IStandaloneCodeEditor): void {
|
||||
const model = editor.getModel();
|
||||
if (!model) return;
|
||||
const spec = parseSpecObject(model);
|
||||
if (!spec) return;
|
||||
|
||||
const { spec: rest, config } = extractConfigFromSpec(spec);
|
||||
if (config === null) {
|
||||
notify({
|
||||
kind: 'info',
|
||||
title: 'No config to extract',
|
||||
message: 'This spec has no config block.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = selectActiveSnippet(useSnippetStore.getState());
|
||||
const theme = useCustomThemeStore
|
||||
.getState()
|
||||
.createTheme(snippet ? `${snippet.name} theme` : 'Extracted theme', config);
|
||||
useAppStore.getState().setChartTheme(customThemeSelection(theme.id));
|
||||
|
||||
replaceDocument(editor, model, 'extract-config-to-theme', rest);
|
||||
notify({
|
||||
kind: 'success',
|
||||
title: 'Theme created',
|
||||
message:
|
||||
`The config block became the custom theme “${theme.name}”, now selected as the ` +
|
||||
'chart theme. Rename or refine it via Edit themes…; undo the spec edit with ⌘/Ctrl+Z.',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the config actions on the editor (context menu + F1 palette — the expert
|
||||
* accelerators; the toolbar Config menu is the discoverable home). Returns a
|
||||
* disposable that detaches them (dispose on editor unmount, like the
|
||||
* format-on-paste hook).
|
||||
@@ -166,10 +215,20 @@ export function installSpecConfigActions(
|
||||
run: () => runExtractConfig(editor),
|
||||
});
|
||||
|
||||
const extractToTheme = editor.addAction({
|
||||
id: 'astrolabe.extract-config-to-theme',
|
||||
label: 'Extract Config to New Theme',
|
||||
contextMenuGroupId: 'astrolabe',
|
||||
contextMenuOrder: 3,
|
||||
precondition: '!editorReadonly',
|
||||
run: () => runExtractConfigToTheme(editor),
|
||||
});
|
||||
|
||||
return {
|
||||
dispose() {
|
||||
merge.dispose();
|
||||
extract.dispose();
|
||||
extractToTheme.dispose();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* changes how the chart looks.
|
||||
* - **Extract** lifts the `config` block out of a spec — for cleaning styling
|
||||
* out of a pasted-in spec (the caller decides where the extracted config
|
||||
* goes: clipboard today, a saved theme later).
|
||||
* goes: the clipboard, or a saved custom theme).
|
||||
*
|
||||
* Pure object-in/object-out; JSON text handling (parse, format, undo) is the
|
||||
* editor integration's job.
|
||||
|
||||
Reference in New Issue
Block a user