galaxy-charts 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.github/workflows/main.yml +64 -0
  2. package/.vscode/extensions.json +3 -0
  3. package/docs/.vitepress/cache/deps/_metadata.json +40 -0
  4. package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js +36 -0
  5. package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js.map +7 -0
  6. package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js +12492 -0
  7. package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js.map +7 -0
  8. package/docs/.vitepress/cache/deps/naive-ui.js +107113 -0
  9. package/docs/.vitepress/cache/deps/naive-ui.js.map +7 -0
  10. package/docs/.vitepress/cache/deps/package.json +3 -0
  11. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4494 -0
  12. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  13. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +9345 -0
  14. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  15. package/docs/.vitepress/cache/deps/vue.js +344 -0
  16. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  17. package/docs/.vitepress/config.mts +55 -0
  18. package/docs/.vitepress/theme/index.js +7 -0
  19. package/docs/.vitepress/theme/tailwind.css +7 -0
  20. package/docs/content/configuration.md +45 -0
  21. package/docs/content/deploy-plugin.md +22 -0
  22. package/docs/content/deploy-request.md +65 -0
  23. package/docs/content/installation.md +42 -0
  24. package/docs/content/introduction.md +45 -0
  25. package/docs/content/vue-introduction.md +38 -0
  26. package/docs/content/vue-utilities.md +70 -0
  27. package/docs/content/xml-datasources.md +34 -0
  28. package/docs/content/xml-framework.md +140 -0
  29. package/docs/content/xml-inputs.md +248 -0
  30. package/docs/content/xml-introduction.md +23 -0
  31. package/docs/content/xml-sections.md +50 -0
  32. package/docs/index.md +27 -0
  33. package/docs/package-lock.json +4203 -0
  34. package/docs/package.json +21 -0
  35. package/docs/postcss.config.js +6 -0
  36. package/docs/public/eslint.svg +7 -0
  37. package/docs/public/galaxy-charts-demo.gif +0 -0
  38. package/docs/public/galaxy-charts-starter.jpg +0 -0
  39. package/docs/public/galaxy-charts.svg +7 -0
  40. package/docs/public/javascript.svg +8 -0
  41. package/docs/public/naive-ui.svg +9 -0
  42. package/docs/public/next-js.svg +25 -0
  43. package/docs/public/nuxt.svg +3 -0
  44. package/docs/public/prettier.svg +46 -0
  45. package/docs/public/react.svg +1 -0
  46. package/docs/public/tailwind.svg +12 -0
  47. package/docs/public/typescript.svg +8 -0
  48. package/docs/public/vite.svg +15 -0
  49. package/docs/public/vitest.svg +9 -0
  50. package/docs/public/vue.svg +8 -0
  51. package/docs/public/vuetify.svg +9 -0
  52. package/docs/tailwind.config.js +9 -0
  53. package/index.html +13 -0
  54. package/lib/galaxy-charts.js +7 -0
  55. package/package.json +8 -10
  56. package/postcss.config.js +6 -0
  57. package/prettier.config.js +5 -0
  58. package/public/galaxy-charts.xml +138 -0
  59. package/src/App.vue +23 -0
  60. package/src/Plugin.vue +37 -0
  61. package/src/api/client.ts +46 -0
  62. package/src/api/datasets.ts +34 -0
  63. package/src/api/visualizations.ts +33 -0
  64. package/src/components/AlertNotify.vue +35 -0
  65. package/src/components/ApiStatus.vue +39 -0
  66. package/src/components/GalaxyCharts.vue +136 -0
  67. package/src/components/InputConditional.vue +109 -0
  68. package/src/components/InputData.vue +74 -0
  69. package/src/components/InputDataColumn.vue +54 -0
  70. package/src/components/InputForm.vue +119 -0
  71. package/src/components/InputRepeats.vue +70 -0
  72. package/src/components/SidePanel.vue +158 -0
  73. package/src/main.js +27 -0
  74. package/src/store/columnsStore.ts +59 -0
  75. package/src/store/configStore.ts +29 -0
  76. package/src/style.css +3 -0
  77. package/src/utilities/getFileName.test.js +8 -0
  78. package/src/utilities/getFileName.ts +4 -0
  79. package/src/utilities/parseColumns.ts +34 -0
  80. package/src/utilities/parseDefaults.test.js +13 -0
  81. package/src/utilities/parseDefaults.ts +17 -0
  82. package/src/utilities/parseIncoming.ts +32 -0
  83. package/src/utilities/parsePlugin.ts +95 -0
  84. package/src/utilities/parseXML.ts +156 -0
  85. package/src/utilities/simpleError.ts +21 -0
  86. package/src/utilities/toBoolean.test.js +12 -0
  87. package/src/utilities/toBoolean.ts +5 -0
  88. package/tailwind.config.js +8 -0
  89. package/tsconfig.json +28 -0
  90. package/vite.config.js +70 -0
  91. package/dist/galaxy-charts.css +0 -1
  92. package/dist/galaxy-charts.js +0 -21277
  93. package/dist/galaxy-charts.umd.cjs +0 -2073
  94. /package/{dist/types.d.ts → src/types.ts} +0 -0
