diff --git a/package-lock.json b/package-lock.json index 6c5d9d153c15480d63ff60691439d6e7fc67eb2a..922c7405a885a92f0df0070321471aa13c9cc70c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -214,9 +214,9 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-s3": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.535.0.tgz", - "integrity": "sha512-qcFCP9a695ZvAbm+hRMyfE2PjqnSkq0Bl57X7z8gHUg4TIjKJHTP7mtND21A4YaWigegQL6OA5kMXMZbCcugLA==", + "version": "3.536.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.536.0.tgz", + "integrity": "sha512-UM5txJxq8qKzLDVuW9c904bpb7+u1jOeyJITLz79WpyHSOP6ERHoTx/ltEuGJ4zQVazfkgthqR0lIn09sXEEuw==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", @@ -572,9 +572,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.535.0.tgz", - "integrity": "sha512-JlamsgeokOPq3lRBH6yQBkjJQ/ak4sBCvZjs4yl2sGpzcOzq75x5qL9Ms1skY9nShPv+UeowzI7K9ia11rNDnw==", + "version": "3.536.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.536.0.tgz", + "integrity": "sha512-jeHnxHy02n2oEaXZx0fwNbZwnA1LZvSC6mEQZYlz3trhhqE3Ryn8HnODGR+cUKb8i+iyVCWdKRa4mfz1Xu+sQw==", "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/middleware-endpoint": "^2.5.0", @@ -775,9 +775,9 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.535.0.tgz", - "integrity": "sha512-lO8uVkiXUuAu3qvLxXoToLrWp7afxH8JzX+acYxWLQdQE10LIFykKGX3NEysCA9YkyCRwEfplqaJKiVQAC3fOw==", + "version": "3.536.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.536.0.tgz", + "integrity": "sha512-bMz7nxYfSjCkw72Hah40o0c9scwU2LQWoT3CkTcWs9OCrG4MCzMTBJNlzRUpxOpfGTZqVzrCaUGINIjh3jP/tQ==", "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.535.0", "@aws-sdk/types": "3.535.0", @@ -940,9 +940,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2464,9 +2464,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.28", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", - "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -2497,16 +2497,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", - "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", + "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/type-utils": "7.2.0", - "@typescript-eslint/utils": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/type-utils": "7.3.1", + "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2515,7 +2515,7 @@ "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2532,19 +2532,19 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", + "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2560,16 +2560,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", - "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", + "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0" + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2577,18 +2577,18 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", - "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", + "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/utils": "7.3.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2604,12 +2604,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", - "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", + "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2617,13 +2617,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", - "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", + "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2632,7 +2632,7 @@ "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2645,21 +2645,21 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", - "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", + "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/typescript-estree": "7.3.1", "semver": "^7.5.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2670,16 +2670,16 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", - "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", + "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/types": "7.3.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3519,9 +3519,9 @@ } }, "node_modules/dompurify": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", - "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.10.tgz", + "integrity": "sha512-WZDL8ZHTliEVP3Lk4phtvjg8SNQ3YMc5WVstxE8cszKZrFjzI4PF4ZTIk9VGAc9vZADO7uGO2V/ZiStcRSAT4Q==" }, "node_modules/entities": { "version": "4.5.0", diff --git a/src/components/modals/BootstrapModal.vue b/src/components/modals/BootstrapModal.vue index 63806ef19d8f804f709b31185083703de16dcb67..7d6cc6cd2efbf855c48defec2e51adda0b16cbce 100644 --- a/src/components/modals/BootstrapModal.vue +++ b/src/components/modals/BootstrapModal.vue @@ -1,21 +1,22 @@ <script setup lang="ts"> import { computed } from "vue"; +import type { SizeModifierType } from "@/types/PropTypes"; const props = defineProps<{ modalId: string; modalLabel: string; staticBackdrop?: boolean; - sizeModifier?: sizeModifierType; // https://getbootstrap.com/docs/5.3/components/modal/#optional-sizes, e.g. sm, lg and xl + sizeModifierModal?: sizeModifierModalType; // https://getbootstrap.com/docs/5.3/components/modal/#optional-sizes, e.g. sm, lg and xl trackModalValue?: string; }>(); -type sizeModifierType = "sm" | "lg" | "xl"; +type sizeModifierModalType = SizeModifierType | "xl"; const modalSizeClass = computed<string>(() => { - if (props.sizeModifier == undefined) { + if (props.sizeModifierModal == undefined) { return ""; } - return "modal-" + props.sizeModifier; + return "modal-" + props.sizeModifierModal; }); function trackModalShow() { diff --git a/src/components/parameter-schema/ParameterSchemaFormComponent.vue b/src/components/parameter-schema/ParameterSchemaFormComponent.vue index 3a77eafb79c39ada5d2a545da4137f5f0433cd97..1322357f2266aa4bc15925bac3d1b42edbd9d3a2 100644 --- a/src/components/parameter-schema/ParameterSchemaFormComponent.vue +++ b/src/components/parameter-schema/ParameterSchemaFormComponent.vue @@ -4,7 +4,7 @@ import ParameterGroupForm from "@/components/parameter-schema/form-mode/Paramete import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import Ajv from "ajv"; import type { ValidateFunction } from "ajv"; -import ParameterStringInput from "@/components/parameter-schema/form-mode/ParameterStringInput.vue"; +import ParameterInput from "@/components/parameter-schema/form-mode/ParameterInput.vue"; import { Toast, Tooltip } from "bootstrap"; import { useBucketStore } from "@/stores/buckets"; import { useS3KeyStore } from "@/stores/s3keys"; @@ -331,14 +331,13 @@ onMounted(() => { <span class="input-group-text border border-secondary"> <font-awesome-icon icon="fa-solid fa-folder" /> </span> - <parameter-string-input - parameter-name="logs_s3_path" + <parameter-input v-model="formState.metaParameters.logs_s3_path" :parameter="{ format: 'directory-path', type: 'string', }" - remove-advanced + border /> </div> <label class="mb-3" for="logsS3Path"> @@ -354,14 +353,13 @@ onMounted(() => { <span class="input-group-text border border-secondary"> <font-awesome-icon icon="fa-solid fa-folder" /> </span> - <parameter-string-input - parameter-name="provenance_s3_path" + <parameter-input v-model="formState.metaParameters.provenance_s3_path" :parameter="{ format: 'directory-path', type: 'string', }" - remove-advanced + border /> </div> <label class="mb-3" for="provenanceS3Path"> @@ -378,14 +376,13 @@ onMounted(() => { <span class="input-group-text border border-secondary"> <font-awesome-icon icon="fa-solid fa-folder" /> </span> - <parameter-string-input - parameter-name="debug_s3_path" + <parameter-input v-model="formState.metaParameters.debug_s3_path" :parameter="{ format: 'directory-path', type: 'string', }" - remove-advanced + border /> </div> <label class="mb-3" for="debugS3Path"> diff --git a/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue b/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue index 54e519ccd40a64f2ab9801973389ae525ea3d97f..f78d2bbf736b69eee4ecf74045a965c8a993fd32 100644 --- a/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue @@ -11,28 +11,35 @@ const props = defineProps({ return "number" === value["type"]; }, }, - required: Boolean, - parameterName: { - type: String, - required: true, - }, - helpId: { - type: String, - }, + border: Boolean, }); const randomIDSuffix = Math.random().toString(16).substring(2, 8); const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); const iconPresent = computed<boolean>(() => props.parameter["fa_icon"]); + +const dynamicCssClasses = computed<string[]>(() => { + const cssClasses = []; + if (props.border) { + cssClasses.push("border", "border-secondary", "text-bg-light"); + } + if (!helpTextPresent.value) { + cssClasses.push("rounded-end"); + } + if (!iconPresent.value) { + cssClasses.push("rounded-start"); + } + return cssClasses; +}); </script> <template> <div - class="flex-fill mb-0 text-bg-light fs-6 ps-4 d-flex align-items-center justify-content-start py-1 border border-secondary" - :class="{ 'rounded-end': !helpTextPresent, 'rounded-start': !iconPresent }" + class="flex-fill mb-0 fs-6 d-flex align-items-center justify-content-start py-1 ps-4" + :class="dynamicCssClasses" > - <div class="form-check form-check-inline"> + <div class="form-check form-check-inline mb-0"> <label class="form-check-label" :for="'trueOption' + randomIDSuffix" >True</label > @@ -45,7 +52,7 @@ const iconPresent = computed<boolean>(() => props.parameter["fa_icon"]); v-model="model" /> </div> - <div class="form-check form-check-inline"> + <div class="form-check form-check-inline mb-0"> <input class="form-check-input" type="radio" diff --git a/src/components/parameter-schema/form-mode/ParameterEnumInput.vue b/src/components/parameter-schema/form-mode/ParameterEnumInput.vue index a92092cc77d4a17a2665bbbb46a979ba2ef18f29..dcd17309d546a985fd1b19f11b90de3f031123fd 100644 --- a/src/components/parameter-schema/form-mode/ParameterEnumInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterEnumInput.vue @@ -1,5 +1,6 @@ <script setup lang="ts"> -import { computed } from "vue"; +import { computed, type PropType } from "vue"; +import type { SizeModifierType } from "@/types/PropTypes"; const model = defineModel<string | undefined>({ required: true }); @@ -12,23 +13,35 @@ const props = defineProps({ }, }, required: Boolean, - parameterName: { - type: String, - required: true, - }, helpId: { type: String, }, + sizeModifier: { + type: String as PropType<SizeModifierType>, + }, + border: Boolean, }); const possibleValues = computed<string[]>(() => props.parameter["enum"]); + +const dynamicCssClasses = computed<string[]>(() => { + const cssClasses = []; + if (props.sizeModifier) { + cssClasses.push(`form-select-${props.sizeModifier}`); + } + if (props.border) { + cssClasses.push("border", "border-secondary"); + } + return cssClasses; +}); </script> <template> <select ref="enumSelection" v-model="model" - class="form-select border border-secondary" + class="form-select" + :class="dynamicCssClasses" :required="required" :aria-describedby="props.helpId" > diff --git a/src/components/parameter-schema/form-mode/ParameterFileInput.vue b/src/components/parameter-schema/form-mode/ParameterFileInput.vue new file mode 100644 index 0000000000000000000000000000000000000000..6ca9c8b9e23533d70daa450951cc98c77422e1d5 --- /dev/null +++ b/src/components/parameter-schema/form-mode/ParameterFileInput.vue @@ -0,0 +1,159 @@ +<script setup lang="ts"> +import { computed, type PropType, reactive, watch } from "vue"; +import { useS3ObjectStore } from "@/stores/s3objects"; +import { useBucketStore } from "@/stores/buckets"; +import type { SizeModifierType } from "@/types/PropTypes"; + +const model = defineModel<string | undefined>({ required: true }); + +const props = defineProps({ + parameter: { + type: Object, + required: true, + validator(value: Record<string, never>) { + return value["format"] != undefined; + }, + }, + required: Boolean, + helpId: { + type: String, + }, + sizeModifier: { + type: String as PropType<SizeModifierType>, + }, + border: Boolean, +}); + +const s3ObjectRepository = useS3ObjectStore(); +const bucketRepository = useBucketStore(); +const randomIDSuffix = Math.random().toString(16).substring(2, 8); + +const baseDynamicClass = computed<string[]>(() => + props.border ? ["border", "border-secondary"] : [], +); + +const selectDynamicClass = computed<string[]>(() => { + const cssClasses = [...baseDynamicClass.value]; + if (props.sizeModifier) { + cssClasses.push(`form-select-${props.sizeModifier}`); + } + return cssClasses; +}); +const inputDynamicClass = computed<string[]>(() => { + const cssClasses = [...baseDynamicClass.value]; + if (props.sizeModifier) { + cssClasses.push(`form-control-${props.sizeModifier}`); + } + if (!helpTextPresent.value) { + cssClasses.push("rounded-end"); + } + return cssClasses; +}); + +const s3Path = reactive<{ + bucket: string; + key?: string; +}>({ + bucket: "", + key: undefined, +}); + +const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); + +const foldersInBucket = computed<string[]>(() => + (s3ObjectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) + .filter((obj) => obj.Key != undefined) + .map((obj) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const parts = obj.Key!.split("/"); + return parts + .slice(0, parts.length - 1) + .map((part, index) => + parts.slice(0, index + 1).reduce((acc, val) => `${acc}/${val}`), + ); + }) + .flat() + .filter((val, index, array) => array.indexOf(val) === index), +); + +const filesInBucket = computed<string[]>(() => + (s3ObjectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) + .filter((obj) => !obj.Key?.endsWith("/")) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .map((obj) => obj.Key!), +); + +const filesAndFoldersInBucket = computed<string[]>(() => + filesInBucket.value.concat(foldersInBucket.value), +); + +const keyDataList = computed<string[]>(() => { + switch (props.parameter["format"]) { + case "file-path": + return filesInBucket.value; + case "directory-path": + return foldersInBucket.value; + case "path": + return filesAndFoldersInBucket.value; + default: + return []; + } +}); + +watch( + () => s3Path.key, + (newVal, oldVal) => { + if (newVal !== oldVal) { + updateModel(s3Path.bucket, newVal); + } + }, +); + +function updateBucket(bucket: string) { + s3Path.bucket = bucket; + updateModel(bucket, s3Path.key); + s3ObjectRepository.fetchS3Objects( + bucket, + bucketRepository.ownPermissions[bucket]?.file_prefix ?? undefined, + ); +} + +function updateModel(bucket: string, key?: string) { + model.value = !bucket ? undefined : `s3://${bucket}${key ? "/" + key : ""}`; +} +</script> + +<template> + <select + class="form-select" + :class="selectDynamicClass" + :required="props.required" + :value="s3Path.bucket" + @change=" + (event) => updateBucket((event.target as HTMLSelectElement)?.value) + " + > + <option selected disabled value="">Please select a bucket</option> + <option + v-for="bucket in bucketRepository.ownBucketsAndFullPermissions" + :key="bucket" + :value="bucket" + > + {{ bucket }} + </option> + </select> + <input + class="form-control" + :list="'keys-options-' + randomIDSuffix" + :class="inputDynamicClass" + placeholder="Type to search in bucket..." + :required="props.required && props.parameter['format'] === 'file-path'" + v-model="s3Path.key" + :pattern="props.parameter['pattern']" + /> + <datalist :id="'keys-options-' + randomIDSuffix"> + <option v-for="obj in keyDataList" :value="obj" :key="obj" /> + </datalist> +</template> + +<style scoped></style> diff --git a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue index 9d4b0f398b7cf79348714c6bcabdf6f013d0d74f..dc75ea44f2a1d6937b4167ca4ed50b781010aec2 100644 --- a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue +++ b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue @@ -1,12 +1,9 @@ <script setup lang="ts"> import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { computed, type PropType } from "vue"; -import ParameterNumberInput from "@/components/parameter-schema/form-mode/ParameterNumberInput.vue"; import MarkdownRenderer from "@/components/MarkdownRenderer.vue"; -import ParameterBooleanInput from "@/components/parameter-schema/form-mode/ParameterBooleanInput.vue"; -import ParameterEnumInput from "@/components/parameter-schema/form-mode/ParameterEnumInput.vue"; -import ParameterStringInput from "@/components/parameter-schema/form-mode/ParameterStringInput.vue"; import type { WorkflowParameters } from "@/types/WorkflowParameters"; +import ParameterInput from "@/components/parameter-schema/form-mode/ParameterInput.vue"; const model = defineModel<WorkflowParameters>({ required: true }); @@ -63,6 +60,10 @@ function parameterRequired( ?.reduce((acc: boolean, val: string) => acc || val, false) ); } + +function parameterId(parameterName: string): string { + return parameterName.replace(/\./g, ""); +} </script> <template> @@ -101,61 +102,19 @@ function parameterRequired( > <font-awesome-icon :icon="parameter['fa_icon']" /> </span> - <template - v-if=" - parameter['type'] === 'number' || - parameter['type'] === 'integer' - " - > - <!-- @vue-ignore --> - <parameter-number-input - :parameter-name="parameterName" - :parameter="parameter" - :help-id="parameterName + '-help'" - :required="parameterRequired(parameterGroup, parameterName)" - v-model="model[parameterName]" - /> - </template> - <template v-else-if="parameter['type'] === 'boolean'"> - <!-- @vue-ignore --> - <parameter-boolean-input - :parameter-name="parameterName" - :parameter="parameter" - :help-id="parameterName + '-help'" - v-model="model[parameterName]" - /> - </template> - <template v-else-if="parameter['type'] === 'string'"> - <!-- @vue-ignore --> - <template v-if="parameter['enum']"> - <!-- @vue-ignore --> - <parameter-enum-input - :parameter-name="parameterName" - :parameter="parameter" - :required="parameterRequired(parameterGroup, parameterName)" - v-model="model[parameterName]" - /> - </template> - <!-- @vue-ignore --> - <parameter-string-input - v-else - :parameter-name="parameterName" - :parameter="parameter" - :required="parameterRequired(parameterGroup, parameterName)" - v-model="model[parameterName]" - :remove-advanced="!showOptional" - :clowm-resource="resourceParameters?.includes(parameterName)" - /> - </template> + <parameter-input + :parameter="parameter" + v-model="model[parameterName]" + :required="parameterRequired(parameterGroup, parameterName)" + border + /> <span class="input-group-text cursor-pointer px-2 border border-secondary" v-if="parameter['help_text']" data-bs-toggle="collapse" - :data-bs-target=" - '#helpCollapse' + parameterName.replace(/\./g, '') - " + :data-bs-target="'#help-collapse-' + parameterId(parameterName)" aria-expanded="false" - :aria-controls="'helpCollapse' + parameterName.replace(/\./g, '')" + :aria-controls="'help-collapse-' + parameterId(parameterName)" > <font-awesome-icon class="cursor-pointer" @@ -168,7 +127,7 @@ function parameterRequired( </label> <div class="collapse" - :id="'helpCollapse' + parameterName.replace(/\./g, '')" + :id="'help-collapse-' + parameterId(parameterName)" v-if="parameter['help_text']" > <div class="p-2 pb-0 mx-2 mb-3 flex-shrink-1 border rounded"> diff --git a/src/components/parameter-schema/form-mode/ParameterInput.vue b/src/components/parameter-schema/form-mode/ParameterInput.vue new file mode 100644 index 0000000000000000000000000000000000000000..ca67de093b6fc7906b53971b925458583e2b672b --- /dev/null +++ b/src/components/parameter-schema/form-mode/ParameterInput.vue @@ -0,0 +1,87 @@ +<script setup lang="ts"> +import { computed } from "vue"; +import ParameterResourceInput from "@/components/parameter-schema/form-mode/ParameterResourceInput.vue"; +import ParameterNumberInput from "@/components/parameter-schema/form-mode/ParameterNumberInput.vue"; +import ParameterBooleanInput from "@/components/parameter-schema/form-mode/ParameterBooleanInput.vue"; +import ParameterEnumInput from "@/components/parameter-schema/form-mode/ParameterEnumInput.vue"; +import ParameterFileInput from "@/components/parameter-schema/form-mode/ParameterFileInput.vue"; +import ParameterStringInput from "@/components/parameter-schema/form-mode/ParameterStringInput.vue"; +import type { SizeModifierType } from "@/types/PropTypes"; +import type { ResourcePath_Input } from "@/client/workflow"; + +const model = defineModel< + number | string | boolean | ResourcePath_Input | undefined +>({ + required: true, +}); + +const props = defineProps<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + parameter: Record<string, any>; + required?: boolean; + resourceParameter?: boolean; + advanced?: boolean; + rawModel?: boolean; + sizeModifier?: SizeModifierType; + border?: boolean; +}>(); + +const parameterType = computed<string>( + () => props.parameter["type"] ?? "string", +); +</script> + +<template> + <parameter-number-input + v-if="parameterType === 'number' || parameterType === 'integer'" + :parameter="parameter" + v-model="model as number" + :required="required" + :size-modifier="sizeModifier" + :border="border" + /> + <parameter-boolean-input + v-else-if="parameterType === 'boolean'" + :parameter="parameter" + v-model="model as boolean" + :border="border" + /> + <parameter-enum-input + v-else-if="parameter['enum']" + :required="required" + :parameter="parameter" + v-model="model as string" + :size-modifier="sizeModifier" + :border="border" + /> + <template v-else> + <parameter-resource-input + v-if="resourceParameter && !advanced" + :parameter="parameter" + :required="required" + v-model="model as ResourcePath_Input" + :rawModel="rawModel" + :size-modifier="sizeModifier" + :border="border" + /> + <parameter-file-input + v-else-if="parameter['format'] && !advanced" + class="border border-secondary" + :required="required" + :parameter="parameter" + v-model="model as string" + :size-modifier="sizeModifier" + :border="border" + /> + <parameter-string-input + v-else + :parameter="parameter" + :required="required" + v-model="model as string" + :size-modifier="sizeModifier" + :border="border" + /> + </template> +</template> + +<style scoped></style> diff --git a/src/components/parameter-schema/form-mode/ParameterNumberInput.vue b/src/components/parameter-schema/form-mode/ParameterNumberInput.vue index 0cc7ca60dd195a5c999b6570d88af7e85bb3b117..194bcb291ab958e914a4efaed6955dac17a6f7c5 100644 --- a/src/components/parameter-schema/form-mode/ParameterNumberInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterNumberInput.vue @@ -1,4 +1,7 @@ <script setup lang="ts"> +import { computed, type PropType } from "vue"; +import type { SizeModifierType } from "@/types/PropTypes"; + const model = defineModel<number | undefined>({ required: true }); const props = defineProps({ @@ -10,19 +13,31 @@ const props = defineProps({ }, }, required: Boolean, - parameterName: { - type: String, - required: true, - }, helpId: { type: String, }, + sizeModifier: { + type: String as PropType<SizeModifierType>, + }, + border: Boolean, +}); + +const dynamicCssClasses = computed<string[]>(() => { + const cssClasses = []; + if (props.sizeModifier) { + cssClasses.push(`form-control-${props.sizeModifier}`); + } + if (props.border) { + cssClasses.push("border", "border-secondary"); + } + return cssClasses; }); </script> <template> <input - class="form-control border border-secondary" + class="form-control flex-fill" + :class="dynamicCssClasses" type="number" :max="props.parameter['maximum']" :min="props.parameter['minimum']" diff --git a/src/components/parameter-schema/form-mode/ParameterResourceInput.vue b/src/components/parameter-schema/form-mode/ParameterResourceInput.vue new file mode 100644 index 0000000000000000000000000000000000000000..4dfeaae237d9357d495e3db62a253142379efe68 --- /dev/null +++ b/src/components/parameter-schema/form-mode/ParameterResourceInput.vue @@ -0,0 +1,154 @@ +<script setup lang="ts"> +import type { ResourcePath_Input } from "@/client/workflow"; +import { Status } from "@/client/resource"; +import { computed, reactive, watch } from "vue"; +import { useResourceStore } from "@/stores/resources"; +import type { SizeModifierType } from "@/types/PropTypes"; + +const model = defineModel<string | ResourcePath_Input>({ + required: true, +}); + +const props = defineProps<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + parameter: Record<string, any>; + required?: boolean; + helpId?: string; + rawModel?: boolean; + sizeModifier?: SizeModifierType; + border?: boolean; +}>(); + +const resourceRepository = useResourceStore(); +const randomIDSuffix = Math.random().toString(16).substring(2, 8); + +const resource = reactive<ResourcePath_Input>({ + resource_id: "", + resource_version_id: "", + suffix: undefined, +}); + +const baseDynamicClass = computed<string[]>(() => + props.border ? ["border", "border-secondary"] : [], +); + +const selectDynamicClass = computed<string[]>(() => { + const cssClasses = [...baseDynamicClass.value]; + if (props.sizeModifier) { + cssClasses.push(`form-select-${props.sizeModifier}`); + } + return cssClasses; +}); +const inputDynamicClass = computed<string[]>(() => { + const cssClasses = [...baseDynamicClass.value]; + if (props.sizeModifier) { + cssClasses.push(`form-control-${props.sizeModifier}`); + } + return cssClasses; +}); + +function updateResourceId(rid: string) { + resource.resource_id = rid; + resource.resource_version_id = ""; + updateModel(); +} + +function updateResourceVersionId(rvid: string) { + resource.resource_version_id = rvid; + updateModel(); + resourceRepository.fetchResourceTree(resource.resource_id, rvid); +} + +watch( + () => resource.suffix, + (newVal, oldVal) => { + if (newVal !== oldVal) { + updateModel(); + } + }, +); + +function updateModel() { + if (props.rawModel) { + model.value = resource; + return; + } + if (resource.resource_version_id.length === 0) { + model.value = ""; + return; + } + let val = + resourceRepository.versionMapping[resource.resource_version_id] + ?.cluster_path ?? ""; + if (resource.suffix != undefined && val.length > 0) { + val = val + resource.suffix; + } + model.value = val; +} +</script> + +<template> + <select + class="form-select" + :class="selectDynamicClass" + :required="props.required" + :value="resource.resource_id" + @change=" + (event) => updateResourceId((event.target as HTMLSelectElement)?.value) + " + > + > + <option selected disabled value="">Please select a resource</option> + <option + v-for="resource in resourceRepository.resources" + :key="resource.resource_id" + :value="resource.resource_id" + > + {{ resource.name }} + </option> + </select> + <select + class="form-select" + :class="selectDynamicClass" + :required="resource.resource_id.length > 0" + :value="resource.resource_version_id" + @change=" + (event) => + updateResourceVersionId((event.target as HTMLSelectElement)?.value) + " + :disabled="resource.resource_id.length === 0" + > + <option disabled selected value="">Please select a version</option> + <option + v-for="version in resourceRepository.resourceMapping[resource.resource_id] + ?.versions ?? []" + :key="version.resource_version_id" + :value="version.resource_version_id" + > + {{ version.release }} + {{ version.status === Status.LATEST ? "- Latest" : "" }} + </option> + </select> + <input + type="text" + class="form-control rounded-end" + :class="inputDynamicClass" + placeholder="/optional/path/in/resource/..." + minlength="2" + maxlength="256" + pattern="\/\S*" + v-model="resource.suffix" + :list="'resource-tree-options-' + randomIDSuffix" + /> + <datalist :id="'resource-tree-options-' + randomIDSuffix"> + <option + v-for="file in resourceRepository.resourceTreeList[ + resource.resource_version_id + ] ?? []" + :value="file" + :key="file" + /> + </datalist> +</template> + +<style scoped></style> diff --git a/src/components/parameter-schema/form-mode/ParameterStringInput.vue b/src/components/parameter-schema/form-mode/ParameterStringInput.vue index a41cc04f692ad9733e06bba04c1fa0ae2697c061..85e6eee21e2c23f2c53a6f58bbb5b2cb855e520d 100644 --- a/src/components/parameter-schema/form-mode/ParameterStringInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterStringInput.vue @@ -1,15 +1,8 @@ <script setup lang="ts"> -import { computed, watch, onMounted, reactive, ref } from "vue"; -import { useBucketStore } from "@/stores/buckets"; -import { useS3ObjectStore } from "@/stores/s3objects"; -import { useResourceStore } from "@/stores/resources"; -import { Status } from "@/client/resource"; - -const bucketRepository = useBucketStore(); -const s3objectRepository = useS3ObjectStore(); -const resourceRepository = useResourceStore(); -const randomIDSuffix = Math.random().toString(16).substring(2, 8); +import { computed, type PropType, ref } from "vue"; +import type { SizeModifierType } from "@/types/PropTypes"; +const model = defineModel<string | undefined>({ required: true }); const props = defineProps({ parameter: { type: Object, @@ -19,273 +12,45 @@ const props = defineProps({ }, }, required: Boolean, - parameterName: { - type: String, - required: true, - }, - modelValue: { - type: String, - }, helpId: { type: String, }, - removeAdvanced: { - type: Boolean, - default: false, - }, - clowmResource: { - type: Boolean, - default: false, + sizeModifier: { + type: String as PropType<SizeModifierType>, }, + border: Boolean, }); -const emit = defineEmits<{ - (e: "update:modelValue", value?: string): void; -}>(); - -const s3Path = reactive<{ - bucket: string; - key?: string; -}>({ - bucket: "", - key: undefined, -}); - -const selectedResource = reactive<{ - resourceId: string; - resourceVersionIndex: number; -}>({ - resourceId: "", - resourceVersionIndex: 0, -}); - -const formState = reactive<{ - advancedInput: boolean; - stringVal?: string; -}>({ - advancedInput: false, - stringVal: undefined, -}); - -const stringInput = ref<HTMLInputElement | undefined>(undefined); -const pattern = computed<string>(() => props.parameter["pattern"]); -const dataFormat = computed<string | undefined>( - () => props.parameter["format"], -); -const dataPath = computed<boolean>(() => dataFormat.value != undefined); -const clowmResource = computed<boolean>(() => props.clowmResource ?? false); -const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); - -const filesInBucket = computed<string[]>(() => - (s3objectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) - .filter((obj) => !obj.Key?.endsWith("/")) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - .map((obj) => obj.Key!), -); - -const foldersInBucket = computed<string[]>(() => - (s3objectRepository.objectMapping[s3Path.bucket ?? ""] ?? []) - .filter((obj) => obj.Key != undefined) - .map((obj) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const parts = obj.Key!.split("/"); - return parts - .slice(0, parts.length - 1) - .map((part, index) => - parts.slice(0, index + 1).reduce((acc, val) => `${acc}/${val}`), - ); - }) - .flat() - .filter((val, index, array) => array.indexOf(val) === index), -); - -const filesAndFoldersInBucket = computed<string[]>(() => - filesInBucket.value.concat(foldersInBucket.value), -); - -const keyDataList = computed<string[]>(() => { - switch (dataFormat.value) { - case "file-path": - return filesInBucket.value; - case "directory-path": - return foldersInBucket.value; - case "path": - return filesAndFoldersInBucket.value; - default: - return []; +const dynamicCssClass = computed<string[]>(() => { + const cssClasses = []; + if (props.sizeModifier) { + cssClasses.push(`form-control-${props.sizeModifier}`); } -}); - -watch(s3Path, () => { - if (dataPath.value && !formState.advancedInput) { - updateStringFromS3(); + if (!helpTextPresent.value) { + cssClasses.push("rounded-end"); } -}); - -watch( - () => s3Path.bucket, - (newVal, oldVal) => { - if (newVal !== oldVal) { - updateKeysInBucket(newVal); - } - }, -); - -watch(() => formState.stringVal, updateValue); -watch(selectedResource, () => { - if (clowmResource.value && !formState.advancedInput) { - updateStringFromResource(); + if (props.border) { + cssClasses.push("border", "border-secondary"); } + return cssClasses; }); -watch( - () => props.modelValue, - (newVal) => { - if (formState.stringVal != newVal) { - formState.stringVal = newVal; - formState.advancedInput = true; - } - }, -); - -watch( - () => formState.advancedInput, - (newVal, oldValue) => { - if (newVal != oldValue && !newVal) { - if (clowmResource.value) { - updateStringFromResource(); - } else if (dataPath.value) { - updateStringFromS3(); - } - } - }, -); - -function updateValue() { - emit("update:modelValue", formState.stringVal); -} - -function updateStringFromS3() { - formState.stringVal = !s3Path.bucket - ? undefined - : `s3://${s3Path.bucket}${s3Path.key ? "/" + s3Path.key : ""}`; -} - -function updateStringFromResource() { - formState.stringVal = - resourceRepository.resourceMapping[selectedResource.resourceId]?.versions[ - selectedResource.resourceVersionIndex - ]?.cluster_path ?? undefined; -} - -function updateKeysInBucket(bucketName?: string) { - if (bucketName != null) { - s3objectRepository.fetchS3Objects( - bucketName, - bucketRepository.ownPermissions[bucketName]?.file_prefix ?? undefined, - ); - } -} - -onMounted(() => { - formState.stringVal = props.modelValue; - if (formState.stringVal) { - formState.advancedInput = true; - } -}); +const stringInput = ref<HTMLInputElement | undefined>(undefined); +const pattern = computed<string>(() => props.parameter["pattern"]); +const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); </script> <template> - <template v-if="clowmResource && !formState.advancedInput"> - <select - class="form-select border border-secondary" - :required="props.required" - v-model="selectedResource.resourceId" - > - <option selected disabled value="">Please select a resource</option> - <option - v-for="resource in resourceRepository.resources" - :key="resource.resource_id" - :value="resource.resource_id" - > - {{ resource.name }} - </option> - </select> - <select - class="form-select border border-secondary" - :class="{ 'rounded-end': props.removeAdvanced && !helpTextPresent }" - :required="props.required || selectedResource.resourceId != undefined" - v-model="selectedResource.resourceVersionIndex" - :disabled="selectedResource.resourceId.length === 0" - > - <option disabled>Please select a version</option> - <option - v-for="(version, index) in resourceRepository.resourceMapping[ - selectedResource.resourceId - ]?.versions ?? []" - :key="version.resource_version_id" - :value="index" - > - {{ version.release }} - {{ version.status === Status.LATEST ? "- Latest" : "" }} - </option> - </select> - </template> - <template v-else-if="dataPath && !formState.advancedInput"> - <select - class="form-select border border-secondary" - :required="props.required" - v-model="s3Path.bucket" - > - <option selected disabled value="">Please select a bucket</option> - <option - v-for="bucket in bucketRepository.ownBucketsAndFullPermissions" - :key="bucket" - :value="bucket" - > - {{ bucket }} - </option> - </select> - <input - class="form-control border-top border-secondary" - :list="'datalistOptions2' + randomIDSuffix" - :class="{ 'rounded-end': props.removeAdvanced && !helpTextPresent }" - placeholder="Type to search in bucket..." - :required="props.required && dataFormat === 'file-path'" - v-model="s3Path.key" - :pattern="pattern" - /> - <datalist :id="'datalistOptions2' + randomIDSuffix"> - <option v-for="obj in keyDataList" :value="obj" :key="obj" /> - </datalist> - </template> - <template v-else> - <input - ref="stringInput" - class="form-control border border-secondary" - :class="{ 'rounded-end': props.removeAdvanced && !helpTextPresent }" - type="text" - v-model="formState.stringVal" - :required="props.required" - :aria-describedby="props.helpId" - :pattern="pattern" - /> - </template> - <div v-if="(clowmResource || dataPath) && !props.removeAdvanced"> - <input - type="checkbox" - class="btn-check" - :id="'flexCheckDefault' + randomIDSuffix" - autocomplete="off" - v-model="formState.advancedInput" - /> - <label - class="btn btn-outline-secondary rounded-0" - :class="{ 'rounded-end': !helpTextPresent }" - :for="'flexCheckDefault' + randomIDSuffix" - >Advanced</label - > - </div> + <input + ref="stringInput" + class="form-control" + :class="dynamicCssClass" + type="text" + v-model="model" + :required="props.required" + :aria-describedby="props.helpId" + :pattern="pattern" + /> </template> <style scoped></style> diff --git a/src/components/resources/modals/UploadResourceInfoModal.vue b/src/components/resources/modals/UploadResourceInfoModal.vue index 3b33bd34cb50e96858aeb700eb73d2ff096c97fa..52174003972327cf7d4b87c4a355dda4d5785ddd 100644 --- a/src/components/resources/modals/UploadResourceInfoModal.vue +++ b/src/components/resources/modals/UploadResourceInfoModal.vue @@ -137,7 +137,7 @@ onMounted(() => { <bootstrap-modal :modalId="props.modalId" modal-label="Upload Resource Info Modal" - sizeModifier="lg" + sizeModifierModal="lg" :track-modal-value="resourceVersion?.resource_version_id" > <template #header>How to upload a resource to the cluster</template> diff --git a/src/stores/resources.ts b/src/stores/resources.ts index ee419a29020337957bbe9e866e777e4fc1148186..31cbaf40311e61851a07d07d677bc9ab16a244f6 100644 --- a/src/stores/resources.ts +++ b/src/stores/resources.ts @@ -56,6 +56,15 @@ export const useResourceStore = defineStore({ resources(): ResourceOut[] { return Object.values(this.resourceMapping); }, + versionMapping(): Record<string, ResourceVersionOut> { + const mapping: Record<string, ResourceVersionOut> = {}; + for (const resource of this.resources) { + for (const version of resource.versions) { + mapping[version.resource_version_id] = version; + } + } + return mapping; + }, ownResources(): ResourceOut[] { return Object.values(this.ownResourceMapping); }, diff --git a/src/types/PropTypes.ts b/src/types/PropTypes.ts new file mode 100644 index 0000000000000000000000000000000000000000..a937fe2c84e2fb896e30c3045fff6caf1f568f8f --- /dev/null +++ b/src/types/PropTypes.ts @@ -0,0 +1 @@ +export type SizeModifierType = "sm" | "lg"; diff --git a/src/views/workflows/CreateParameterTranslationView.vue b/src/views/workflows/CreateParameterTranslationView.vue index 6c025bb454eaec78f55c3d399a1850c65d5b9c2e..33f14ba17310f0019efe95fc4b78589a8c1d1512 100644 --- a/src/views/workflows/CreateParameterTranslationView.vue +++ b/src/views/workflows/CreateParameterTranslationView.vue @@ -5,11 +5,10 @@ import { computed, onMounted, reactive, ref, watch } from "vue"; import { DocumentationEnum, type ParameterExtension_Input, - type ResourcePath_Input, } from "@/client/workflow"; import type { ClowmInfo } from "@/types/ClowmInfo"; import { useResourceStore } from "@/stores/resources"; -import { Status } from "@/client/resource"; +import ParameterInput from "@/components/parameter-schema/form-mode/ParameterInput.vue"; const props = defineProps<{ versionId: string; @@ -141,7 +140,6 @@ function addDefaultParameter(param: string, index: number) { parameterState.extension.defaults[param] = { resource_id: "", resource_version_id: "", - suffix: "", }; break; } @@ -158,59 +156,23 @@ function addDefaultParameter(param: string, index: number) { } } -function updateDefaultResourceParam(param: string, resource_id?: string) { - if ( - parameterState.extension.defaults && - resource_id && - isResource(parameterState.extension.defaults[param]) - ) { - ( - parameterState.extension.defaults[param] as ResourcePath_Input - ).resource_version_id = ""; - ( - parameterState.extension.defaults[param] as ResourcePath_Input - ).resource_id = resource_id; - } -} - -function updateDefaultResourceVersionParam( - param: string, - resource_version_id?: string, -) { - if ( - parameterState.extension.defaults && - resource_version_id && - isResource(parameterState.extension.defaults[param]) - ) { - ( - parameterState.extension.defaults[param] as ResourcePath_Input - ).resource_version_id = resource_version_id; - resourceRepository.fetchResourceTree( - (parameterState.extension.defaults[param] as ResourcePath_Input) - .resource_id, - resource_version_id, - ); - } -} - -function isResource( - param: ResourcePath_Input | string | number | boolean | undefined, -): param is ResourcePath_Input { - return typeof param === "object"; -} - function makeResourceParameter(param: string) { parameterState.formValidated = false; parameterState.resourceParameters.add(param); parameterState.extension.defaults![param] = { resource_id: "", resource_version_id: "", - suffix: "", }; } function deleteDefaultParameter(param: string) { - parameterState.resourceParameters.delete(param); + if ( + !workflowRepository.documentationFiles[ + props.versionId + ]?.clowm_info?.resourceParameters?.includes(param) + ) { + parameterState.resourceParameters.delete(param); + } delete parameterState.extension.defaults?.[param]; parameterPools.defaults.push(param); if (Object.keys(parameterState.extension.defaults ?? {}).length === 0) { @@ -304,7 +266,7 @@ onMounted(() => { novalidate @submit.prevent="submitForm()" > - <table class="table table-bordered"> + <table class="table table-bordered align-middle"> <thead> <tr> <th scope="col"><b>Parameter</b></th> @@ -313,201 +275,34 @@ onMounted(() => { </thead> <tbody v-if="parameterState.extension.defaults" id="defaultParamsTable"> <tr - v-for="(param, index) in Object.keys( - parameterState.extension.defaults, - )" + v-for="param in Object.keys(parameterState.extension.defaults)" :key="param" > <td style="width: 10%">{{ param }}</td> <td class="d-flex justify-content-between align-items-center"> - <input + <div class="flex-fill input-group"> + <parameter-input + :parameter="parameterSchema[param]" + v-model="parameterState.extension.defaults[param]" + size-modifier="sm" + :resource-parameter=" + parameterState.resourceParameters.has(param) + " + :advanced="!parameterState.resourceParameters.has(param)" + raw-model + /> + </div> + <button v-if=" - getParameterType(param) === 'number' || - getParameterType(param) === 'integer' + !parameterState.resourceParameters.has(param) && + getParameterType(param) === 'string' " - type="number" - required - class="form-control form-control-sm flex-grow" - v-model="parameterState.extension.defaults[param]" - :step="getParameterType(param) === 'integer' ? 1 : 0.0001" - :min="parameterSchema[param]['minimum']" - :max="parameterSchema[param]['maximum']" - /> - <div - v-else-if="getParameterType(param) === 'boolean'" - class="flex-grow" - > - <div class="form-check form-check-inline"> - <label - class="form-check-label" - :for="'trueOption' + param.replace(/\./g, '')" - >True</label - > - <input - class="form-check-input" - type="radio" - :name="'inlineRadioOptions' + param.replace(/\./g, '')" - :id="'trueOption' + param.replace(/\./g, '')" - :value="true" - v-model="parameterState.extension.defaults[param]" - /> - </div> - <div class="form-check form-check-inline"> - <input - class="form-check-input" - type="radio" - :name="'inlineRadioOptions' + param.replace(/\./g, '')" - :id="'falseOption' + param.replace(/\./g, '')" - :value="false" - v-model="parameterState.extension.defaults[param]" - /> - <label - class="form-check-label" - :for="'falseOption' + param.replace(/\./g, '')" - >False</label - > - </div> - </div> - <select - v-else-if="parameterSchema[param]?.['enum']" - class="form-select form-select-sm flex-grow" - required - v-model="parameterState.extension.defaults[param]" + class="btn btn-primary btn-sm ms-2" + type="button" + @click="makeResourceParameter(param)" > - <option - v-for="option in parameterSchema[param]?.['enum']" - :key="option" - :value="option" - > - {{ option }} - </option> - </select> - <template v-else> - <div - class="input-group" - v-if="parameterState.resourceParameters.has(param)" - > - <select - class="form-select form-select-sm" - required - :value=" - ( - parameterState.extension.defaults[ - param - ] as ResourcePath_Input - ).resource_id - " - @change=" - (event) => - updateDefaultResourceParam( - param, - (event.target as HTMLSelectElement)?.value, - ) - " - > - > - <option selected disabled value=""> - Please select a resource - </option> - <option - v-for="resource in resourceRepository.resources" - :key="resource.resource_id" - :value="resource.resource_id" - > - {{ resource.name }} - </option> - </select> - <select - class="form-select form-select-sm" - required - :value=" - ( - parameterState.extension.defaults[ - param - ] as ResourcePath_Input - ).resource_version_id - " - @change=" - (event) => - updateDefaultResourceVersionParam( - param, - (event.target as HTMLSelectElement)?.value, - ) - " - :disabled=" - ( - parameterState.extension.defaults[ - param - ] as ResourcePath_Input - ).resource_id?.length === 0 - " - > - <option disabled selected value=""> - Please select a version - </option> - <option - v-for="version in resourceRepository.resourceMapping[ - ( - parameterState.extension.defaults[ - param - ] as ResourcePath_Input - ).resource_id - ]?.versions ?? []" - :key="version.resource_version_id" - :value="version.resource_version_id" - > - {{ version.release }} - {{ version.status === Status.LATEST ? "- Latest" : "" }} - </option> - </select> - <input - type="text" - class="form-control rounded-end form-control-sm" - placeholder="/optional/path/in/resource/..." - minlength="2" - maxlength="256" - pattern="\/\S*" - v-model=" - ( - parameterState.extension.defaults[ - param - ] as ResourcePath_Input - ).suffix - " - :list="'datalistOptions-' + index" - /> - <datalist :id="'datalistOptions-' + index"> - <option - v-for="file in resourceRepository.resourceTreeList[ - ( - parameterState.extension.defaults[ - param - ] as ResourcePath_Input - ).resource_version_id - ] ?? []" - :value="file" - :key="file" - /> - </datalist> - </div> - <template v-else> - <input - type="text" - class="form-control form-control-sm flex-grow" - v-model="parameterState.extension.defaults[param]" - :pattern="parameterSchema[param]?.['pattern']" - /> - <button - v-if="!parameterState.resourceParameters.has(param)" - class="btn btn-primary btn-sm ms-2" - type="button" - :id="'make-resource-param-' + param.replace(/\./g, '')" - @click="makeResourceParameter(param)" - > - Resource - </button> - </template> - </template> + Resource + </button> <button type="button" class="btn btn-outline-danger btn-sm ms-2"