Skip to content

Commit 9bde71c

Browse files
authored
Merge pull request galaxyproject#19775 from guerler/add_cell_editor_viz
Add visualization framework interface to cell-based markdown editor
2 parents f0c8c32 + 86d3dbd commit 9bde71c

23 files changed

Lines changed: 601 additions & 29 deletions

File tree

client/src/components/Markdown/Editor/CellAction.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<small class="my-1 mx-3 text-info">{{ title }}</small>
1414
</span>
1515
<CellOption
16-
v-if="name !== 'markdown'"
16+
v-if="['galaxy', 'visualization'].includes(name)"
1717
title="Attach Data"
1818
description="Select data for this cell"
1919
:icon="faPaperclip"

client/src/components/Markdown/Editor/CellAdd.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
<script setup lang="ts">
3131
import { faPlus } from "@fortawesome/free-solid-svg-icons";
3232
import { BAlert } from "bootstrap-vue";
33-
import { computed, ref } from "vue";
33+
import { computed, onMounted, type Ref, ref } from "vue";
3434
35+
import { getVisualizations } from "./services";
3536
import cellTemplates from "./templates.yml";
3637
import type { CellType, TemplateEntry } from "./types";
3738
@@ -46,9 +47,11 @@ defineEmits<{
4647
4748
const buttonRef = ref();
4849
const query = ref("");
50+
const visualizations: Ref<Array<TemplateEntry>> = ref([]);
4951
5052
const allTemplates = computed(() => {
5153
const result = { ...(cellTemplates as Record<string, Array<TemplateEntry>>) };
54+
result["Visualization"] = visualizations.value;
5255
return result;
5356
});
5457
@@ -67,6 +70,10 @@ const filteredTemplates = computed(() => {
6770
});
6871
return filteredCategories;
6972
});
73+
74+
onMounted(async () => {
75+
visualizations.value = await getVisualizations();
76+
});
7077
</script>
7178

7279
<style>

