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.
- package/.github/workflows/main.yml +64 -0
- package/.vscode/extensions.json +3 -0
- package/docs/.vitepress/cache/deps/_metadata.json +40 -0
- package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js +36 -0
- package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js +12492 -0
- package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js.map +7 -0
- package/docs/.vitepress/cache/deps/naive-ui.js +107113 -0
- package/docs/.vitepress/cache/deps/naive-ui.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4494 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +9345 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +344 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/config.mts +55 -0
- package/docs/.vitepress/theme/index.js +7 -0
- package/docs/.vitepress/theme/tailwind.css +7 -0
- package/docs/content/configuration.md +45 -0
- package/docs/content/deploy-plugin.md +22 -0
- package/docs/content/deploy-request.md +65 -0
- package/docs/content/installation.md +42 -0
- package/docs/content/introduction.md +45 -0
- package/docs/content/vue-introduction.md +38 -0
- package/docs/content/vue-utilities.md +70 -0
- package/docs/content/xml-datasources.md +34 -0
- package/docs/content/xml-framework.md +140 -0
- package/docs/content/xml-inputs.md +248 -0
- package/docs/content/xml-introduction.md +23 -0
- package/docs/content/xml-sections.md +50 -0
- package/docs/index.md +27 -0
- package/docs/package-lock.json +4203 -0
- package/docs/package.json +21 -0
- package/docs/postcss.config.js +6 -0
- package/docs/public/eslint.svg +7 -0
- package/docs/public/galaxy-charts-demo.gif +0 -0
- package/docs/public/galaxy-charts-starter.jpg +0 -0
- package/docs/public/galaxy-charts.svg +7 -0
- package/docs/public/javascript.svg +8 -0
- package/docs/public/naive-ui.svg +9 -0
- package/docs/public/next-js.svg +25 -0
- package/docs/public/nuxt.svg +3 -0
- package/docs/public/prettier.svg +46 -0
- package/docs/public/react.svg +1 -0
- package/docs/public/tailwind.svg +12 -0
- package/docs/public/typescript.svg +8 -0
- package/docs/public/vite.svg +15 -0
- package/docs/public/vitest.svg +9 -0
- package/docs/public/vue.svg +8 -0
- package/docs/public/vuetify.svg +9 -0
- package/docs/tailwind.config.js +9 -0
- package/index.html +13 -0
- package/lib/galaxy-charts.js +7 -0
- package/package.json +8 -10
- package/postcss.config.js +6 -0
- package/prettier.config.js +5 -0
- package/public/galaxy-charts.xml +138 -0
- package/src/App.vue +23 -0
- package/src/Plugin.vue +37 -0
- package/src/api/client.ts +46 -0
- package/src/api/datasets.ts +34 -0
- package/src/api/visualizations.ts +33 -0
- package/src/components/AlertNotify.vue +35 -0
- package/src/components/ApiStatus.vue +39 -0
- package/src/components/GalaxyCharts.vue +136 -0
- package/src/components/InputConditional.vue +109 -0
- package/src/components/InputData.vue +74 -0
- package/src/components/InputDataColumn.vue +54 -0
- package/src/components/InputForm.vue +119 -0
- package/src/components/InputRepeats.vue +70 -0
- package/src/components/SidePanel.vue +158 -0
- package/src/main.js +27 -0
- package/src/store/columnsStore.ts +59 -0
- package/src/store/configStore.ts +29 -0
- package/src/style.css +3 -0
- package/src/utilities/getFileName.test.js +8 -0
- package/src/utilities/getFileName.ts +4 -0
- package/src/utilities/parseColumns.ts +34 -0
- package/src/utilities/parseDefaults.test.js +13 -0
- package/src/utilities/parseDefaults.ts +17 -0
- package/src/utilities/parseIncoming.ts +32 -0
- package/src/utilities/parsePlugin.ts +95 -0
- package/src/utilities/parseXML.ts +156 -0
- package/src/utilities/simpleError.ts +21 -0
- package/src/utilities/toBoolean.test.js +12 -0
- package/src/utilities/toBoolean.ts +5 -0
- package/tailwind.config.js +8 -0
- package/tsconfig.json +28 -0
- package/vite.config.js +70 -0
- package/dist/galaxy-charts.css +0 -1
- package/dist/galaxy-charts.js +0 -21277
- package/dist/galaxy-charts.umd.cjs +0 -2073
- /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>
|