galaxy-charts 0.0.20 → 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 +2 -8
- 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/{dist/types.d.ts → src/types.ts} +7 -8
- 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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, defineProps, defineEmits } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
AdjustmentsHorizontalIcon,
|
|
5
|
+
ChevronDoubleRightIcon,
|
|
6
|
+
CloudArrowUpIcon,
|
|
7
|
+
Square3Stack3DIcon,
|
|
8
|
+
} from "@heroicons/vue/24/outline";
|
|
9
|
+
import { NButton, NIcon, NInput, NTabs, NTabPane } from "naive-ui";
|
|
10
|
+
import { visualizationsCreate, visualizationsSave } from "@/api/visualizations";
|
|
11
|
+
import { errorMessageAsString } from "@/utilities/simpleError";
|
|
12
|
+
import InputForm from "@/components/InputForm.vue";
|
|
13
|
+
import InputRepeats from "@/components/InputRepeats.vue";
|
|
14
|
+
import AlertNotify from "@/components/AlertNotify.vue";
|
|
15
|
+
import ApiStatus from "@/components/ApiStatus.vue";
|
|
16
|
+
import type { InputElementType, InputValuesType, MessageType } from "@/types";
|
|
17
|
+
|
|
18
|
+
const props = defineProps<{
|
|
19
|
+
datasetId: string;
|
|
20
|
+
description: string;
|
|
21
|
+
logoUrl: string;
|
|
22
|
+
html: string;
|
|
23
|
+
name: string;
|
|
24
|
+
settingInputs: InputElementType[];
|
|
25
|
+
settingValues: InputValuesType;
|
|
26
|
+
trackInputs: InputElementType[];
|
|
27
|
+
trackValues: InputValuesType[];
|
|
28
|
+
visualizationId: string | null | undefined;
|
|
29
|
+
visualizationTitle: string;
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
// Emit events with TypeScript
|
|
33
|
+
const emit = defineEmits<{
|
|
34
|
+
(event: "update:tracks", newValues: InputValuesType[]): void;
|
|
35
|
+
(event: "update:settings", newValues: InputValuesType): void;
|
|
36
|
+
(event: "toggle"): void;
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
// Create local copies of props with reactivity
|
|
40
|
+
const currentTitle = ref<string>(props.visualizationTitle);
|
|
41
|
+
const currentVisualizationId = ref<string | null | undefined>(props.visualizationId);
|
|
42
|
+
|
|
43
|
+
// Manage message and message type for notifications
|
|
44
|
+
const message = ref<string>("");
|
|
45
|
+
const messageType = ref<MessageType>("info");
|
|
46
|
+
|
|
47
|
+
// Determine if tabs should be hidden based on input arrays
|
|
48
|
+
const hideTabs = computed(() => props.settingInputs.length === 0 || props.trackInputs.length === 0);
|
|
49
|
+
|
|
50
|
+
// Save or create the visualization
|
|
51
|
+
async function onSave(): Promise<void> {
|
|
52
|
+
try {
|
|
53
|
+
if (currentVisualizationId.value) {
|
|
54
|
+
await visualizationsSave(currentVisualizationId.value, currentTitle.value, {
|
|
55
|
+
dataset_id: props.datasetId,
|
|
56
|
+
settings: props.settingValues,
|
|
57
|
+
});
|
|
58
|
+
message.value = "Successfully saved.";
|
|
59
|
+
messageType.value = "success";
|
|
60
|
+
} else {
|
|
61
|
+
currentVisualizationId.value = await visualizationsCreate(props.name, currentTitle.value, {
|
|
62
|
+
dataset_id: props.datasetId,
|
|
63
|
+
settings: props.settingValues,
|
|
64
|
+
});
|
|
65
|
+
message.value = "Successfully created.";
|
|
66
|
+
messageType.value = "success";
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
message.value = errorMessageAsString(err);
|
|
70
|
+
messageType.value = "error";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Update settings handler
|
|
75
|
+
function onUpdateSettings(newValues: InputValuesType): void {
|
|
76
|
+
emit("update:settings", newValues);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Update tracks handler
|
|
80
|
+
function onUpdateTracks(newValues: InputValuesType[]): void {
|
|
81
|
+
emit("update:tracks", newValues);
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<template>
|
|
86
|
+
<div class="overflow-auto select-none bg-white z-10">
|
|
87
|
+
<div class="flex p-2">
|
|
88
|
+
<div class="flex-1 font-thin text-lg p-1 p-2">
|
|
89
|
+
<span>Charts</span>
|
|
90
|
+
<ApiStatus />
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
<n-button strong secondary circle class="bg-sky-100 m-1" @click="onSave">
|
|
94
|
+
<template #icon>
|
|
95
|
+
<n-icon><CloudArrowUpIcon /></n-icon>
|
|
96
|
+
</template>
|
|
97
|
+
</n-button>
|
|
98
|
+
<n-button strong secondary circle class="bg-sky-100 m-1" @click="emit('toggle')">
|
|
99
|
+
<template #icon>
|
|
100
|
+
<n-icon><ChevronDoubleRightIcon /></n-icon>
|
|
101
|
+
</template>
|
|
102
|
+
</n-button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
<AlertNotify :message="message" :message-type="messageType" @timeout="message = ''" class="m-4" />
|
|
106
|
+
<div class="m-4 mt-0 p-2 bg-sky-50 text-sky-900 rounded">
|
|
107
|
+
<div class="md:flex">
|
|
108
|
+
<div class="flex justify-center center-items">
|
|
109
|
+
<div class="m-2">
|
|
110
|
+
<img v-if="props.logoUrl" :src="props.logoUrl" class="min-w-14 max-w-14 object-contain" />
|
|
111
|
+
<svg
|
|
112
|
+
v-else
|
|
113
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
114
|
+
fill="none"
|
|
115
|
+
viewBox="0 0 100 100"
|
|
116
|
+
class="size-14">
|
|
117
|
+
<circle cx="50" cy="50" r="45" stroke="#E30A17" stroke-width="5" />
|
|
118
|
+
<path d="M 50,5 A 45,45 0 0,1 95,50 L 50,50 Z" fill="#E30A17" />
|
|
119
|
+
</svg>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="overflow-hidden break-words p-1">
|
|
123
|
+
<span class="font-bold">{{ html }}</span>
|
|
124
|
+
<div class="text-xs" v-html="description" />
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="px-4 pb-2">
|
|
129
|
+
<div class="font-bold">Title</div>
|
|
130
|
+
<div class="text-xs py-1">Specify a visualization title.</div>
|
|
131
|
+
<n-input v-model:value="currentTitle" />
|
|
132
|
+
</div>
|
|
133
|
+
<n-tabs type="line" animated class="px-4" :tab-class="hideTabs ? '!hidden' : ''">
|
|
134
|
+
<n-tab-pane v-if="trackInputs.length > 0" name="tracks">
|
|
135
|
+
<template #tab>
|
|
136
|
+
<n-icon><Square3Stack3DIcon /></n-icon>
|
|
137
|
+
<span class="mx-1">Tracks</span>
|
|
138
|
+
</template>
|
|
139
|
+
<InputRepeats
|
|
140
|
+
:dataset-id="datasetId"
|
|
141
|
+
:inputs="trackInputs"
|
|
142
|
+
:values-array="trackValues"
|
|
143
|
+
@update:values-array="onUpdateTracks" />
|
|
144
|
+
</n-tab-pane>
|
|
145
|
+
<n-tab-pane v-if="settingInputs.length > 0" name="settings">
|
|
146
|
+
<template #tab>
|
|
147
|
+
<n-icon><AdjustmentsHorizontalIcon /></n-icon>
|
|
148
|
+
<span class="mx-1">Settings</span>
|
|
149
|
+
</template>
|
|
150
|
+
<InputForm
|
|
151
|
+
:dataset-id="datasetId"
|
|
152
|
+
:inputs="settingInputs"
|
|
153
|
+
:values="settingValues"
|
|
154
|
+
@update:values="onUpdateSettings" />
|
|
155
|
+
</n-tab-pane>
|
|
156
|
+
</n-tabs>
|
|
157
|
+
</div>
|
|
158
|
+
</template>
|
package/src/main.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createApp, h } from "vue";
|
|
2
|
+
import App from "./App.vue";
|
|
3
|
+
|
|
4
|
+
const dataIncoming = {
|
|
5
|
+
visualization_config: {
|
|
6
|
+
dataset_url: null,
|
|
7
|
+
dataset_id: "12e4b4feedfe9f3f",
|
|
8
|
+
settings: {
|
|
9
|
+
setting_text: "My Test Setting",
|
|
10
|
+
setting_boolean: true,
|
|
11
|
+
setting_conditional: {
|
|
12
|
+
case_false: "something else",
|
|
13
|
+
test_condition: "false",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const xml = "galaxy-charts.xml";
|
|
20
|
+
|
|
21
|
+
// Attach config to the data-incoming attribute
|
|
22
|
+
const appElement = document.querySelector("#app");
|
|
23
|
+
appElement.setAttribute("data-incoming", JSON.stringify(dataIncoming));
|
|
24
|
+
|
|
25
|
+
createApp({
|
|
26
|
+
render: () => h(App, { credentials: process.env.credentials, xml: xml }),
|
|
27
|
+
}).mount("#app");
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { datasetsGetColumns } from "@/api/datasets";
|
|
3
|
+
|
|
4
|
+
const SPECIAL_KEYS = ["auto", undefined];
|
|
5
|
+
|
|
6
|
+
// Define the structure for columns
|
|
7
|
+
const columns = ref<Record<string, Record<string, any>>>({});
|
|
8
|
+
|
|
9
|
+
interface Track {
|
|
10
|
+
[key: string]: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useColumnsStore() {
|
|
14
|
+
function getColumns(tracks: Track[], keys: string[]): string[] {
|
|
15
|
+
const columnsList: string[] = [];
|
|
16
|
+
for (const track of tracks) {
|
|
17
|
+
for (const key of keys) {
|
|
18
|
+
const column = track[key];
|
|
19
|
+
if (column && ![...columnsList, ...SPECIAL_KEYS].includes(column)) {
|
|
20
|
+
columnsList.push(column);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return columnsList;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function fetchColumns(datasetId: string, tracks: Track[], keys: string[]): Promise<Record<string, any>[]> {
|
|
28
|
+
columns.value[datasetId] = columns.value[datasetId] || {};
|
|
29
|
+
const columnsAvailable = Object.keys(columns.value[datasetId]);
|
|
30
|
+
const columnsList = getColumns(tracks, keys).filter((x) => !columnsAvailable.includes(x));
|
|
31
|
+
|
|
32
|
+
if (columnsList.length > 0) {
|
|
33
|
+
const columnsData = await datasetsGetColumns(datasetId, columnsList);
|
|
34
|
+
if (columnsData) {
|
|
35
|
+
for (const [index, column] of columnsList.entries()) {
|
|
36
|
+
columns.value[datasetId][column] = columnsData[index];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const results: Record<string, any>[] = [];
|
|
42
|
+
tracks.forEach((track) => {
|
|
43
|
+
const trackEntry: Record<string, any> = {};
|
|
44
|
+
keys.forEach((key) => {
|
|
45
|
+
const column = track[key];
|
|
46
|
+
if (column && !SPECIAL_KEYS.includes(column)) {
|
|
47
|
+
trackEntry[key] = columns.value[datasetId][column];
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
results.push(trackEntry);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
fetchColumns,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ref, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
const root = ref("/");
|
|
4
|
+
const credentials: Ref<RequestCredentials> = ref("include");
|
|
5
|
+
|
|
6
|
+
export function useConfigStore() {
|
|
7
|
+
function getCredentials(): RequestCredentials {
|
|
8
|
+
return credentials.value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function setCredentials(newCredentials: RequestCredentials) {
|
|
12
|
+
credentials.value = newCredentials;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getRoot(): string {
|
|
16
|
+
return root.value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function setRoot(newRoot: string) {
|
|
20
|
+
root.value = newRoot;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
getCredentials,
|
|
25
|
+
setCredentials,
|
|
26
|
+
getRoot,
|
|
27
|
+
setRoot,
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/style.css
ADDED
|
@@ -11,18 +11,12 @@ export interface InputElementType {
|
|
|
11
11
|
is_auto?: string;
|
|
12
12
|
is_text?: string;
|
|
13
13
|
is_number?: string;
|
|
14
|
-
data?: Array<{
|
|
15
|
-
label: string;
|
|
16
|
-
value: string;
|
|
17
|
-
}>;
|
|
14
|
+
data?: Array<{ label: string; value: string }>;
|
|
18
15
|
value?: string;
|
|
19
16
|
test_param?: {
|
|
20
17
|
name: string;
|
|
21
18
|
type: string;
|
|
22
|
-
data?: Array<{
|
|
23
|
-
label: string;
|
|
24
|
-
value: string;
|
|
25
|
-
}>;
|
|
19
|
+
data?: Array<{ label: string; value: string }>;
|
|
26
20
|
value?: string;
|
|
27
21
|
};
|
|
28
22
|
cases?: Array<{
|
|
@@ -30,9 +24,13 @@ export interface InputElementType {
|
|
|
30
24
|
inputs: Array<InputElementType>;
|
|
31
25
|
}>;
|
|
32
26
|
}
|
|
27
|
+
|
|
33
28
|
export type InputAtomicType = boolean | string | number | null | undefined;
|
|
29
|
+
|
|
34
30
|
export type InputValuesType = Record<string, any>;
|
|
31
|
+
|
|
35
32
|
export type MessageType = "info" | "default" | "warning" | "error" | "success" | undefined;
|
|
33
|
+
|
|
36
34
|
export interface PluginConfigType {
|
|
37
35
|
credentials?: RequestCredentials;
|
|
38
36
|
dataset_id?: string;
|
|
@@ -44,6 +42,7 @@ export interface PluginConfigType {
|
|
|
44
42
|
settings?: any;
|
|
45
43
|
};
|
|
46
44
|
}
|
|
45
|
+
|
|
47
46
|
export interface PluginType {
|
|
48
47
|
name: string;
|
|
49
48
|
html?: string | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getFileName } from "./getFileName.ts";
|
|
2
|
+
|
|
3
|
+
test("identify name without extension", () => {
|
|
4
|
+
expect(getFileName("name.extension")).toEqual("name");
|
|
5
|
+
expect(getFileName("/sub/path/name.extension")).toEqual("name");
|
|
6
|
+
expect(getFileName("http://name.extension")).toEqual("name");
|
|
7
|
+
expect(getFileName("http://sub/path/name.extension")).toEqual("name");
|
|
8
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
interface Dataset {
|
|
2
|
+
metadata_column_types: Record<string, string>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface Column {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseColumns(
|
|
11
|
+
dataset: Dataset,
|
|
12
|
+
isAuto: boolean,
|
|
13
|
+
isText: boolean,
|
|
14
|
+
isNumber: boolean
|
|
15
|
+
): Array<Column> {
|
|
16
|
+
const columns: Array<Column> = [];
|
|
17
|
+
|
|
18
|
+
if (isAuto) {
|
|
19
|
+
columns.push({ label: "Column: Default", value: "auto" });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const meta = dataset.metadata_column_types;
|
|
23
|
+
for (const key in meta) {
|
|
24
|
+
if (
|
|
25
|
+
(isNumber && ["int", "float"].includes(meta[key])) ||
|
|
26
|
+
(isText && meta[key] === "str") ||
|
|
27
|
+
(!isNumber && !isText)
|
|
28
|
+
) {
|
|
29
|
+
columns.push({ label: "Column: " + (parseInt(key) + 1), value: key });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return columns;
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { parseDefaults } from "./parseDefaults.ts";
|
|
2
|
+
|
|
3
|
+
test("parse defaults", () => {
|
|
4
|
+
expect(
|
|
5
|
+
parseDefaults([
|
|
6
|
+
{ name: "a", value: "b" },
|
|
7
|
+
{ name: "c", value: "d" },
|
|
8
|
+
{ name: "e", value: undefined },
|
|
9
|
+
{ name: "f" },
|
|
10
|
+
{ value: "g" },
|
|
11
|
+
]),
|
|
12
|
+
).toEqual({ a: "b", c: "d", e: null, f: null });
|
|
13
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { InputElementType, InputValuesType } from "@/types";
|
|
2
|
+
|
|
3
|
+
export function parseDefaults(inputs?: Array<InputElementType>): InputValuesType {
|
|
4
|
+
const dv: InputValuesType = {};
|
|
5
|
+
|
|
6
|
+
if (inputs) {
|
|
7
|
+
inputs.forEach((input) => {
|
|
8
|
+
if (input.name) {
|
|
9
|
+
dv[input.name] = input.value ?? null;
|
|
10
|
+
} else {
|
|
11
|
+
console.debug("Warning: Detected input element with no name.");
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return dv;
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PluginType, PluginConfigType } from "@/types";
|
|
2
|
+
|
|
3
|
+
interface ParsedIncoming {
|
|
4
|
+
root: string;
|
|
5
|
+
visualizationConfig: PluginConfigType;
|
|
6
|
+
visualizationId?: string;
|
|
7
|
+
visualizationPlugin?: PluginType;
|
|
8
|
+
visualizationTitle: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseIncoming(): ParsedIncoming {
|
|
12
|
+
// Access attached data
|
|
13
|
+
const element = document.getElementById("app");
|
|
14
|
+
const incoming = JSON.parse(element?.getAttribute("data-incoming") || "{}") || {};
|
|
15
|
+
|
|
16
|
+
// Parse incoming data
|
|
17
|
+
const root = incoming.root || "/";
|
|
18
|
+
const visualizationId = incoming.visualization_id || null;
|
|
19
|
+
const visualizationPlugin = incoming.visualization_plugin;
|
|
20
|
+
const visualizationTitle = incoming.visualization_title || "Unnamed Visualization";
|
|
21
|
+
|
|
22
|
+
// Parse chart dict
|
|
23
|
+
let visualizationConfig = incoming.visualization_config;
|
|
24
|
+
if (incoming.visualization_config?.chart_dict) {
|
|
25
|
+
const chartDict = incoming.visualization_config.chart_dict;
|
|
26
|
+
visualizationConfig.groups = chartDict.groups;
|
|
27
|
+
visualizationConfig.settings = chartDict.settings;
|
|
28
|
+
delete visualizationConfig["chart_dict"];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { root, visualizationConfig, visualizationId, visualizationPlugin, visualizationTitle };
|
|
32
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { rethrowSimple } from "@/utilities/simpleError";
|
|
2
|
+
import { parseXML } from "@/utilities/parseXML";
|
|
3
|
+
import type { InputAtomicType, InputElementType, InputValuesType, PluginConfigType, PluginType } from "@/types";
|
|
4
|
+
|
|
5
|
+
interface ParsedPlugin {
|
|
6
|
+
plugin: PluginType;
|
|
7
|
+
settings: InputValuesType;
|
|
8
|
+
specs: Record<string, string> | undefined;
|
|
9
|
+
tracks: Array<InputValuesType>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Parse plugin either from incoming object or XML
|
|
13
|
+
export async function parsePlugin(
|
|
14
|
+
xml: string,
|
|
15
|
+
plugin?: PluginType,
|
|
16
|
+
config: PluginConfigType = {}
|
|
17
|
+
): Promise<ParsedPlugin> {
|
|
18
|
+
// Build plugin from XML if not provided through attached DOM data
|
|
19
|
+
if (!plugin) {
|
|
20
|
+
try {
|
|
21
|
+
plugin = await parseXML(xml);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error("Visualization requires configuration from XML or attached `visualization_plugin` details.");
|
|
24
|
+
rethrowSimple(err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Filter, format, and add defaults to incoming settings and track values
|
|
29
|
+
const settings = parseValues(plugin.settings, config.settings);
|
|
30
|
+
const specs = plugin.specs;
|
|
31
|
+
const tracks = parseTracks(plugin.tracks, config.tracks);
|
|
32
|
+
|
|
33
|
+
return { plugin, settings, specs, tracks };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Format value according to input type
|
|
37
|
+
function formatValue(input: InputElementType, inputValue: InputAtomicType): InputAtomicType {
|
|
38
|
+
let value = inputValue ?? input.value;
|
|
39
|
+
if (input.type === "float") {
|
|
40
|
+
value = Number(value);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Format conditional values based on test cases
|
|
46
|
+
function formatConditional(input: InputElementType, values: InputValuesType = {}): InputValuesType {
|
|
47
|
+
const result = values;
|
|
48
|
+
const testName = input.test_param?.name;
|
|
49
|
+
|
|
50
|
+
if (!testName) {
|
|
51
|
+
console.error(`Test parameter has no name: ${input.name}.`);
|
|
52
|
+
} else {
|
|
53
|
+
const testValue = result[testName] ?? input.test_param?.value;
|
|
54
|
+
for (const inputCase of input.cases || []) {
|
|
55
|
+
if (inputCase.value === testValue) {
|
|
56
|
+
result[testName] = testValue;
|
|
57
|
+
if (inputCase.inputs?.length) {
|
|
58
|
+
for (const conditionalInput of inputCase.inputs) {
|
|
59
|
+
result[conditionalInput.name] = formatValue(conditionalInput, result[conditionalInput.name]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Parse values with conditional handling
|
|
69
|
+
function parseValues(inputs?: Array<InputElementType>, values?: InputValuesType): InputValuesType {
|
|
70
|
+
const result = values || {};
|
|
71
|
+
|
|
72
|
+
inputs?.forEach((input) => {
|
|
73
|
+
if (input.type === "conditional") {
|
|
74
|
+
result[input.name] = formatConditional(input, result[input.name]);
|
|
75
|
+
} else {
|
|
76
|
+
result[input.name] = formatValue(input, result[input.name]);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Parse tracks with nested values
|
|
84
|
+
function parseTracks(inputs?: Array<InputElementType>, tracks?: Array<InputValuesType>): Array<InputValuesType> {
|
|
85
|
+
const values = tracks || [];
|
|
86
|
+
if (inputs) {
|
|
87
|
+
if (values.length === 0) {
|
|
88
|
+
values.push({});
|
|
89
|
+
}
|
|
90
|
+
values.forEach((track, trackIndex) => {
|
|
91
|
+
values[trackIndex] = parseValues(inputs, track);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return values;
|
|
95
|
+
}
|