galaxy-charts 0.0.21 → 0.0.22

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/dist/galaxy-charts.css +1 -0
  2. package/dist/galaxy-charts.js +21275 -0
  3. package/dist/galaxy-charts.umd.cjs +2073 -0
  4. package/{src/types.ts → dist/types.d.ts} +8 -7
  5. package/package.json +7 -1
  6. package/.github/workflows/main.yml +0 -64
  7. package/.vscode/extensions.json +0 -3
  8. package/docs/.vitepress/cache/deps/_metadata.json +0 -40
  9. package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js +0 -36
  10. package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js.map +0 -7
  11. package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js +0 -12492
  12. package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js.map +0 -7
  13. package/docs/.vitepress/cache/deps/naive-ui.js +0 -107113
  14. package/docs/.vitepress/cache/deps/naive-ui.js.map +0 -7
  15. package/docs/.vitepress/cache/deps/package.json +0 -3
  16. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4494
  17. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  18. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -9345
  19. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  20. package/docs/.vitepress/cache/deps/vue.js +0 -344
  21. package/docs/.vitepress/cache/deps/vue.js.map +0 -7
  22. package/docs/.vitepress/config.mts +0 -55
  23. package/docs/.vitepress/theme/index.js +0 -7
  24. package/docs/.vitepress/theme/tailwind.css +0 -7
  25. package/docs/content/configuration.md +0 -45
  26. package/docs/content/deploy-plugin.md +0 -22
  27. package/docs/content/deploy-request.md +0 -65
  28. package/docs/content/installation.md +0 -42
  29. package/docs/content/introduction.md +0 -45
  30. package/docs/content/vue-introduction.md +0 -38
  31. package/docs/content/vue-utilities.md +0 -70
  32. package/docs/content/xml-datasources.md +0 -34
  33. package/docs/content/xml-framework.md +0 -140
  34. package/docs/content/xml-inputs.md +0 -248
  35. package/docs/content/xml-introduction.md +0 -23
  36. package/docs/content/xml-sections.md +0 -50
  37. package/docs/index.md +0 -27
  38. package/docs/package-lock.json +0 -4203
  39. package/docs/package.json +0 -21
  40. package/docs/postcss.config.js +0 -6
  41. package/docs/public/eslint.svg +0 -7
  42. package/docs/public/galaxy-charts-demo.gif +0 -0
  43. package/docs/public/galaxy-charts-starter.jpg +0 -0
  44. package/docs/public/galaxy-charts.svg +0 -7
  45. package/docs/public/javascript.svg +0 -8
  46. package/docs/public/naive-ui.svg +0 -9
  47. package/docs/public/next-js.svg +0 -25
  48. package/docs/public/nuxt.svg +0 -3
  49. package/docs/public/prettier.svg +0 -46
  50. package/docs/public/react.svg +0 -1
  51. package/docs/public/tailwind.svg +0 -12
  52. package/docs/public/typescript.svg +0 -8
  53. package/docs/public/vite.svg +0 -15
  54. package/docs/public/vitest.svg +0 -9
  55. package/docs/public/vue.svg +0 -8
  56. package/docs/public/vuetify.svg +0 -9
  57. package/docs/tailwind.config.js +0 -9
  58. package/index.html +0 -13
  59. package/lib/galaxy-charts.js +0 -7
  60. package/postcss.config.js +0 -6
  61. package/prettier.config.js +0 -5
  62. package/public/galaxy-charts.xml +0 -138
  63. package/src/App.vue +0 -23
  64. package/src/Plugin.vue +0 -37
  65. package/src/api/client.ts +0 -46
  66. package/src/api/datasets.ts +0 -34
  67. package/src/api/visualizations.ts +0 -33
  68. package/src/components/AlertNotify.vue +0 -35
  69. package/src/components/ApiStatus.vue +0 -39
  70. package/src/components/GalaxyCharts.vue +0 -136
  71. package/src/components/InputConditional.vue +0 -109
  72. package/src/components/InputData.vue +0 -74
  73. package/src/components/InputDataColumn.vue +0 -54
  74. package/src/components/InputForm.vue +0 -119
  75. package/src/components/InputRepeats.vue +0 -70
  76. package/src/components/SidePanel.vue +0 -158
  77. package/src/main.js +0 -27
  78. package/src/store/columnsStore.ts +0 -59
  79. package/src/store/configStore.ts +0 -29
  80. package/src/style.css +0 -3
  81. package/src/utilities/getFileName.test.js +0 -8
  82. package/src/utilities/getFileName.ts +0 -4
  83. package/src/utilities/parseColumns.ts +0 -34
  84. package/src/utilities/parseDefaults.test.js +0 -13
  85. package/src/utilities/parseDefaults.ts +0 -17
  86. package/src/utilities/parseIncoming.ts +0 -32
  87. package/src/utilities/parsePlugin.ts +0 -95
  88. package/src/utilities/parseXML.ts +0 -156
  89. package/src/utilities/simpleError.ts +0 -21
  90. package/src/utilities/toBoolean.test.js +0 -12
  91. package/src/utilities/toBoolean.ts +0 -5
  92. package/tailwind.config.js +0 -8
  93. package/tsconfig.json +0 -28
  94. 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
- }
@@ -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,3 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
@@ -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,4 +0,0 @@
1
- export function getFileName(path: string) {
2
- const extension = path.replace(/^.*[\\\/]/, "");
3
- return extension.substring(0, extension.lastIndexOf("."));
4
- }
@@ -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
- });