galaxy-charts 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main.yml +64 -0
- package/.vscode/extensions.json +3 -0
- package/docs/.vitepress/cache/deps/_metadata.json +40 -0
- package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js +36 -0
- package/docs/.vitepress/cache/deps/chunk-G3PMV62Z.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js +12492 -0
- package/docs/.vitepress/cache/deps/chunk-XYSSNQS4.js.map +7 -0
- package/docs/.vitepress/cache/deps/naive-ui.js +107113 -0
- package/docs/.vitepress/cache/deps/naive-ui.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4494 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +9345 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +344 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/config.mts +55 -0
- package/docs/.vitepress/theme/index.js +7 -0
- package/docs/.vitepress/theme/tailwind.css +7 -0
- package/docs/content/configuration.md +45 -0
- package/docs/content/deploy-plugin.md +22 -0
- package/docs/content/deploy-request.md +65 -0
- package/docs/content/installation.md +42 -0
- package/docs/content/introduction.md +45 -0
- package/docs/content/vue-introduction.md +38 -0
- package/docs/content/vue-utilities.md +70 -0
- package/docs/content/xml-datasources.md +34 -0
- package/docs/content/xml-framework.md +140 -0
- package/docs/content/xml-inputs.md +248 -0
- package/docs/content/xml-introduction.md +23 -0
- package/docs/content/xml-sections.md +50 -0
- package/docs/index.md +27 -0
- package/docs/package-lock.json +4203 -0
- package/docs/package.json +21 -0
- package/docs/postcss.config.js +6 -0
- package/docs/public/eslint.svg +7 -0
- package/docs/public/galaxy-charts-demo.gif +0 -0
- package/docs/public/galaxy-charts-starter.jpg +0 -0
- package/docs/public/galaxy-charts.svg +7 -0
- package/docs/public/javascript.svg +8 -0
- package/docs/public/naive-ui.svg +9 -0
- package/docs/public/next-js.svg +25 -0
- package/docs/public/nuxt.svg +3 -0
- package/docs/public/prettier.svg +46 -0
- package/docs/public/react.svg +1 -0
- package/docs/public/tailwind.svg +12 -0
- package/docs/public/typescript.svg +8 -0
- package/docs/public/vite.svg +15 -0
- package/docs/public/vitest.svg +9 -0
- package/docs/public/vue.svg +8 -0
- package/docs/public/vuetify.svg +9 -0
- package/docs/tailwind.config.js +9 -0
- package/index.html +13 -0
- package/lib/galaxy-charts.js +7 -0
- package/package.json +8 -10
- package/postcss.config.js +6 -0
- package/prettier.config.js +5 -0
- package/public/galaxy-charts.xml +138 -0
- package/src/App.vue +23 -0
- package/src/Plugin.vue +37 -0
- package/src/api/client.ts +46 -0
- package/src/api/datasets.ts +34 -0
- package/src/api/visualizations.ts +33 -0
- package/src/components/AlertNotify.vue +35 -0
- package/src/components/ApiStatus.vue +39 -0
- package/src/components/GalaxyCharts.vue +136 -0
- package/src/components/InputConditional.vue +109 -0
- package/src/components/InputData.vue +74 -0
- package/src/components/InputDataColumn.vue +54 -0
- package/src/components/InputForm.vue +119 -0
- package/src/components/InputRepeats.vue +70 -0
- package/src/components/SidePanel.vue +158 -0
- package/src/main.js +27 -0
- package/src/store/columnsStore.ts +59 -0
- package/src/store/configStore.ts +29 -0
- package/src/style.css +3 -0
- package/src/utilities/getFileName.test.js +8 -0
- package/src/utilities/getFileName.ts +4 -0
- package/src/utilities/parseColumns.ts +34 -0
- package/src/utilities/parseDefaults.test.js +13 -0
- package/src/utilities/parseDefaults.ts +17 -0
- package/src/utilities/parseIncoming.ts +32 -0
- package/src/utilities/parsePlugin.ts +95 -0
- package/src/utilities/parseXML.ts +156 -0
- package/src/utilities/simpleError.ts +21 -0
- package/src/utilities/toBoolean.test.js +12 -0
- package/src/utilities/toBoolean.ts +5 -0
- package/tailwind.config.js +8 -0
- package/tsconfig.json +28 -0
- package/vite.config.js +70 -0
- package/dist/galaxy-charts.css +0 -1
- package/dist/galaxy-charts.js +0 -21277
- package/dist/galaxy-charts.umd.cjs +0 -2073
- /package/{dist/types.d.ts → src/types.ts} +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Node",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"jsx": "preserve",
|
|
9
|
+
"sourceMap": false,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"lib": ["ESNext", "DOM"],
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"baseUrl": ".",
|
|
15
|
+
"paths": {
|
|
16
|
+
"@/*": ["src/*"]
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"dist",
|
|
22
|
+
"docs",
|
|
23
|
+
"node_modules",
|
|
24
|
+
"static",
|
|
25
|
+
"src/App.vue",
|
|
26
|
+
"src/Plugin.vue"
|
|
27
|
+
],
|
|
28
|
+
}
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import tailwindcss from "tailwindcss";
|
|
3
|
+
import vue from "@vitejs/plugin-vue";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { libInjectCss } from "vite-plugin-lib-inject-css";
|
|
6
|
+
import { configDefaults } from "vitest/config";
|
|
7
|
+
import Checker from "vite-plugin-checker";
|
|
8
|
+
|
|
9
|
+
// collect Galaxy server root
|
|
10
|
+
let GALAXY_ROOT = "http://127.0.0.1:8080";
|
|
11
|
+
if (process.env.GALAXY_ROOT) {
|
|
12
|
+
GALAXY_ROOT = process.env.GALAXY_ROOT;
|
|
13
|
+
} else {
|
|
14
|
+
console.log("GALAXY_ROOT not available. Please provide as environment variable.");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// collect Galaxy API key
|
|
18
|
+
let GALAXY_KEY = "";
|
|
19
|
+
if (process.env.GALAXY_KEY) {
|
|
20
|
+
GALAXY_KEY = process.env.GALAXY_KEY;
|
|
21
|
+
} else {
|
|
22
|
+
console.log("GALAXY_KEY not available. Please provide as environment variable.");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// https://vitejs.dev/config/
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
build: {
|
|
28
|
+
lib: {
|
|
29
|
+
entry: path.resolve(__dirname, "lib/galaxy-charts.js"),
|
|
30
|
+
name: "GalaxyCharts",
|
|
31
|
+
fileName: "galaxy-charts",
|
|
32
|
+
},
|
|
33
|
+
rollupOptions: {
|
|
34
|
+
external: ["vue"],
|
|
35
|
+
output: {
|
|
36
|
+
globals: { vue: "vue" },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
define: {
|
|
41
|
+
"process.env.credentials": JSON.stringify(GALAXY_KEY ? "omit" : "include"),
|
|
42
|
+
},
|
|
43
|
+
plugins: [vue(), tailwindcss(), libInjectCss(), Checker({ typescript: true })],
|
|
44
|
+
resolve: {
|
|
45
|
+
alias: {
|
|
46
|
+
"@": path.resolve(__dirname, "src"),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
server: {
|
|
50
|
+
proxy: {
|
|
51
|
+
"/api": {
|
|
52
|
+
changeOrigin: true,
|
|
53
|
+
rewrite: (path) => {
|
|
54
|
+
if (GALAXY_KEY) {
|
|
55
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
56
|
+
return `${path}${separator}key=${GALAXY_KEY}`;
|
|
57
|
+
} else {
|
|
58
|
+
return path;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
target: GALAXY_ROOT,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
test: {
|
|
66
|
+
globals: true,
|
|
67
|
+
environment: "jsdom",
|
|
68
|
+
exclude: configDefaults.exclude,
|
|
69
|
+
},
|
|
70
|
+
});
|
package/dist/galaxy-charts.css
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.z-10{z-index:10}.m-1{margin:.25rem}.m-2{margin:.5rem}.m-4{margin:1rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mr-1{margin-right:.25rem}.mt-0{margin-top:0}.mt-2{margin-top:.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.\!hidden{display:none!important}.hidden{display:none}.size-14{width:3.5rem;height:3.5rem}.size-3{width:.75rem;height:.75rem}.size-4{width:1rem;height:1rem}.h-screen{height:100vh}.w-full{width:100%}.min-w-14{min-width:3.5rem}.max-w-14{max-width:3.5rem}.flex-1{flex:1 1 0%}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.grid-cols-\[70\%_30\%\]{grid-template-columns:70% 30%}.justify-center{justify-content:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-dotted{border-style:dotted}.border-green-600{--tw-border-opacity: 1;border-color:rgb(22 163 74 / var(--tw-border-opacity))}.bg-gray-600{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity))}.bg-sky-50{--tw-bg-opacity: 1;background-color:rgb(240 249 255 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-thin{font-weight:100}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.text-sky-900{--tw-text-opacity: 1;color:rgb(12 74 110 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width: 768px){.md\:flex{display:flex}}
|