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:
@@ -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();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user