Skip to content
Snippets Groups Projects
Select Git revision
  • 032c2f8fbd01462b46681c7ed56fbd28763c04da
  • main default protected
  • dev protected
  • Issue/3090-tosProblems
  • Issue/3178-iconColorBug
  • Issue/3176-addNewNFDI4INGLogo
  • Issue/3141-rdsNoLonga
  • Issue/3180-fixMetadataNotLoading
  • Issue/3177-resourceTypeDescriptionTexts
  • Issue/3160-deactivateDownloadForFolders
  • Issue/3111-fixLoadingGitLabResource
  • Issue/3133-subProjectsChanges
  • Issue/3139-dsnrw
  • Issue/3167-changeTextAndAddLink
  • Issue/3070-newIconsForResourceTypes
  • Issue/3145-redesignLoginPage
  • Issue/3093-moreInformationInTheDeletionEmails
  • Issue/3040-closeTokenWindowWithXButton
  • Issue/3152-fixResourceStore
  • Issue/xxxx-DuckDBTest
  • Issue/3152-fixUpdateRequestPayload
  • v3.19.1
  • v3.19.0
  • v3.18.0
  • v3.17.2
  • v3.17.1
  • v3.17.0
  • v3.16.1
  • v3.16.0
  • v3.15.6
  • v3.15.5
  • v3.15.4
  • v3.15.3
  • v3.15.2
  • v3.15.1
  • v3.15.0
  • v3.14.0
  • v3.13.1
  • v3.13.0
  • v3.12.0
  • v3.11.0
41 results

