diff --git a/package-lock.json b/package-lock.json index ca2cca0..8c445fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -400,6 +400,54 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, "node_modules/@codemirror/lang-json": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", @@ -411,6 +459,22 @@ "@lezer/json": "^1.0.0" } }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, "node_modules/@codemirror/lang-sql": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", @@ -1731,6 +1795,18 @@ "license": "MIT", "peer": true }, + "node_modules/@lezer/css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/highlight": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", @@ -1741,6 +1817,30 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/html": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", + "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/json": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", @@ -1763,6 +1863,17 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/markdown": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.0.tgz", + "integrity": "sha512-AXb98u3M6BEzTnreBnGtQaF7xFTiMA92Dsy5tqEjpacbjRxDSFdN4bKJo9uvU4cEEOS7D2B9MT7kvDgOEIzJSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/@lezer/yaml": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", @@ -3051,6 +3162,19 @@ "tailwindcss": "4.1.13" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, "node_modules/@tsconfig/svelte": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.5.tgz", @@ -4537,6 +4661,19 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -6856,6 +6993,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss/node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -8477,6 +8628,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", @@ -9667,6 +9825,7 @@ "@codemirror/autocomplete": "^6.19.0", "@codemirror/commands": "^6.9.0", "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "6.5.0", "@codemirror/lang-sql": "^6.10.0", "@codemirror/language": "^6.11.3", "@codemirror/lint": "^6.9.0", @@ -9685,6 +9844,7 @@ "@sveltejs/vite-plugin-svelte": "^6.1.0", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/postcss": "^4.1.11", + "@tailwindcss/typography": "0.5.19", "@tsconfig/svelte": "^5.0.4", "@types/d3-array": "^3.2.1", "@types/d3-color": "^3.1.3", diff --git a/packages/viewer/package.json b/packages/viewer/package.json index f72b878..a3e9148 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -39,6 +39,7 @@ "@codemirror/lint": "^6.9.0", "@codemirror/lang-json": "^6.0.2", "@codemirror/lang-sql": "^6.10.0", + "@codemirror/lang-markdown": "6.5.0", "@codemirror/theme-one-dark": "^6.1.3", "codemirror-json-schema": "^0.8.1", "@huggingface/transformers": "^3.7.1", @@ -47,6 +48,7 @@ "@sveltejs/vite-plugin-svelte": "^6.1.0", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/postcss": "^4.1.11", + "@tailwindcss/typography": "0.5.19", "@tsconfig/svelte": "^5.0.4", "@types/d3-array": "^3.2.1", "@types/d3-color": "^3.1.3", diff --git a/packages/viewer/src/assets/chart-markdown.svg b/packages/viewer/src/assets/chart-markdown.svg new file mode 100644 index 0000000..ac1bb90 --- /dev/null +++ b/packages/viewer/src/assets/chart-markdown.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/viewer/src/assets/chart_icons.ts b/packages/viewer/src/assets/chart_icons.ts index d9fbd6d..9993b48 100644 --- a/packages/viewer/src/assets/chart_icons.ts +++ b/packages/viewer/src/assets/chart_icons.ts @@ -4,6 +4,7 @@ import chart_boxplot from "./chart-boxplot.svg?raw"; import chart_embedding from "./chart-embedding.svg?raw"; import chart_h_bar from "./chart-h-bar.svg?raw"; import chart_heatmap from "./chart-heatmap.svg?raw"; +import chart_markdown from "./chart-markdown.svg?raw"; import chart_predicates from "./chart-predicates.svg?raw"; import chart_spec from "./chart-spec.svg?raw"; import chart_stacked from "./chart-stacked.svg?raw"; @@ -18,4 +19,5 @@ export const chartIcons: Record = { "chart-v-histogram": chart_v_histogram, "chart-spec": chart_spec, "chart-predicates": chart_predicates, + "chart-markdown": chart_markdown, }; diff --git a/packages/viewer/src/assets/icons.ts b/packages/viewer/src/assets/icons.ts index 60fe527..0db6989 100644 --- a/packages/viewer/src/assets/icons.ts +++ b/packages/viewer/src/assets/icons.ts @@ -2,6 +2,7 @@ export { default as IconPlus } from "~icons/fluent/add-16-filled"; export { default as IconReset } from "~icons/fluent/arrow-reset-24-filled"; +export { default as IconCheck } from "~icons/fluent/checkmark-16-filled"; export { default as IconChevronDown, default as IconDown } from "~icons/fluent/chevron-down-16-filled"; export { default as IconChevronUp, default as IconUp } from "~icons/fluent/chevron-up-16-filled"; export { default as IconAnnotation } from "~icons/fluent/comment-text-16-filled"; @@ -9,12 +10,11 @@ export { default as IconEmbeddingView } from "~icons/fluent/data-scatter-20-fill export { default as IconClose } from "~icons/fluent/dismiss-16-filled"; export { default as IconDownload, default as IconExport } from "~icons/fluent/document-arrow-down-16-filled"; export { default as IconEdit } from "~icons/fluent/edit-16-filled"; +export { default as IconDashboardLayout } from "~icons/fluent/layout-dynamic-20-filled"; +export { default as IconListLayout } from "~icons/fluent/layout-row-four-20-filled"; export { default as IconTable } from "~icons/fluent/panel-bottom-20-filled"; export { default as IconMenu } from "~icons/fluent/panel-right-20-filled"; export { default as IconSearch } from "~icons/fluent/search-16-filled"; export { default as IconSettings } from "~icons/fluent/settings-16-filled"; export { default as IconDarkMode } from "~icons/fluent/weather-moon-16-filled"; export { default as IconLightMode } from "~icons/fluent/weather-sunny-16-filled"; - -export { default as IconDashboardLayout } from "~icons/fluent/layout-dynamic-20-filled"; -export { default as IconListLayout } from "~icons/fluent/layout-row-four-20-filled"; diff --git a/packages/viewer/src/charts/basic/Markdown.svelte b/packages/viewer/src/charts/basic/Markdown.svelte new file mode 100644 index 0000000..04704d6 --- /dev/null +++ b/packages/viewer/src/charts/basic/Markdown.svelte @@ -0,0 +1,31 @@ + + + +{#if mode == "view"} + + {@html marked(spec.content ?? "", { async: false })} + +{:else} + + spec.title ?? "", (value) => onSpecChange({ title: value })} /> + { + onSpecChange({ content: value }); + }} + /> + +{/if} diff --git a/packages/viewer/src/charts/basic/types.ts b/packages/viewer/src/charts/basic/types.ts index ff4e24b..ca7647d 100644 --- a/packages/viewer/src/charts/basic/types.ts +++ b/packages/viewer/src/charts/basic/types.ts @@ -66,3 +66,9 @@ export interface PredicatesSpec { title?: string; items?: { name: string; predicate: string }[]; } + +export interface MarkdownSpec { + type: "markdown"; + title?: string; + content: string; +} diff --git a/packages/viewer/src/charts/builder/Builder.svelte b/packages/viewer/src/charts/builder/Builder.svelte index 4c851ac..aad6cf0 100644 --- a/packages/viewer/src/charts/builder/Builder.svelte +++ b/packages/viewer/src/charts/builder/Builder.svelte @@ -178,6 +178,7 @@ spec={localChartSpec} state={localChartState ?? {}} width={"container"} + mode="view" onStateChange={(update, mode = "merge") => { applyUpdatesIfNeeded(localChartState ?? {}, update, mode, (r) => (localChartState = r)); }} diff --git a/packages/viewer/src/charts/chart.ts b/packages/viewer/src/charts/chart.ts index 120d975..1c64d53 100644 --- a/packages/viewer/src/charts/chart.ts +++ b/packages/viewer/src/charts/chart.ts @@ -130,6 +130,9 @@ export interface ChartViewProps { */ state: State; + /** The mode of the chart view. The view can decide how to interpret this. */ + mode: "view" | "edit"; + /** * Callback for when the state changes. * The default update mode is "merge", where the new state is recursively merged into the existing state. diff --git a/packages/viewer/src/charts/chart_types.ts b/packages/viewer/src/charts/chart_types.ts index cace150..a64e526 100644 --- a/packages/viewer/src/charts/chart_types.ts +++ b/packages/viewer/src/charts/chart_types.ts @@ -8,6 +8,7 @@ import CountPlotList from "./basic/CountPlotList.svelte"; import Histogram from "./basic/Histogram.svelte"; import Histogram2D from "./basic/Histogram2D.svelte"; import HistogramStack from "./basic/HistogramStack.svelte"; +import Markdown from "./basic/Markdown.svelte"; import Placeholder from "./basic/Placeholder.svelte"; import Predicates from "./basic/Predicates.svelte"; import Builder from "./builder/Builder.svelte"; @@ -20,21 +21,32 @@ import type { Histogram2DSpec, HistogramSpec, HistogramStackSpec, + MarkdownSpec, PredicatesSpec, } from "./basic/types.js"; -import type { EmbeddingSpec } from "./embedding/types.js"; -import type { TableSpec } from "./table/types.js"; - import type { UIElement } from "./builder/builder_description.js"; import type { ChartBuilderDescription, ChartViewProps } from "./chart.js"; +import type { EmbeddingSpec } from "./embedding/types.js"; +import type { TableSpec } from "./table/types.js"; export type ChartComponent = Component, {}, "">; +interface ChartTypeOptions { + /** + * The chart component supports edit mode. + * If set to true, the chart component is responsible for editing the chart. + * Otherwise, a JSON spec editor will be used. + */ + supportsEditMode?: boolean; +} + const chartTypes: Record = {}; +const chartTypeOptions: Record = {}; const chartBuilders: ChartBuilderDescription[] = []; -export function registerChartType(type: string, component: ChartComponent) { +export function registerChartType(type: string, component: ChartComponent, options: ChartTypeOptions = {}) { chartTypes[type] = component; + chartTypeOptions[type] = options; } export function registerChartBuilder(builder: ChartBuilderDescription) { @@ -55,6 +67,20 @@ export function findChartComponent(spec: any): ChartComponent { return Placeholder; } +export function findChartTypeOptions(spec: any): ChartTypeOptions { + if (typeof spec != "object") { + return {}; + } + if (typeof spec.type == "string") { + let r = chartTypeOptions[spec.type]; + if (r == null) { + return {}; + } + return r; + } + return {}; +} + export function chartBuilderDescriptions(): ChartBuilderDescription[] { return chartBuilders; } @@ -72,6 +98,7 @@ registerChartType("box-plot", BoxPlot); registerChartType("embedding", Embedding); registerChartType("predicates", Predicates); registerChartType("table", Table); +registerChartType("markdown", Markdown, { supportsEditMode: true }); // Spec type for all builtin chart types export type BuiltinChartSpec = @@ -82,7 +109,8 @@ export type BuiltinChartSpec = | CountPlotSpec | PredicatesSpec | EmbeddingSpec - | TableSpec; + | TableSpec + | MarkdownSpec; // Chart builders @@ -218,6 +246,18 @@ registerChartBuilder({ }), }); +registerChartBuilder({ + icon: "chart-markdown", + description: "Create a view with markdown content", + preview: false, + ui: [{ code: { key: "content", language: "markdown" } }] as const, + create: ({ content }): any | undefined => ({ + type: "markdown", + title: "Markdown", + content: content, + }), +}); + registerChartBuilder({ icon: "chart-spec", description: "Create a chart with custom spec", diff --git a/packages/viewer/src/charts/embedding/Embedding.svelte b/packages/viewer/src/charts/embedding/Embedding.svelte index 102451f..3c64db8 100644 --- a/packages/viewer/src/charts/embedding/Embedding.svelte +++ b/packages/viewer/src/charts/embedding/Embedding.svelte @@ -249,6 +249,7 @@ context={context} spec={{ items: categoryLegend.legend }} state={chartState.legend ?? {}} + mode="view" onSpecChange={() => {}} onStateChange={(update, mode) => { onStateChange({ legend: update }); diff --git a/packages/viewer/src/layouts/LayoutView.svelte b/packages/viewer/src/layouts/LayoutView.svelte index f0f9c23..1a431f1 100644 --- a/packages/viewer/src/layouts/LayoutView.svelte +++ b/packages/viewer/src/layouts/LayoutView.svelte @@ -61,13 +61,14 @@ onChartsChange={updateCharts} onChartStatesChange={updateChartStates} > - {#snippet chartView({ id, width, height })} + {#snippet chartView({ id, width, height, mode })} diff --git a/packages/viewer/src/layouts/dashboard/DashboardChartPanel.svelte b/packages/viewer/src/layouts/dashboard/DashboardChartPanel.svelte index 9c65419..cdd9799 100644 --- a/packages/viewer/src/layouts/dashboard/DashboardChartPanel.svelte +++ b/packages/viewer/src/layouts/dashboard/DashboardChartPanel.svelte @@ -6,8 +6,10 @@ import SpecEditor from "../../charts/builder/SpecEditor.svelte"; import CornerButton from "../../widgets/CornerButton.svelte"; - import { IconClose, IconEdit } from "../../assets/icons.js"; + import { IconCheck, IconClose, IconEdit } from "../../assets/icons.js"; + import type { ChartContext } from "../../charts/chart.js"; + import { findChartTypeOptions } from "../../charts/chart_types.js"; import type { LayoutProps } from "../layout.js"; import { type Grid } from "./placement.js"; @@ -55,6 +57,8 @@ let { x, y, width, height } = $derived(grid.resolvePlacement(placement)); let isEditing = $state(false); + let supportsEditMode = $derived(findChartTypeOptions(spec).supportsEditMode ?? false); + let chartMode: "edit" | "view" = $derived(supportsEditMode && isEditing ? "edit" : "view"); function dragHandler(mask: [number, number, number, number]) { return (e1: CursorValue) => { @@ -141,7 +145,7 @@
{ isEditing = !isEditing; }} @@ -150,8 +154,8 @@
- {#if !isEditing} - {@render chartView({ id: id, width: "container", height: "container" })} + {#if !(chartMode == "view" && isEditing && onSpecChange)} + {@render chartView({ id: id, width: "container", height: "container", mode: chartMode })} {:else}
{ onChartStatesChange: (states: Record, mode?: "merge" | "replace") => void; /** A snippet that renders a given chart. */ - chartView: Snippet<[{ id: string; width?: number | "container"; height?: number | "container" }]>; + chartView: Snippet< + [{ id: string; width?: number | "container"; height?: number | "container"; mode?: "view" | "edit" }] + >; } export type LayoutOptionsProps = Omit, "chartView">; diff --git a/packages/viewer/src/layouts/list/ListChartPanel.svelte b/packages/viewer/src/layouts/list/ListChartPanel.svelte index 32c149b..167978b 100644 --- a/packages/viewer/src/layouts/list/ListChartPanel.svelte +++ b/packages/viewer/src/layouts/list/ListChartPanel.svelte @@ -5,7 +5,17 @@ import SpecEditor from "../../charts/builder/SpecEditor.svelte"; import CornerButton from "../../widgets/CornerButton.svelte"; - import { IconChevronDown, IconChevronUp, IconClose, IconDown, IconEdit, IconUp } from "../../assets/icons.js"; + import { + IconCheck, + IconChevronDown, + IconChevronUp, + IconClose, + IconDown, + IconEdit, + IconUp, + } from "../../assets/icons.js"; + + import { findChartTypeOptions } from "../../charts/chart_types.js"; import type { LayoutProps } from "../layout.js"; interface Props { @@ -31,11 +41,13 @@ $props(); let isEditing = $state(false); + let supportsEditMode = $derived(findChartTypeOptions(spec).supportsEditMode ?? false); + let chartMode: "edit" | "view" = $derived(supportsEditMode && isEditing ? "edit" : "view"); -
+
-
+
{#if onSpecChange} - (isEditing = !isEditing)} /> + { + isEditing = !isEditing; + if (isEditing) { + onIsVisibleChange?.(true); + } + }} + /> {/if} {#if onUp} @@ -68,8 +89,8 @@ style:transition="grid-template-rows 300ms ease-in-out" >
- {@render chartView({ id: id, width: "container" })} - {#if isEditing && onSpecChange} + {@render chartView({ id: id, width: "container", mode: chartMode })} + {#if chartMode == "view" && isEditing && onSpecChange}
+
import { untrack } from "svelte"; + // CodeMirror packages import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete"; import { defaultKeymap, history, historyKeymap, indentLess, insertTab } from "@codemirror/commands"; - import { PostgreSQL, sql } from "@codemirror/lang-sql"; import { bracketMatching, defaultHighlightStyle, indentOnInput, syntaxHighlighting } from "@codemirror/language"; import { lintKeymap } from "@codemirror/lint"; import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; import { EditorState } from "@codemirror/state"; - import { oneDark } from "@codemirror/theme-one-dark"; import { crosshairCursor, drawSelection, @@ -24,8 +23,14 @@ tooltips, } from "@codemirror/view"; + // Languages + import { markdown } from "@codemirror/lang-markdown"; + import { PostgreSQL, sql } from "@codemirror/lang-sql"; import { jsonSchema } from "codemirror-json-schema"; + // Theme + import { oneDark } from "@codemirror/theme-one-dark"; + interface JSONOptions { schema?: any; } @@ -94,6 +99,9 @@ "&.cm-editor": { height: "100%" }, ".cm-scroller": { overflow: "auto" }, "&.cm-focused": { outline: "none" }, + ".cm-content": { + fontFamily: "var(--font-mono)", + }, ".cm-tooltip": { boxShadow: options.colorScheme == "light" ? "0 2px 5px rgba(0,0,0,0.2)" : "0 2px 5px rgba(0,0,0,1)", }, @@ -136,6 +144,9 @@ }), ); } + if (options.language == "markdown") { + languageExtensions.push(markdown()); + } const view = new EditorView({ doc: options.initialText, diff --git a/packages/viewer/tailwind.config.js b/packages/viewer/tailwind.config.js index 59588c7..548d5af 100644 --- a/packages/viewer/tailwind.config.js +++ b/packages/viewer/tailwind.config.js @@ -48,4 +48,5 @@ export default { theme: { extend: resolveREM(defaultTheme), }, + plugins: [require("@tailwindcss/typography")], };