Skip to content
Snippets Groups Projects
ParameterResourceInput.vue 5.83 KiB
<script setup lang="ts">
import { Status } from "@/client/resource";
import { computed, onMounted, reactive, watch } from "vue";
import { useResourceStore } from "@/stores/resources";
import type { ExtendedColors, SizeModifierType } from "@/types/PropTypes";

const model = defineModel<string | undefined>({
  required: true,
});

const resourceRegex = /CLDB-([\da-f]{32})\/(latest|[\da-f]{32})([/\S]*)/g;

const props = defineProps<{
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  parameter: Record<string, any>;
  required?: boolean;
  sizeModifier?: SizeModifierType;
  border?: ExtendedColors;
  allowRaw?: boolean;
}>();

const emit = defineEmits<{
  (e: "switch-to-raw"): void;
}>();

const resourceRepository = useResourceStore();
const randomIDSuffix = Math.random().toString(16).substring(2, 8);

type ResourcePath = {
  resource_id: string;
  resource_version_id: string;
  suffix?: string;
};

const resource = reactive<ResourcePath>({
  resource_id: "",
  resource_version_id: "",
  suffix: undefined,
});

const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]);

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;
});

function updateResourceId(rid: string) {
  resource.resource_id = rid;
  resource.resource_version_id = "";
  model.value = translateToModel();
}

function updateResourceVersionId(rvid: string) {
  resource.resource_version_id = rvid;
  model.value = translateToModel();
  resourceRepository.fetchResourceTree(resource.resource_id, rvid);
}

watch(model, (newVal, oldVal) => {
  if (newVal != oldVal && newVal !== translateToModel()) {
    parseModel(newVal);
  }
});

function parseModel(val?: string) {
  if (val == undefined || val.length === 0) {
    Object.assign(resource, {
      resource_id: "",
      resource_version_id: "",
      suffix: undefined,
    });
  } else {
    const match = resourceRegex.exec(val);
    if (match) {
      const tempResource: ResourcePath = {
        resource_id: "",
        resource_version_id: "",
      };
      tempResource.resource_id = hexToUUID(match[1]);
      tempResource.suffix = match[3];
      tempResource.resource_version_id =
        match[2].length === 32
          ? hexToUUID(match[2])
          : resourceRepository.getLatestVersion(resource.resource_id);
      if (
        resourceRepository.resourceMapping[tempResource.resource_id] ==
          undefined ||
        resourceRepository.versionMapping[tempResource.resource_version_id] ==
          undefined
      ) {
        // Missing resource
        emit("switch-to-raw");
        return;
      }
      Object.assign(resource, tempResource);
    } else {
      // Not resource path
      emit("switch-to-raw");
    }
  }
}

function hexToUUID(hex?: string): string {
  if (hex) {
    return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
  }
  return "";
}

watch(
  () => resource.suffix,
  (newVal, oldVal) => {
    if (newVal !== oldVal) {
      model.value = translateToModel();
    }
  },
);

function translateToModel(): string {
  if (resource.resource_version_id.length === 0) {
    return "";
  }
  let val =
    resourceRepository.versionMapping[resource.resource_version_id]
      ?.cluster_path ?? "";
  if (resource.suffix != undefined && val.length > 0) {
    val = val + resource.suffix;
  }
  return val;
}

onMounted(() => {
  parseModel(model.value);
});
</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"
    :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>
  <button
    v-if="allowRaw"
    type="button"
    class="btn btn-outline-secondary"
    @click="emit('switch-to-raw')"
  >
    Advanced
  </button>
</template>

<style scoped></style>