ResourceMetadata.vue

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    ResourceMetadata.vue 16.13 KiB
    <template>
      <div class="resourceMetadata">
        <!-- Form -->
        <b-form id="edit_form" @submit.stop.prevent="">
          <!-- Resource Name -->
          <CoscineFormGroup
            :mandatory="true"
            label-for="ResourceName"
            :label="$t('form.resource.resourceNameLabel')"
            :is-loading="isLoading"
            type="input"
          >
            <!-- number of characters -->
            <template #hint>
              {{ v$.resourceForUpdate.name.$model.length }} /
              {{ v$.resourceForUpdate.name.maxLength.$params.max }}
            </template>
            <b-form-input
              id="ResourceName"
              v-model="v$.resourceForUpdate.name.$model"
              :state="
                v$.resourceForUpdate.name.$dirty
                  ? !v$.resourceForUpdate.name.$error
                  : null
              "
              :placeholder="$t('form.resource.resourceName')"
              :maxlength="v$.resourceForUpdate.name.maxLength.$params.max"
              required
              :readonly="readonly"
              :disabled="readonly"
              @update:modelValue="translateResourceNameToDisplayName"
            />
            <div class="invalid-tooltip">
              {{
                $t("form.resource.resourceNameHelp", {
                  maxLength: v$.resourceForUpdate.name.maxLength.$params.max,
                })
              }}
            </div>
          </CoscineFormGroup>
    
          <!-- Display Name -->
          <CoscineFormGroup
            :mandatory="true"
            label-for="DisplayName"
            :label="$t('form.resource.displayNameLabel')"
            :is-loading="isLoading"
            type="input"
          >
            <!-- number of characters -->
            <template #hint>
              {{ resourceForUpdate.displayName?.length ?? 0 }} /
              {{ v$.resourceForUpdate.displayName.maxLength.$params.max }}
            </template>
            <b-form-input
              id="DisplayName"
              v-model="v$.resourceForUpdate.displayName.$model"
              :state="
                v$.resourceForUpdate.displayName.$dirty
                  ? !v$.resourceForUpdate.displayName.$error
                  : null
              "
              :placeholder="$t('form.resource.displayName')"
              :maxlength="v$.resourceForUpdate.displayName.maxLength.$params.max"
              required
              :readonly="readonly"
              :disabled="readonly"
              @update:modelValue="isLockedDisplayName = true"
            />
            <div class="invalid-tooltip">
              {{
                $t("form.resource.displayNameHelp", {
                  maxLength: v$.resourceForUpdate.displayName.maxLength.$params.max,
                })
              }}
            </div>
          </CoscineFormGroup>
    
          <!-- Description -->
          <CoscineFormGroup
            :mandatory="true"
            label-for="Description"
            :label="$t('form.resource.resourceDescriptionLabel')"
            :is-loading="isLoading"
            type="input"
          >
            <!-- number of characters -->
            <template #hint>
              {{ v$.resourceForUpdate.description.$model.length }} /
              {{ v$.resourceForUpdate.description.maxLength.$params.max }}
            </template>
            <b-form-textarea
              id="Description"
              v-model="v$.resourceForUpdate.description.$model"
              :state="
                v$.resourceForUpdate.description.$dirty
                  ? !v$.resourceForUpdate.description.$error
                  : null
              "
              :placeholder="$t('form.resource.resourceDescription')"
              :maxlength="v$.resourceForUpdate.description.maxLength.$params.max"
              required
              :readonly="readonly"
              :disabled="readonly"
            />
            <div class="invalid-tooltip">
              {{
                $t("form.resource.resourceDescriptionHelp", {
                  maxLength: v$.resourceForUpdate.description.maxLength.$params.max,
                })
              }}
            </div>
          </CoscineFormGroup>
    
          <!-- Discipline -->
          <CoscineFormGroup
            :mandatory="true"
            label-for="Discipline"
            :label="$t('form.resource.resourceDisciplineLabel')"
            :is-loading="isLoading"
          >
            <multiselect
              id="Discipline"
              v-model="v$.resourceForUpdate.disciplines.$model"
              :options="disciplines"
              :multiple="true"
              :hide-selected="true"
              :label="disciplineLabel"
              track-by="id"
              :show-labels="false"
              :placeholder="$t('form.resource.resourceDiscipline')"
              :disabled="readonly"
            >
              <template #singleLabel="multiProps">
                <div>
                  {{ multiProps.option[disciplineLabel] }}
                </div>
              </template>
              <template #option="multiProps">
                <div>
                  {{ multiProps.option[disciplineLabel] }}
                </div>
              </template>
            </multiselect>
          </CoscineFormGroup>
    
          <!-- Keywords -->
          <CoscineFormGroup
            label-for="Keywords"
            :label="$t('form.resource.resourceKeywordsLabel')"
            :is-loading="isLoading"
          >
            <multiselect
              id="Keywords"
              v-model="v$.resourceForUpdate.keywords.$model"
              :options="resourceForUpdate.keywords"
              :placeholder="$t('form.resource.resourceKeywordsPlaceholder')"
              :multiple="true"
              :taggable="true"
              :max="limitKeywords(resourceForUpdate.keywords)"
              :tag-placeholder="$t('form.resource.tagPlaceholder')"
              :disabled="readonly"
              :show-labels="false"
              @tag="addTag"
              @remove="v$.resourceForUpdate.keywords.$touch()"
            >
              <template #maxElements>
                {{
                  $t("form.resource.resourceKeywordsHelp", {
                    maxLength: v$.resourceForUpdate.keywords.maxLength.$params.max,
                  })
                }}
              </template>
              <template #noOptions>
                {{
                  $t("form.resource.resourceKeywordsEmpty", {
                    maxLength: v$.resourceForUpdate.keywords.maxLength.$params.max,
                  })
                }}
              </template>
            </multiselect>
          </CoscineFormGroup>
    
          <!-- Visibility -->
          <CoscineFormGroup
            :mandatory="true"
            label-for="Visibility"
            :label="$t('form.resource.resourceMetadataVisibilityLabel')"
            :is-loading="isLoading"
            info
          >
            <template #popover>
              {{ $t("form.resource.resourceMetadataPopover") }}
              <b-link
                :href="$t('form.resource.resourceMetadataPopoverUrl').toString()"
                target="_blank"
              >
                {{ $t("default.help") }}
              </b-link>
            </template>
    
            <b-form-radio-group
              v-model="selectedVisibility"
              name="radios-stacked"
              :options="visibilities"
              text-field="displayName"
              value-field="id"
              stacked
              :disabled="readonly"
              @update:modelValue="setVisibility(selectedVisibility)"
            />
          </CoscineFormGroup>
    
          <!-- License -->
          <CoscineFormGroup
            label-for="ResourceLicense"
            :label="$t('form.resource.resourceLicenseLabel')"
            :is-loading="isLoading"
            info
          >
            <template #popover>
              {{ $t("form.resource.resourceLicensePopover") }}
              <b-link
                :href="$t('form.resource.resourceLicensePopoverUrl').toString()"
                target="_blank"
              >
                {{ $t("default.help") }}
              </b-link>
            </template>
            <b-form-select
              id="ResourceLicense"
              v-model="selectedLicense"
              :options="licenses"
              text-field="displayName"
              value-field="id"
              :state="
                v$.resourceForUpdate.license.$dirty && resourceForUpdate.license
                  ? !v$.resourceForUpdate.license.$error
                  : null
              "
              :placeholder="$t('form.resource.resourceLicense')"
              :disabled="readonly"
              @update:model-value="setLicense(selectedLicense)"
            >
              <template #first>
                <option :value="null">
                  {{ $t("form.resource.resourceLicenseSelect") }}
                </option>
              </template>
            </b-form-select>
          </CoscineFormGroup>
    
          <!-- Internal Rules for Reuse -->
          <CoscineFormGroup
            label-for="InternalRulesForReuse"
            :label="$t('form.resource.resourceReuseLabel').toString()"
            :is-loading="isLoading"
            info
          >
            <template #popover>
              {{ $t("form.resource.resourceReusePopover") }}
              <b-link
                :href="$t('form.resource.resourceReusePopoverUrl').toString()"
                target="_blank"
                >{{ $t("default.help") }}
              </b-link>
            </template>
            <template #hint>
              {{ v$.resourceForUpdate.usageRights.$model.length }} /
              {{ v$.resourceForUpdate.usageRights.maxLength.$params.max }}
            </template>
            <b-form-input
              id="InternalRulesForReuse"
              v-model="v$.resourceForUpdate.usageRights.$model"
              :state="
                v$.resourceForUpdate.usageRights.$dirty
                  ? !v$.resourceForUpdate.usageRights.$error
                  : null
              "
              :placeholder="$t('form.resource.resourceReuse')"
              :maxlength="v$.resourceForUpdate.usageRights.maxLength.$params.max"
              required
              :readonly="readonly"
              :disabled="readonly"
            />
            <div class="invalid-tooltip">
              {{
                $t("form.resource.resourceReuseHelp", {
                  maxLength: v$.resourceForUpdate.usageRights.maxLength.$params.max,
                })
              }}
            </div>
          </CoscineFormGroup>
        </b-form>
      </div>
    </template>
    
    <script lang="ts" setup>
    import useProjectStore from "@/modules/project/store";
    
    import { useVuelidate, type BaseValidation } from "@vuelidate/core";
    import { required, maxLength } from "@vuelidate/validators";
    import type {
      DisciplineDto,
      DisciplineForResourceManipulationDto,
      LicenseForResourceManipulationDto,
      ProjectDto,
      ResourceForCreationDto,
      ResourceForUpdateDto,
    } from "@coscine/api-client/dist/types/Coscine.Api";
    
    import { useI18n } from "vue-i18n";
    const i18n = useI18n();
    
    const resourceForUpdate = defineModel<
      ResourceForCreationDto | ResourceForUpdateDto
    >({ required: true });
    
    defineProps({
      isLoading: {
        default: false,
        type: Boolean,
      },
      readonly: {
        default: false,
        type: Boolean,
      },
    });
    
    const emit = defineEmits<{
      valid: [_: boolean];
      validation: [
        _: BaseValidation<ResourceForCreationDto | ResourceForUpdateDto>,
      ];
      input: [_: ResourceForCreationDto | ResourceForUpdateDto];
    }>();
    
    const projectStore = useProjectStore();
    
    /*
      Definition of the validation rules and initial state
      will enable proper typings in the code
    */
    const state = reactive({
      resourceForUpdate: resourceForUpdate,
    });
    const rules = {
      resourceForUpdate: {
        name: { required, maxLength: maxLength(200) },
        displayName: { required, maxLength: maxLength(25) },
        description: { required, maxLength: maxLength(10000) },
        disciplines: { required },
        keywords: { maxLength: maxLength(1000) },
        license: {},
        visibility: { required },
        usageRights: { maxLength: maxLength(200) },
      },
    };
    const v$ = useVuelidate(rules, state);
    
    const selectedLicense = ref(null as string | null);
    const selectedVisibility = ref("" as string | undefined);
    const isLockedDisplayName = ref(false);
    
    const project = computed(() => {
      return projectStore.currentProject;
    });
    const visibilities = computed(() => {
      return projectStore.visibilities ?? [];
    });
    const licenses = computed(() => {
      return projectStore.licenses ?? [];
    });
    const disciplines = computed(() => {
      return projectStore.disciplines ?? [];
    });
    const disciplineLabel = computed(() => {
      let locale = i18n.locale.value;
      locale = locale.charAt(0).toUpperCase() + locale.slice(1);
      return `displayName${locale}`;
    });
    
    watch(
      () => visibilities.value,
      () => {
        // Used in Create Resource
        if (!resourceForUpdate.value.visibility) {
          setDefaultVisibility();
        }
      },
    );
    
    watch(
      () => v$.value.resourceForUpdate,
      () => {
        emit("validation", v$.value.resourceForUpdate);
      },
      { deep: true },
    );
    
    watch(
      () => v$.value.resourceForUpdate.$invalid,
      () => {
        emit("valid", !v$.value.resourceForUpdate.$invalid);
      },
    );
    
    /**
     * Initialize tab content with project, keywords, license, and visibility details.
     * If no visibility set, then set default visibility
     */
    const initTabContent = () => {
      if (project.value) {
        loadSelectedDisciplines(project.value);
      }
      if (resourceForUpdate.value.license?.id) {
        selectedLicense.value = resourceForUpdate.value.license.id;
      }
      if (resourceForUpdate.value.visibility?.id) {
        selectedVisibility.value = resourceForUpdate.value.visibility.id;
        setVisibility(selectedVisibility.value);
      } else {
        setDefaultVisibility();
      }
    };
    
    /**
     * Limits the number of keywords based on validation
     * @param {string[]} values - array of keyword strings
     * @return {number | null} - returns the current length of values if the keyword validation fails, else null
     */
    const limitKeywords = (values: string[] | null | undefined): number | null => {
      // Should the max number of allowed characters is exceeded, prevent the addition of further tags
      if (v$.value.resourceForUpdate.keywords?.$invalid) {
        return values?.length ?? null;
      } else {
        return null;
      }
    };
    
    /**
     * Add a new tag to the selected keywords
     * @param {string} newTag - new tag to be added to the keywords
     */
    const addTag = (newTag: string) => {
      resourceForUpdate.value.keywords?.push(newTag);
      v$.value.resourceForUpdate.keywords?.$touch();
    };
    
    /**
     * Load selected disciplines for the given project, adding project disciplines if no disciplines currently selected for resource
     * @param {ProjectDto} project - project data transfer object
     */
    const loadSelectedDisciplines = (project: ProjectDto) => {
      let fromProject = [] as DisciplineDto[];
      // When there are no disciplines selected, take the disciplines provided by the project
      if (
        !resourceForUpdate.value.disciplines ||
        resourceForUpdate.value.disciplines.length == 0
      ) {
        fromProject = project.disciplines ? project.disciplines : [];
      }
      const fromResource = resourceForUpdate.value.disciplines
        ? resourceForUpdate.value.disciplines
        : [];
      const disciplinesForManipulation = [...fromProject, ...fromResource].filter(
        (discipline) => discipline.id !== undefined,
      ) as DisciplineForResourceManipulationDto[];
      resourceForUpdate.value.disciplines = disciplinesForManipulation;
    };
    
    /**
     * Set visibility for the resource form
     * @param {string | undefined} visibilityId - ID of the visibility option to be set
     */
    const setVisibility = (visibilityId: string | undefined) => {
      if (visibilityId) {
        resourceForUpdate.value.visibility = {
          id: visibilityId,
        };
        v$.value.resourceForUpdate.visibility.$touch();
      }
    };
    
    /**
     * Set license for the resource form
     * @param {string | null} id - ID of the license to be set
     */
    const setLicense = (id: string | null) => {
      if (licenses.value && id) {
        const license = licenses.value.find((entry) => entry.id === id);
    
        resourceForUpdate.value.license = license?.id
          ? ({ id: license?.id } as LicenseForResourceManipulationDto)
          : undefined;
        v$.value.resourceForUpdate.license?.$touch();
      } else if (!id) {
        resourceForUpdate.value.license = undefined;
      }
    };
    
    /**
     * Set default visibility option for the resource form
     */
    const setDefaultVisibility = () => {
      if (visibilities.value) {
        const visibility = visibilities.value.find(
          (entry) => entry.displayName === "Project Members",
        );
    
        if (visibility?.id) {
          selectedVisibility.value = visibility.id;
        } else {
          // If visibility not found or has no id, select the first visibility from the array visibilities.value
          if (visibilities.value.length > 0) {
            selectedVisibility.value = visibilities.value[0].id;
          }
        }
        // If selectedVisibility is undefined, assign an empty string
        setVisibility(selectedVisibility.value);
      }
    };
    
    /**
     * Translate the resource name into display name if not locked and assign the first 25 characters
     */
    const translateResourceNameToDisplayName = () => {
      if (!isLockedDisplayName.value && resourceForUpdate.value.name) {
        resourceForUpdate.value.displayName =
          resourceForUpdate.value.name.substring(0, 25);
        v$.value.resourceForUpdate.displayName?.$touch();
      }
    };
    
    initTabContent();
    </script>