client/src/components/Markdown/Editor/CellCode.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const props = defineProps({
1414
type: String,
1515
default: "github_light_default",
1616
},
17+
maxLines: {
18+
type: Number,
19+
default: 99999,
20+
},
1721
mode: {
1822
type: String,
1923
default: "json",
@@ -44,7 +48,7 @@ async function buildEditor() {
4448
aceEditor = ace.edit(editor.value, {
4549
highlightActiveLine: false,
4650
highlightGutterLine: false,
47-
maxLines: 30,
51+
maxLines: props.maxLines,
4852
minLines: 1,
4953
mode: modePath,
5054
showPrintMargin: false,

client/src/components/Markdown/Editor/CellWrapper.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
:labels="labels"
3737
@cancel="$emit('configure')"
3838
@change="handleConfigure($event)" />
39+
<ConfigureVisualization
40+
v-else-if="name === 'visualization' && configure"
41+
:name="name"
42+
:content="content"
43+
:labels="labels"
44+
@cancel="$emit('configure')"
45+
@change="handleConfigure($event)" />
3946
<CellCode
4047
:key="name"
4148
class="mt-1"
@@ -60,6 +67,7 @@ import type { WorkflowLabel } from "./types";
6067
import CellAction from "./CellAction.vue";
6168
import CellButton from "./CellButton.vue";
6269
import ConfigureGalaxy from "./Configurations/ConfigureGalaxy.vue";
70+
import ConfigureVisualization from "./Configurations/ConfigureVisualization.vue";
6371
import SectionWrapper from "@/components/Markdown/Sections/SectionWrapper.vue";
6472
6573
const CellCode = () => import("./CellCode.vue");
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<template>
2+
<b-alert v-if="errorMessage" variant="warning" show>{{ errorMessage }}</b-alert>
3+
<MarkdownSelector v-else-if="labels !== undefined" argument-name="Dataset" :labels="labels" @onOk="onLabel" />
4+
<DataDialog
5+
v-else-if="currentHistoryId !== null"
6+
:history="currentHistoryId"
7+
format="id"
8+
@onOk="onData"
9+
@onCancel="$emit('cancel')" />
10+
<b-alert v-else v-localize variant="info" show> No history available to choose from. </b-alert>
11+
</template>
12+
13+
<script setup lang="ts">
14+
import { storeToRefs } from "pinia";
15+
import { type Ref, ref, watch } from "vue";
16+
17+
import type { DatasetLabel, WorkflowLabel } from "@/components/Markdown/Editor/types";
18+
import { stringify } from "@/components/Markdown/Utilities/stringify";
19+
import { useHistoryStore } from "@/stores/historyStore";
20+
21+
import DataDialog from "@/components/DataDialog/DataDialog.vue";
22+
import MarkdownSelector from "@/components/Markdown/MarkdownSelector.vue";
23+
24+
const { currentHistoryId } = storeToRefs(useHistoryStore());
25+
26+
const props = defineProps<{
27+
content: string;
28+
labels?: Array<WorkflowLabel>;
29+
}>();
30+
31+
const emit = defineEmits<{
32+
(e: "cancel"): void;
33+
(e: "change", content: string): void;
34+
}>();
35+
36+
interface contentType {
37+
dataset_id?: string;
38+
dataset_label?: DatasetLabel;
39+
dataset_url?: string;
40+
}
41+
42+
const contentObject: Ref<contentType | undefined> = ref();
43+
const errorMessage = ref("");
44+
45+
function onData(datasetId: any) {
46+
if (contentObject.value) {
47+
contentObject.value.dataset_id = datasetId;
48+
contentObject.value.dataset_label = undefined;
49+
contentObject.value.dataset_url = undefined;
50+
emit("change", stringify(contentObject.value));
51+
}
52+
}
53+
54+
function onLabel(selectedLabel: any) {
55+
if (selectedLabel !== undefined) {
56+
const labelType = selectedLabel.type;
57+
const label = selectedLabel.label;
58+
if (contentObject.value && label && labelType) {
59+
contentObject.value.dataset_label = {
60+
invocation_id: "",
61+
[labelType]: label,
62+
};
63+
contentObject.value.dataset_id = undefined;
64+
contentObject.value.dataset_url = undefined;
65+
emit("change", stringify(contentObject.value));
66+
}
67+
}
68+
}
69+
70+
function parseContent() {
71+
try {
72+
contentObject.value = JSON.parse(props.content);
73+
errorMessage.value = "";
74+
} catch (e) {
75+
errorMessage.value = `Failed to parse: ${e}`;
76+
}
77+
}
78+
79+
watch(
80+
() => props.content,
81+
() => parseContent(),
82+
{ immediate: true }
83+
);
84+
</script>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import axios from "axios";
2+
3+
import { getAppRoot } from "@/onload";
4+
import { rethrowSimple } from "@/utils/simple-error";
5+
6+
import type { TemplateEntry } from "./types";
7+
8+
export interface VisualizationType {
9+
description: string;
10+
html: string;
11+
logo?: string;
12+
name: string;
13+
}
14+
15+
export async function getVisualizations(): Promise<Array<TemplateEntry>> {
16+
try {
17+
const { data } = await axios.get(`${getAppRoot()}api/plugins?embeddable=True`);
18+
return data.map((v: VisualizationType) => ({
19+
title: v.html,
20+
description: v.description || "",
21+
logo: v.logo ? `${getAppRoot()}${v.logo}` : undefined,
22+
cell: {
23+
name: "visualization",
24+
configure: true,
25+
content: `{ "visualization_name": "${v.name}", "visualization_title": "${v.html}" }`,
26+
},
27+
}));
28+
} catch (e) {
29+
rethrowSimple("Failed to load Visualizations.");
30+
}
31+
}

client/src/components/Markdown/Editor/templates.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,68 @@ Galaxy:
194194
cell:
195195
name: "galaxy"
196196
content: "generate_galaxy_version()"
197+
198+
Vega:
199+
- title: "Bar Diagram"
200+
description: "Basic bar diagram"
201+
cell:
202+
name: "vega"
203+
content: |
204+
{
205+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
206+
"description": "A simple bar chart with embedded data.",
207+
"data": {"values": [
208+
{ "a": "A", "b": 1 },
209+
{ "a": "B", "b": 2 },
210+
{ "a": "C", "b": 3 }
211+
]},
212+
"mark": "bar",
213+
"encoding": {
214+
"x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}},
215+
"y": {"field": "b", "type": "quantitative"}
216+
}
217+
}
218+
- title: "Line Chart"
219+
description: "Basic line chart"
220+
cell:
221+
name: "vega"
222+
content: |
223+
{
224+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
225+
"description": "A simple line chart with embedded data.",
226+
"data": {"values": [
227+
{ "a": "A", "b": 1 },
228+
{ "a": "B", "b": 2 },
229+
{ "a": "C", "b": 3 }
230+
]},
231+
"mark": "line",
232+
"encoding": {
233+
"x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}},
234+
"y": {"field": "b", "type": "quantitative"}
235+
}
236+
}
237+
238+
Vitessce:
239+
- title: "Hello World"
240+
description: "A simple vitessce example"
241+
cell:
242+
name: "vitessce"
243+
content: |
244+
{
245+
"version": "1.0.16",
246+
"name": "Example configuration",
247+
"description": "",
248+
"datasets": [],
249+
"initStrategy": "auto",
250+
"coordinationSpace": {},
251+
"layout": [{
252+
"component": "description",
253+
"props": {
254+
"description": "Hello, world!"
255+
},
256+
"x": 0,
257+
"y": 0,
258+
"w": 6,
259+
"h": 6
260+
}]
261+
}

client/src/components/Markdown/MarkdownEditor.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
</div>
99
<div>
1010
<b-form-radio-group
11-
v-if="!labels || labels.length === 0"
1211
v-model="editor"
1312
v-b-tooltip.hover.bottom
1413
button-variant="outline-primary"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script setup lang="ts">
2+
import { ref, watch } from "vue";
3+
4+
const VegaWrapper = () => import("@/components/Common/VegaWrapper.vue");
5+
6+
const props = defineProps<{
7+
content: string;
8+
}>();
9+
10+
const errorMessage = ref("");
11+
const spec = ref({});
12+
13+
function render() {
14+
try {
15+
errorMessage.value = "";
16+
spec.value = {
17+
...JSON.parse(props.content),
18+
width: "container",
19+
};
20+
} catch (e: any) {
21+
errorMessage.value = String(e);
22+
spec.value = {};
23+
}
24+
}
25+
26+
watch(
27+
() => props.content,
28+
() => {
29+
render();
30+
},
31+
{ immediate: true }
32+
);
33+
</script>
34+
35+
<template>
36+
<div>
37+
<b-alert v-if="errorMessage" class="p-2" variant="danger" show>
38+
{{ errorMessage }}
39+
</b-alert>
40+
<VegaWrapper :spec="spec" />
41+
</div>
42+
</template>

0 commit comments

Comments
 (0)