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