galaxy-charts 0.0.21 → 0.0.23
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/dist/galaxy-charts.css +1 -0
- package/dist/galaxy-charts.js +21275 -0
- package/dist/galaxy-charts.umd.cjs +2073 -0
- package/{src/types.ts → dist/types.d.ts} +8 -7
- package/package.json +7 -1
- package/.github/workflows/main.yml +0 -64
- package/.vscode/extensions.json +0 -3
- package/docs/.vitepress/cache/deps/_metadata.json +0 -40
- package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js +0 -36
- package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js +0 -12492
- package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js.map +0 -7
- package/docs/.vitepress/cache/deps/naive-ui.js +0 -107113
- package/docs/.vitepress/cache/deps/naive-ui.js.map +0 -7
- package/docs/.vitepress/cache/deps/package.json +0 -3
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4494
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -9345
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
- package/docs/.vitepress/cache/deps/vue.js +0 -344
- package/docs/.vitepress/cache/deps/vue.js.map +0 -7
- package/docs/.vitepress/config.mts +0 -55
- package/docs/.vitepress/theme/index.js +0 -7
- package/docs/.vitepress/theme/tailwind.css +0 -7
- package/docs/content/configuration.md +0 -45
- package/docs/content/deploy-plugin.md +0 -22
- package/docs/content/deploy-request.md +0 -65
- package/docs/content/installation.md +0 -42
- package/docs/content/introduction.md +0 -45
- package/docs/content/vue-introduction.md +0 -38
- package/docs/content/vue-utilities.md +0 -70
- package/docs/content/xml-datasources.md +0 -34
- package/docs/content/xml-framework.md +0 -140
- package/docs/content/xml-inputs.md +0 -248
- package/docs/content/xml-introduction.md +0 -23
- package/docs/content/xml-sections.md +0 -50
- package/docs/index.md +0 -27
- package/docs/package-lock.json +0 -4203
- package/docs/package.json +0 -21
- package/docs/postcss.config.js +0 -6
- package/docs/public/eslint.svg +0 -7
- package/docs/public/galaxy-charts-demo.gif +0 -0
- package/docs/public/galaxy-charts-starter.jpg +0 -0
- package/docs/public/galaxy-charts.svg +0 -7
- package/docs/public/javascript.svg +0 -8
- package/docs/public/naive-ui.svg +0 -9
- package/docs/public/next-js.svg +0 -25
- package/docs/public/nuxt.svg +0 -3
- package/docs/public/prettier.svg +0 -46
- package/docs/public/react.svg +0 -1
- package/docs/public/tailwind.svg +0 -12
- package/docs/public/typescript.svg +0 -8
- package/docs/public/vite.svg +0 -15
- package/docs/public/vitest.svg +0 -9
- package/docs/public/vue.svg +0 -8
- package/docs/public/vuetify.svg +0 -9
- package/docs/tailwind.config.js +0 -9
- package/index.html +0 -13
- package/lib/galaxy-charts.js +0 -7
- package/postcss.config.js +0 -6
- package/prettier.config.js +0 -5
- package/public/galaxy-charts.xml +0 -138
- package/src/App.vue +0 -23
- package/src/Plugin.vue +0 -37
- package/src/api/client.ts +0 -46
- package/src/api/datasets.ts +0 -34
- package/src/api/visualizations.ts +0 -33
- package/src/components/AlertNotify.vue +0 -35
- package/src/components/ApiStatus.vue +0 -39
- package/src/components/GalaxyCharts.vue +0 -136
- package/src/components/InputConditional.vue +0 -109
- package/src/components/InputData.vue +0 -74
- package/src/components/InputDataColumn.vue +0 -54
- package/src/components/InputForm.vue +0 -119
- package/src/components/InputRepeats.vue +0 -70
- package/src/components/SidePanel.vue +0 -158
- package/src/main.js +0 -27
- package/src/store/columnsStore.ts +0 -59
- package/src/store/configStore.ts +0 -29
- package/src/style.css +0 -3
- package/src/utilities/getFileName.test.js +0 -8
- package/src/utilities/getFileName.ts +0 -4
- package/src/utilities/parseColumns.ts +0 -34
- package/src/utilities/parseDefaults.test.js +0 -13
- package/src/utilities/parseDefaults.ts +0 -17
- package/src/utilities/parseIncoming.ts +0 -32
- package/src/utilities/parsePlugin.ts +0 -95
- package/src/utilities/parseXML.ts +0 -156
- package/src/utilities/simpleError.ts +0 -21
- package/src/utilities/toBoolean.test.js +0 -12
- package/src/utilities/toBoolean.ts +0 -5
- package/tailwind.config.js +0 -8
- package/tsconfig.json +0 -28
- package/vite.config.js +0 -70
|
@@ -1,158 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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");
|
|
@@ -1,59 +0,0 @@
|
|
|
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
|
-
}
|
package/src/store/configStore.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import { getFileName } from "@/utilities/getFileName";
|
|
3
|
-
import type { PluginType } from "@/types";
|
|
4
|
-
const parser = new DOMParser();
|
|
5
|
-
|
|
6
|
-
interface MacroEntries {
|
|
7
|
-
[key: string]: Element[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Parses the base XML with macros and expands nodes
|
|
11
|
-
async function parseMacros(xmlBaseString: string, xmlPath: string = ""): Promise<Document> {
|
|
12
|
-
const xmlDoc = parser.parseFromString(xmlBaseString, "text/xml");
|
|
13
|
-
|
|
14
|
-
// Parse macros from base XML
|
|
15
|
-
const macroFiles: string[] = [];
|
|
16
|
-
const macrosNodes = xmlDoc.documentElement.getElementsByTagName("macros");
|
|
17
|
-
if (macrosNodes.length > 0) {
|
|
18
|
-
for (const macroNode of Array.from(macrosNodes)) {
|
|
19
|
-
for (const element of Array.from(macroNode.children)) {
|
|
20
|
-
macroFiles.push(element.textContent?.trim() || "");
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
console.debug(`Detected ${macroFiles.length} macro file(s) in xml.`);
|
|
25
|
-
|
|
26
|
-
// Load and collect macro entries
|
|
27
|
-
const xmlEntries: MacroEntries = {};
|
|
28
|
-
for (const macroFile of macroFiles) {
|
|
29
|
-
const { data } = await axios.get(`${xmlPath}${macroFile}`);
|
|
30
|
-
const xmlMacro = parser.parseFromString(data, "text/xml");
|
|
31
|
-
const xmlNode = xmlMacro.documentElement.getElementsByTagName("xml");
|
|
32
|
-
if (xmlNode.length > 0) {
|
|
33
|
-
for (const xmlEntry of Array.from(xmlNode)) {
|
|
34
|
-
const xmlName = xmlEntry.getAttribute("name");
|
|
35
|
-
if (xmlName) {
|
|
36
|
-
for (const entry of Array.from(xmlEntry.children)) {
|
|
37
|
-
xmlEntries[xmlName] = xmlEntries[xmlName] || [];
|
|
38
|
-
xmlEntries[xmlName].push(entry);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
console.debug(`Successfully collected macro files.`);
|
|
45
|
-
|
|
46
|
-
// Parse macros from base XML
|
|
47
|
-
const expandNodes = xmlDoc.documentElement.getElementsByTagName("expand");
|
|
48
|
-
if (expandNodes.length > 0) {
|
|
49
|
-
for (const expandNode of Array.from(expandNodes)) {
|
|
50
|
-
const expandName = expandNode.getAttribute("macro");
|
|
51
|
-
if (expandName && expandName in xmlEntries) {
|
|
52
|
-
const entries = xmlEntries[expandName];
|
|
53
|
-
for (const entry of entries) {
|
|
54
|
-
expandNode.parentNode?.appendChild(entry.cloneNode(true));
|
|
55
|
-
}
|
|
56
|
-
expandNode.parentNode?.removeChild(expandNode);
|
|
57
|
-
} else {
|
|
58
|
-
console.error(`Failed to detect '${expandName}' macros source.`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Returns full XML document with populated macros
|
|
64
|
-
return xmlDoc;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Parse XML file and collect attributes
|
|
68
|
-
export async function parseXML(xmlFileName: string): Promise<PluginType> {
|
|
69
|
-
const xmlBase = await axios.get(xmlFileName);
|
|
70
|
-
const xmlDoc = await parseMacros(xmlBase.data);
|
|
71
|
-
|
|
72
|
-
// Parse name and logo
|
|
73
|
-
const result: PluginType = { name: getFileName(xmlFileName) };
|
|
74
|
-
result.html = xmlDoc.documentElement.getAttribute("name");
|
|
75
|
-
result.logo = xmlDoc.documentElement.getAttribute("logo");
|
|
76
|
-
|
|
77
|
-
// Parse description
|
|
78
|
-
const descriptionNode = xmlDoc.documentElement.getElementsByTagName("description");
|
|
79
|
-
if (descriptionNode.length === 1) {
|
|
80
|
-
result.description = descriptionNode[0].textContent?.trim() || null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Parse specifications
|
|
84
|
-
const specsNode = xmlDoc.documentElement.getElementsByTagName("specs");
|
|
85
|
-
if (specsNode.length === 1) {
|
|
86
|
-
result.specs = new DictParser(specsNode[0]);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Parse inputs from groups and settings sections
|
|
90
|
-
["settings", "tracks"].forEach((key) => {
|
|
91
|
-
const xmlNode = xmlDoc.documentElement.getElementsByTagName(key);
|
|
92
|
-
if (xmlNode.length === 1) {
|
|
93
|
-
(result as any)[key] = new ListParser(xmlNode[0]);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Converts an XML structure into an array
|
|
102
|
-
*/
|
|
103
|
-
class ListParser extends Array {
|
|
104
|
-
constructor(elements: Element) {
|
|
105
|
-
super();
|
|
106
|
-
for (const element of Array.from(elements.children)) {
|
|
107
|
-
if (element.children.length > 0) {
|
|
108
|
-
if (element.tagName === element.children[0].tagName) {
|
|
109
|
-
this.push(new ListParser(element));
|
|
110
|
-
} else {
|
|
111
|
-
this.push(new DictParser(element));
|
|
112
|
-
}
|
|
113
|
-
} else if (element.textContent && element.textContent.trim()) {
|
|
114
|
-
this.push(element.textContent.trim());
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Converts an XML structure into a dictionary
|
|
122
|
-
*/
|
|
123
|
-
class DictParser {
|
|
124
|
-
[key: string]: any;
|
|
125
|
-
|
|
126
|
-
constructor(parentElement: Element) {
|
|
127
|
-
if (parentElement.attributes.length > 0) {
|
|
128
|
-
for (const attr of Array.from(parentElement.attributes)) {
|
|
129
|
-
this[attr.name] = attr.value;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
for (const element of Array.from(parentElement.children)) {
|
|
133
|
-
if (element.children.length > 0) {
|
|
134
|
-
let asJson;
|
|
135
|
-
if (element.tagName === element.children[0].tagName) {
|
|
136
|
-
asJson = new ListParser(element);
|
|
137
|
-
} else {
|
|
138
|
-
const aDict = new DictParser(element);
|
|
139
|
-
for (const attr of Array.from(element.attributes)) {
|
|
140
|
-
aDict[attr.name] = attr.value;
|
|
141
|
-
}
|
|
142
|
-
asJson = aDict;
|
|
143
|
-
}
|
|
144
|
-
this[element.tagName] = asJson;
|
|
145
|
-
} else if (element.attributes.length > 0) {
|
|
146
|
-
const attrObj: { [key: string]: string } = {};
|
|
147
|
-
for (const attr of Array.from(element.attributes)) {
|
|
148
|
-
attrObj[attr.name] = attr.value;
|
|
149
|
-
}
|
|
150
|
-
this[element.tagName] = attrObj;
|
|
151
|
-
} else {
|
|
152
|
-
this[element.tagName] = element.textContent?.trim() || null;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export function errorMessageAsString(e: any, defaultMessage = "Request failed.") {
|
|
2
|
-
// Note that despite the name, this can actually currently return an object,
|
|
3
|
-
// depending on what data.err_msg is (e.g. an object)
|
|
4
|
-
let message = defaultMessage;
|
|
5
|
-
if (e && e.response && e.response.data && e.response.data.err_msg) {
|
|
6
|
-
message = e.response.data.err_msg;
|
|
7
|
-
} else if (e && e.data && e.data.err_msg) {
|
|
8
|
-
message = e.data.err_msg;
|
|
9
|
-
} else if (e && e.response) {
|
|
10
|
-
message = `${e.response.statusText} (${e.response.status})`;
|
|
11
|
-
} else if (e instanceof Error) {
|
|
12
|
-
message = e.message;
|
|
13
|
-
} else if (typeof e == "string") {
|
|
14
|
-
message = e;
|
|
15
|
-
}
|
|
16
|
-
return message;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function rethrowSimple(e: any): never {
|
|
20
|
-
throw Error(errorMessageAsString(e));
|
|
21
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { toBoolean } from "./toBoolean.ts";
|
|
2
|
-
|
|
3
|
-
test("to boolean conversion", () => {
|
|
4
|
-
expect(toBoolean(true)).toBeTruthy();
|
|
5
|
-
expect(toBoolean("true")).toBeTruthy();
|
|
6
|
-
expect(toBoolean("TrUe")).toBeTruthy();
|
|
7
|
-
expect(toBoolean(false)).toBeFalsy();
|
|
8
|
-
expect(toBoolean("rue")).toBeFalsy();
|
|
9
|
-
expect(toBoolean(1)).toBeFalsy();
|
|
10
|
-
expect(toBoolean(null)).toBeFalsy();
|
|
11
|
-
expect(toBoolean(undefined)).toBeFalsy();
|
|
12
|
-
});
|