@@ -0,0 +1,34 @@
1
+ import { GalaxyApi } from "@/api/client";
2
+
3
+ export async function datasetsGetColumns(datasetId: string, columnList: string[]): Promise<any[][] | undefined> {
4
+ try {
5
+ const params = new URLSearchParams({
6
+ data_type: "raw_data",
7
+ provider: "dataset-column",
8
+ indeces: columnList.toString(),
9
+ }).toString();
10
+
11
+ const { data } = await GalaxyApi().GET(`/api/datasets/${datasetId}?${params}`);
12
+ const columnLength = columnList.length;
13
+ const results: any[][] = new Array(columnLength).fill(null).map(() => []);
14
+
15
+ for (const row of data) {
16
+ for (const j in row) {
17
+ const index = Number(j);
18
+ const value = row[j];
19
+ if (value !== undefined && value != 2147483647 && index < columnLength) {
20
+ results[index].push(value);
21
+ }
22
+ }
23
+ }
24
+
25
+ return results;
26
+ } catch (err) {
27
+ console.error(err);
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ export function datasetsGetUrl(datasetId: string): string {
33
+ return `/api/datasets/${datasetId}/display`;
34
+ }
@@ -0,0 +1,33 @@
1
+ import { rethrowSimple } from "@/utilities/simpleError";
2
+ import { GalaxyApi } from "@/api/client";
3
+ import { InputValuesType } from "@/types"
4
+
5
+ interface VisualizationConfig {
6
+ dataset_id: string;
7
+ settings: InputValuesType;
8
+ }
9
+
10
+ export async function visualizationsCreate(type: string, title: string, config: VisualizationConfig): Promise<string | undefined> {
11
+ try {
12
+ const { data } = await GalaxyApi().POST("/api/visualizations", {
13
+ type,
14
+ title,
15
+ config,
16
+ });
17
+ return data.id;
18
+ } catch (err) {
19
+ rethrowSimple(err);
20
+ }
21
+ }
22
+
23
+ export async function visualizationsSave(id: string, title: string, config: VisualizationConfig): Promise<string | undefined> {
24
+ try {
25
+ const { data } = await GalaxyApi().PUT(`/api/visualizations/${id}`, {
26
+ title,
27
+ config,
28
+ });
29
+ return data.id;
30
+ } catch (err) {
31
+ rethrowSimple(err);
32
+ }
33
+ }
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import { watch } from "vue";
3
+ import { NAlert } from "naive-ui";
4
+ import { MessageType } from "@/types";
5
+
6
+ const MESSAGE_TIMEOUT = 2000;
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ message: string;
11
+ messageType?: MessageType;
12
+ }>(),
13
+ {
14
+ messageType: "info",
15
+ },
16
+ );
17
+
18
+ const emit = defineEmits(["timeout"]);
19
+
20
+ // Watch and clear messages
21
+ let timeoutMessage: ReturnType<typeof setTimeout> | null = null;
22
+ watch(
23
+ () => props.message,
24
+ () => {
25
+ timeoutMessage && clearTimeout(timeoutMessage);
26
+ timeoutMessage = setTimeout(() => emit("timeout"), MESSAGE_TIMEOUT);
27
+ },
28
+ );
29
+ </script>
30
+
31
+ <template>
32
+ <n-alert v-if="message" :type="messageType" class="m-4">
33
+ {{ message }}
34
+ </n-alert>
35
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import { CheckCircleIcon, ExclamationCircleIcon } from "@heroicons/vue/24/outline";
4
+ import { NIcon, NTooltip } from "naive-ui";
5
+ import { GalaxyApi } from "@/api/client";
6
+
7
+ const version = ref("...");
8
+
9
+ async function checkVersion() {
10
+ try {
11
+ const { data } = await GalaxyApi().GET("/api/version");
12
+ version.value = data.version_major;
13
+ } catch (err) {
14
+ console.log(`Unable to connect to Galaxy. Verify Galaxy is running and refer to docs.`);
15
+ version.value = "";
16
+ }
17
+ }
18
+
19
+ checkVersion();
20
+ </script>
21
+
22
+ <template>
23
+ <n-tooltip v-if="version" class="mx-1" trigger="hover">
24
+ <template #trigger>
25
+ <n-icon class="mx-1">
26
+ <CheckCircleIcon class="text-green-600" />
27
+ </n-icon>
28
+ </template>
29
+ <span class="text-xs">Connected to Galaxy Version {{ version }}.</span>
30
+ </n-tooltip>
31
+ <n-tooltip v-else class="mx-1" trigger="hover">
32
+ <template #trigger>
33
+ <n-icon class="mx-1">
34
+ <ExclamationCircleIcon class="text-red-600" />
35
+ </n-icon>
36
+ </template>
37
+ <span class="text-xs">Galaxy is not accessible!</span>
38
+ </n-tooltip>
39
+ </template>
@@ -0,0 +1,136 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, nextTick, defineProps } from "vue";
3
+ import { ArrowPathIcon, ChevronDoubleLeftIcon } from "@heroicons/vue/24/outline";
4
+ import SidePanel from "@/components/SidePanel.vue";
5
+ import { parsePlugin } from "@/utilities/parsePlugin";
6
+ import { NAlert, NFloatButton, NIcon } from "naive-ui";
7
+ import { datasetsGetUrl } from "@/api/datasets";
8
+ import { parseIncoming } from "@/utilities/parseIncoming";
9
+ import { useConfigStore } from "@/store/configStore";
10
+ import { InputElementType, InputValuesType, PluginConfigType } from "@/types";
11
+
12
+ const props = defineProps<{
13
+ credentials?: RequestCredentials;
14
+ xml: string;
15
+ }>();
16
+
17
+ // References with reactive types
18
+ const collapsePanel = ref<boolean>(false);
19
+ const datasetUrl = ref<string>("");
20
+ const description = ref<string>("");
21
+ const errorMessage = ref<string>("");
22
+ const html = ref<string>("");
23
+ const isLoading = ref<boolean>(true);
24
+ const logo = ref<string>("");
25
+ const name = ref<string>("");
26
+ const settingInputs = ref<Array<InputElementType>>([]);
27
+ const settingValues = ref<InputValuesType>({});
28
+ const specValues = ref<InputValuesType>({});
29
+ const trackInputs = ref<Array<InputElementType>>([]);
30
+ const trackValues = ref<Array<InputValuesType>>([]);
31
+
32
+ // Parse incoming visualization details
33
+ const { root, visualizationConfig, visualizationId, visualizationPlugin, visualizationTitle } = parseIncoming();
34
+
35
+ // Store values in config store
36
+ const configStore = useConfigStore();
37
+ configStore.setCredentials(props.credentials || "include");
38
+ configStore.setRoot(root || "/");
39
+
40
+ // Collect plugin details and parse incoming settings
41
+ parsePlugin(props.xml, visualizationPlugin, visualizationConfig).then(({ plugin, settings, specs, tracks }) => {
42
+ description.value = plugin.description || "";
43
+ html.value = plugin.html || "";
44
+ isLoading.value = false;
45
+ logo.value = plugin.logo || "";
46
+ name.value = plugin.name;
47
+ settingInputs.value = plugin.settings || [];
48
+ settingValues.value = settings;
49
+ specValues.value = specs || {};
50
+ trackInputs.value = plugin.tracks || [];
51
+ trackValues.value = tracks;
52
+ });
53
+
54
+ // Get visualization dataset ID (required)
55
+ const datasetId = visualizationConfig.dataset_id || "";
56
+ if (visualizationConfig.dataset_url) {
57
+ datasetUrl.value = visualizationConfig.dataset_url;
58
+ console.debug(`GalaxyCharts: Evaluating dataset url: ${datasetUrl.value}.`);
59
+ } else {
60
+ if (!datasetId) {
61
+ errorMessage.value = "Visualization requires `dataset_id` or `dataset_url`.";
62
+ } else {
63
+ datasetUrl.value = datasetsGetUrl(datasetId);
64
+ console.debug(`GalaxyCharts: Built dataset url from dataset id: ${datasetUrl.value}.`);
65
+ }
66
+ }
67
+
68
+ // Determine logo URL
69
+ const logoUrl = computed(() => logo.value && `${root}${logo.value}`);
70
+
71
+ // Hide panel condition
72
+ const hidePanel = computed(() => settingInputs.value.length === 0 && trackInputs.value.length === 0);
73
+
74
+ // Toggle side panel visibility
75
+ async function onToggle(): Promise<void> {
76
+ collapsePanel.value = !collapsePanel.value;
77
+ await nextTick();
78
+ window.dispatchEvent(new Event("resize"));
79
+ }
80
+
81
+ // Event handler for updating settings
82
+ function updateSettings(newSettings: InputValuesType): void {
83
+ settingValues.value = { ...newSettings };
84
+ }
85
+
86
+ // Event handler for updating tracks
87
+ function updateTracks(newTracks: Array<InputValuesType>): void {
88
+ trackValues.value = [...newTracks];
89
+ }
90
+ </script>
91
+
92
+ <template>
93
+ <n-alert v-if="errorMessage" title="Visualization Error!" type="error" class="m-2">
94
+ {{ errorMessage }}
95
+ </n-alert>
96
+ <div v-else-if="isLoading" class="m-2">
97
+ <span>
98
+ <ArrowPathIcon class="animate-spin size-4 inline mx-1" />
99
+ </span>
100
+ <span class="text-xs">Please wait...</span>
101
+ </div>
102
+ <div v-else class="grid h-screen" :class="{ 'grid-cols-[70%_30%]': !collapsePanel && !hidePanel }">
103
+ <slot
104
+ :dataset-id="datasetId"
105
+ :dataset-url="datasetUrl"
106
+ :root="root"
107
+ :settings="settingValues"
108
+ :specs="specValues"
109
+ :tracks="trackValues" />
110
+ <div v-if="collapsePanel && !hidePanel">
111
+ <n-float-button strong secondary circle class="bg-sky-100 m-2" :top="0" :right="0" @click="onToggle">
112
+ <n-icon><ChevronDoubleLeftIcon /></n-icon>
113
+ </n-float-button>
114
+ </div>
115
+ <SidePanel
116
+ v-else
117
+ :dataset-id="datasetId"
118
+ :description="description"
119
+ :html="html"
120
+ :logo-url="logoUrl"
121
+ :name="name"
122
+ :setting-inputs="settingInputs"
123
+ :setting-values="settingValues"
124
+ :track-inputs="trackInputs"
125
+ :track-values="trackValues"
126
+ :visualization-id="visualizationId"
127
+ :visualization-title="visualizationTitle"
128
+ @update:settings="updateSettings"
129
+ @update:tracks="updateTracks"
130
+ @toggle="onToggle" />
131
+ </div>
132
+ </template>
133
+
134
+ <style>
135
+ @import "@/style.css";
136
+ </style>
@@ -0,0 +1,109 @@
1
+ <script setup lang="ts">
2
+ import { NSelect, NSwitch } from "naive-ui";
3
+ import InputForm from "@/components/InputForm.vue";
4
+ import { computed, ref, watch, defineProps, defineEmits, defineModel } from "vue";
5
+ import { parseDefaults } from "@/utilities/parseDefaults";
6
+ import { InputElementType, InputValuesType } from "@/types";
7
+
8
+ // Define props with types
9
+ const props = defineProps<{
10
+ datasetId: string;
11
+ input: InputElementType;
12
+ }>();
13
+
14
+ // Define emit with event typing
15
+ const emit = defineEmits<{
16
+ (event: "update:value", updatedValues: InputValuesType): void;
17
+ }>();
18
+
19
+ if (!props.input.test_param) {
20
+ throw `The conditional '${props.input.name}' is missing a test parameter.`;
21
+ }
22
+
23
+ if (!props.input.cases || props.input.cases.length === 0) {
24
+ throw `The conditional '${props.input.name}' is missing a cases.`;
25
+ }
26
+
27
+ // Get test parameter
28
+ const testParam = ref(props.input.test_param);
29
+
30
+ // Get test parameter name and validate it
31
+ const testName = testParam.value.name;
32
+ if (!testName) {
33
+ console.error(`Test parameter has no name: ${props.input.name}.`);
34
+ }
35
+
36
+ // Reference the model value of the conditional component
37
+ const currentValue = defineModel<InputValuesType>("value");
38
+ if (!currentValue.value || !(testName in currentValue.value)) {
39
+ console.error(`Test parameter of conditional not available: ${props.input.name}.`, currentValue.value);
40
+ }
41
+
42
+ // Reference current test parameter value
43
+ const currentTestValue = ref<string>(currentValue.value && currentValue.value[testName]);
44
+
45
+ // Collect input cases and identify defaults
46
+ const caseDefaults = computed(() => {
47
+ const result: Record<string, InputValuesType> = {};
48
+ if (props.input.cases && props.input.cases.length > 0) {
49
+ for (const inputCase of props.input.cases) {
50
+ result[inputCase.value] = parseDefaults(inputCase.inputs);
51
+ result[inputCase.value][testName] = inputCase.value;
52
+ }
53
+ }
54
+ return result;
55
+ });
56
+
57
+ // Collect all input cases
58
+ const caseInputs = computed(() => {
59
+ const result: Record<string, Array<InputElementType>> = {};
60
+ if (props.input.cases && props.input.cases.length > 0) {
61
+ for (const inputCase of props.input.cases) {
62
+ result[inputCase.value] = inputCase.inputs;
63
+ }
64
+ }
65
+ return result;
66
+ });
67
+
68
+ // Current inputs based on selected test value
69
+ const currentInputs = computed(() => caseInputs.value[currentTestValue.value]);
70
+
71
+ // Handle conversion of boolean switch values to string for case evaluation
72
+ const switchTestValue = computed({
73
+ get() {
74
+ return currentTestValue.value === "true";
75
+ },
76
+ set(newVal: boolean) {
77
+ currentTestValue.value = String(newVal);
78
+ },
79
+ });
80
+
81
+ // Update values if test value changes or conditional input elements are modified
82
+ function onUpdate(newValues?: InputValuesType) {
83
+ let updatedValues = { ...caseDefaults.value[currentTestValue.value] };
84
+ if (newValues) {
85
+ const filteredValues: InputValuesType = {};
86
+ currentInputs.value.forEach((x) => {
87
+ filteredValues[x.name] = newValues[x.name];
88
+ });
89
+ updatedValues = { ...updatedValues, ...newValues };
90
+ }
91
+ emit("update:value", updatedValues);
92
+ }
93
+
94
+ // Load defaults if test value changes
95
+ watch(
96
+ () => currentTestValue.value,
97
+ () => {
98
+ onUpdate();
99
+ },
100
+ );
101
+ </script>
102
+
103
+ <template>
104
+ <n-switch v-if="testParam.type === 'boolean'" v-model:value="switchTestValue" />
105
+ <n-select v-else v-model:value="currentTestValue" :options="testParam.data" />
106
+ <div v-if="currentInputs" class="border border-dotted border-green-600 rounded mt-2 p-2">
107
+ <InputForm :dataset-id="datasetId" :inputs="currentInputs" :values="currentValue" @update:values="onUpdate" />
108
+ </div>
109
+ </template>
@@ -0,0 +1,74 @@
1
+ <script setup lang="ts">
2
+ import { ref, defineProps, defineModel } from "vue";
3
+ import { NSelect, NIcon } from "naive-ui";
4
+ import { GalaxyApi } from "@/api/client";
5
+ import { CheckCircleIcon, ExclamationCircleIcon } from "@heroicons/vue/24/outline";
6
+
7
+ const LIMIT = 100;
8
+
9
+ // Define props with TypeScript
10
+ const props = defineProps<{
11
+ extension?: string;
12
+ optional: boolean;
13
+ }>();
14
+
15
+ // Define the model
16
+ const currentOptions = ref<Array<{ label: string; value: any; disabled?: boolean }>>([]);
17
+ const currentValue = defineModel<{ name: string } | null>("value");
18
+ const isLoading = ref(false);
19
+ const selectValue = ref<any | null>(null);
20
+
21
+ // Load datasets based on filters and query
22
+ async function loadDatasets(query?: string): Promise<void> {
23
+ isLoading.value = true;
24
+ try {
25
+ const extensionFilter = props.extension ? `q=extension-eq&qv=${props.extension}` : "";
26
+ const nameFilter = query ? `q=name-contains&qv=${query}` : "";
27
+ const { data } = await GalaxyApi().GET(`/api/datasets?limit=${LIMIT}&${extensionFilter}&${nameFilter}`);
28
+
29
+ if (data && data.length > 0) {
30
+ const options = data.map((x: { name: string }) => ({
31
+ label: x.name,
32
+ value: x,
33
+ }));
34
+ options.push({ label: "...filter for more", value: null, disabled: true });
35
+ if (props.optional) {
36
+ options.unshift({ label: "-- Clear Selection --", value: null });
37
+ }
38
+ currentOptions.value = options;
39
+ }
40
+ } catch (err) {
41
+ console.log(err);
42
+ } finally {
43
+ isLoading.value = false;
44
+ }
45
+ }
46
+
47
+ // Update the current selection value
48
+ function onUpdate(): void {
49
+ currentValue.value = selectValue.value;
50
+ selectValue.value = null;
51
+ }
52
+
53
+ // Load initial datasets when the component is mounted
54
+ loadDatasets();
55
+ </script>
56
+
57
+ <template>
58
+ <div v-if="currentValue" class="mb-1">
59
+ <n-icon class="size-3 mr-1"><CheckCircleIcon /></n-icon>
60
+ <span>Selected: {{ currentValue.name }}</span>
61
+ </div>
62
+ <div v-else-if="!optional" class="text-red-600 mb-1">
63
+ <n-icon class="size-3 mr-1"><ExclamationCircleIcon /></n-icon>
64
+ <span>Please select a dataset.</span>
65
+ </div>
66
+ <n-select
67
+ v-model:value="selectValue"
68
+ filterable
69
+ placeholder="Select a Dataset"
70
+ :loading="isLoading"
71
+ :options="currentOptions"
72
+ @search="loadDatasets"
73
+ @update:value="onUpdate" />
74
+ </template>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, defineProps, defineModel } from "vue";
3
+ import { NSelect } from "naive-ui";
4
+ import { parseColumns } from "@/utilities/parseColumns";
5
+ import { GalaxyApi } from "@/api/client";
6
+
7
+ // Define props with TypeScript
8
+ const props = defineProps<{
9
+ datasetId: string;
10
+ isAuto: boolean;
11
+ isText: boolean;
12
+ isNumber: boolean;
13
+ }>();
14
+
15
+ // Define refs with appropriate types
16
+ const currentOptions = ref<Array<{ label: string; value: string }>>([]);
17
+ const currentValue = defineModel<string | null>("value");
18
+
19
+ // Load columns based on dataset and filters
20
+ async function loadColumns(): Promise<void> {
21
+ if (props.datasetId) {
22
+ try {
23
+ const { data: dataset } = await GalaxyApi().GET(`/api/datasets/${props.datasetId}`);
24
+ const columns = parseColumns(dataset, props.isAuto, props.isText, props.isNumber);
25
+ currentOptions.value = columns;
26
+ initializeValue();
27
+ } catch (err) {
28
+ console.log(err);
29
+ }
30
+ } else {
31
+ console.log("Data column input disabled, since `datasetId` is unavailable.");
32
+ }
33
+ }
34
+
35
+ // Initialize current value based on options
36
+ function initializeValue(): void {
37
+ if (currentOptions.value.length > 0 && currentValue.value === null) {
38
+ currentValue.value = currentOptions.value[0].value;
39
+ }
40
+ }
41
+
42
+ // Call loadColumns on component mount
43
+ loadColumns();
44
+
45
+ // Watch for changes in currentValue and re-initialize if needed
46
+ watch(
47
+ () => currentValue.value,
48
+ () => initializeValue(),
49
+ );
50
+ </script>
51
+
52
+ <template>
53
+ <n-select v-model:value="currentValue" :options="currentOptions" />
54
+ </template>
@@ -0,0 +1,119 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, defineProps, defineEmits } from "vue";
3
+ import { NColorPicker, NInput, NInputNumber, NSelect, NSlider, NSwitch } from "naive-ui";
4
+ import InputConditional from "@/components/InputConditional.vue";
5
+ import InputData from "@/components/InputData.vue";
6
+ import InputDataColumn from "@/components/InputDataColumn.vue";
7
+ import { toBoolean } from "@/utilities/toBoolean";
8
+ import type { InputElementType, InputValuesType } from "@/types";
9
+
10
+ const NUMBER_STEP_SIZE = 0.01;
11
+
12
+ const props = defineProps<{
13
+ datasetId: string;
14
+ inputs: InputElementType[];
15
+ values?: InputValuesType;
16
+ }>();
17
+
18
+ // Define emit
19
+ const emit = defineEmits<{
20
+ (event: "update:values", values: InputValuesType): void;
21
+ }>();
22
+
23
+ // Create a local copy of the values prop
24
+ const currentValues = ref<InputValuesType>(initialValues());
25
+
26
+ // Initialize all values to ensure reactivity
27
+ function initialValues(): InputValuesType {
28
+ const values = { ...props.values };
29
+ props.inputs.forEach((input) => {
30
+ if (values[input.name] === undefined) {
31
+ values[input.name] = null;
32
+ }
33
+ });
34
+ return values;
35
+ }
36
+
37
+ // Trigger an update of values
38
+ function onUpdate(): void {
39
+ emit("update:values", currentValues.value);
40
+ }
41
+
42
+ // Watch for changes in incoming values and reinitialize
43
+ watch(
44
+ () => props.values,
45
+ () => {
46
+ currentValues.value = initialValues();
47
+ },
48
+ );
49
+ </script>
50
+
51
+ <template>
52
+ <div class="overflow-auto select-none">
53
+ <div v-for="(input, inputIndex) in inputs" :key="inputIndex" class="pb-2">
54
+ <div class="font-bold pb-1">{{ input.label || input.name }}</div>
55
+ <div v-if="input.help" class="text-xs pb-1">{{ input.help }}</div>
56
+ <div>
57
+ <n-switch
58
+ v-if="input.type === 'boolean'"
59
+ v-model:value="currentValues[input.name]"
60
+ @update:value="onUpdate()" />
61
+ <n-color-picker
62
+ v-else-if="input.type === 'color'"
63
+ v-model:value="currentValues[input.name]"
64
+ :modes="['hex']"
65
+ :show-alpha="false"
66
+ @update:value="onUpdate()" />
67
+ <InputConditional
68
+ v-else-if="input.type === 'conditional'"
69
+ v-model:value="currentValues[input.name]"
70
+ :dataset-id="datasetId"
71
+ :input="input"
72
+ @update:value="onUpdate()" />
73
+ <InputData
74
+ v-else-if="input.type === 'data'"
75
+ v-model:value="currentValues[input.name]"
76
+ :extension="input.extension"
77
+ :optional="toBoolean(input.optional)"
78
+ @update:value="onUpdate()" />
79
+ <InputDataColumn
80
+ v-else-if="input.type === 'data_column'"
81
+ v-model:value="currentValues[input.name]"
82
+ :dataset-id="datasetId"
83
+ :is-auto="toBoolean(input.is_auto)"
84
+ :is-text="toBoolean(input.is_text)"
85
+ :is-number="toBoolean(input.is_number)"
86
+ @update:value="onUpdate()" />
87
+ <div v-else-if="input.type === 'float'">
88
+ <n-slider
89
+ v-if="input.min !== undefined && input.max !== undefined"
90
+ class="mb-2"
91
+ v-model:value="currentValues[input.name]"
92
+ :min="Number(input.min)"
93
+ :max="Number(input.max)"
94
+ :step="NUMBER_STEP_SIZE"
95
+ @update:value="onUpdate()" />
96
+ <n-input-number
97
+ v-model:value="currentValues[input.name]"
98
+ size="small"
99
+ :min="Number(input.min)"
100
+ :max="Number(input.max)"
101
+ :step="NUMBER_STEP_SIZE"
102
+ @update:value="onUpdate()" />
103
+ </div>
104
+ <n-select
105
+ v-else-if="input.type === 'select'"
106
+ v-model:value="currentValues[input.name]"
107
+ :options="input.data"
108
+ @update:value="onUpdate()" />
109
+ <n-input
110
+ v-else-if="input.type === 'textarea'"
111
+ v-model:value="currentValues[input.name]"
112
+ :rows="Number(input.rows)"
113
+ type="textarea"
114
+ @update:value="onUpdate()" />
115
+ <n-input v-else v-model:value="currentValues[input.name]" @update:value="onUpdate()" />
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </template>