<script setup lang="ts"> import { computed, onMounted, 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 s3Regex = /s3:\/\/(\S*)\/(\S*)/g; 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; }); watch(model, (newVal, oldVal) => { if ( newVal != oldVal && newVal !== translateToModel(s3Path.bucket, s3Path.key) ) { parseModel(newVal); } }); function parseModel(val?: string) { if (val == undefined) { s3Path.bucket = ""; s3Path.key = undefined; return; } const match = s3Regex.exec(val ?? ""); if (match) { s3Path.bucket = match[1]; s3Path.key = match[2]; if (bucketRepository.bucketMapping[s3Path.bucket] == undefined) { console.log("Missing bucket"); } } else { console.log("Not S3 Path"); } } 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) { model.value = translateToModel(s3Path.bucket, newVal); } }, ); function updateBucket(bucket: string) { s3Path.bucket = bucket; model.value = translateToModel(bucket, s3Path.key); s3ObjectRepository.fetchS3Objects( bucket, bucketRepository.ownPermissions[bucket]?.file_prefix ?? undefined, ); } function translateToModel(bucket: string, key?: string): string | undefined { return !bucket ? undefined : `s3://${bucket}${key ? "/" + key : ""}`; } onMounted(() => { parseModel(model.value); }); </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>