Select Git revision
ResourceMetadata.vue
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>