Skip to content
Snippets Groups Projects

Resolve "Add UI for parameter translation layer"

Merged Daniel Göbel requested to merge feature/105-add-ui-for-parameter-translation-layer into main
4 files
+ 108
17
Compare changes
  • Side-by-side
  • Inline
Files
4
<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 { ExtendedColors, 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: String as PropType<ExtendedColors>,
allowRaw: Boolean,
});
const emit = defineEmits<{
(e: "switch-to-raw"): void;
}>();
const s3ObjectRepository = useS3ObjectStore();
const bucketRepository = useBucketStore();
const randomIDSuffix = Math.random().toString(16).substring(2, 8);
const baseDynamicClass = computed<string[]>(() =>
props.border ? ["border", `border-${props.border}`] : [],
);
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 && !props.allowRaw) {
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 || val.length === 0) {
s3Path.bucket = "";
s3Path.key = undefined;
return;
}
const match = s3Regex.exec(val ?? "");
if (match) {
s3Path.bucket = match[1];
s3Path.key = match[2]?.slice(1);
if (bucketRepository.bucketMapping[s3Path.bucket] == undefined) {
// Missing bucket
emit("switch-to-raw");
}
} else {
// Not S3 Path
emit("switch-to-raw");
}
}
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>
<button
v-if="allowRaw"
type="button"
class="btn btn-outline-secondary"
@click="emit('switch-to-raw')"
>
Raw
</button>
</template>
<style scoped></style>
Loading