Skip to content
Snippets Groups Projects
ParameterFileInput.vue 4.92 KiB
<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>