-
Petar Hristov authoredPetar Hristov authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
FormMetadata.vue 16.15 KiB
<template>
<div>
<!-- Project Metadata Section -->
<CoscineHeadline :headline="$t('form.project.activatedImportFromParent')" />
<!-- Copy Metadata -->
<coscine-form-group
v-if="parentProject"
:mandatory="false"
label-for="CopyData"
:label="
$t('form.project.copyMetadataLabel', {
project: parentProject.projectName,
})
"
:is-loading="isLoading"
type="button"
class="d-flex align-items-center"
>
<b-button
id="project_copy_button"
variant="secondary"
size="sm"
:disabled="disabled"
@click.prevent="copyMetadataFromParent"
>
{{ $t("buttons.copyMetadata") }}</b-button
>
</coscine-form-group>
<!-- Principal Investigators -->
<coscine-form-group
:mandatory="true"
label-for="PrincipleInvestigators"
:label="$t('form.project.projectPrincipleInvestigatorsLabel')"
:is-loading="isLoading"
type="input"
>
<b-form-input
id="PrincipleInvestigators"
v-model="$v.projectForm.principleInvestigators.$model"
:state="
$v.projectForm.principleInvestigators.$dirty
? !$v.projectForm.principleInvestigators.$error
: null
"
:placeholder="$t('form.project.projectPrincipleInvestigators')"
:maxlength="$v.projectForm.principleInvestigators.maxLength.$params.max"
required
:disabled="disabled"
/>
<div class="invalid-tooltip">
{{
$t("form.project.projectPrincipleInvestigatorsHelp", {
maxLength:
$v.projectForm.principleInvestigators.maxLength.$params.max,
})
}}
</div>
</coscine-form-group>
<!-- Project Start -->
<coscine-form-group
:mandatory="true"
label-for="StartDate"
:label="$t('form.project.projectStartLabel')"
:is-loading="isLoading"
type="input"
>
<b-form-datepicker
id="StartDate"
v-model="$v.projectForm.startDate.$model"
:locale="$i18n.locale"
:disabled="disabled"
:state="
$v.projectForm.startDate.$dirty
? !$v.projectForm.startDate.$error
: null
"
required
calendar-width="100%"
start-weekday="1"
:placeholder="$t('form.project.projectStart')"
/>
</coscine-form-group>
<!-- Project End -->
<coscine-form-group
:mandatory="true"
label-for="EndDate"
:label="$t('form.project.projectEndLabel')"
:is-loading="isLoading"
type="input"
>
<b-form-datepicker
id="EndDate"
v-model="$v.projectForm.endDate.$model"
:locale="$i18n.locale"
:min="$v.projectForm.startDate.$model"
:disabled="disabled"
:state="
$v.projectForm.endDate.$dirty ? !$v.projectForm.endDate.$error : null
"
required
calendar-width="100%"
start-weekday="1"
:placeholder="$t('form.project.projectEnd')"
/>
</coscine-form-group>
<!-- Discipline -->
<coscine-form-group
:mandatory="true"
label-for="Discipline"
:label="$t('form.project.projectDisciplineLabel')"
:is-loading="isLoading"
type="input"
>
<multiselect
id="Discipline"
v-model="$v.projectForm.disciplines.$model"
:disabled="disabled"
:options="disciplines"
:multiple="true"
:hide-selected="true"
:label="disciplineLabel"
:track-by="disciplineLabel"
:placeholder="$t('form.project.projectDiscipline')"
:show-labels="false"
>
<template #singleLabel(props)>
<div :disabled="disabled">
{{ props.option[disciplineLabel] }}
</div>
</template>
<template #option(props)>
<div :disabled="disabled">
{{ props.option[disciplineLabel] }}
</div>
</template>
</multiselect>
</coscine-form-group>
<!-- Participating Organizations -->
<coscine-form-group
:mandatory="true"
label-for="Organization"
:label="$t('form.project.projectOrganizationLabel')"
:is-loading="isLoading"
type="input"
>
<multiselect
id="Organization"
v-model="$v.projectForm.organizations.$model"
:disabled="disabled"
:options="organizations"
:multiple="true"
:loading="isLoadingOrganizations"
:hide-selected="true"
label="displayName"
:show-labels="false"
track-by="url"
:placeholder="$t('form.project.projectOrganization')"
@search-change="retrieveOrganizations"
>
<template #singleLabel(props)>
<div :disabled="disabled">
{{ props.option.displayName }}
</div>
</template>
<template #option(props)>
<div :disabled="disabled">
{{ props.option.displayName }}
</div>
</template>
<template #noOptions>
{{ $t("form.project.projectOrganizationNoOptions") }}
</template>
<template #noResult>
{{ $t("form.project.projectOrganizationNoResult") }}
</template>
</multiselect>
</coscine-form-group>
<!-- Project Keywords -->
<coscine-form-group
label-for="Keywords"
:label="$t('form.project.projectKeywordsLabel')"
:is-loading="isLoading"
type="input"
>
<multiselect
id="Keywords"
v-model="selectedKeyword"
:disabled="disabled"
:options="selectedKeyword"
:placeholder="$t('form.project.projectKeywordsPlaceholder')"
:multiple="true"
:taggable="true"
:max="limitKeywords(selectedKeyword)"
:tag-placeholder="$t('form.project.tagPlaceholder')"
:show-labels="false"
@tag="addTag"
@remove="$v.projectForm.keywords.$touch()"
>
<template #maxElements>
{{
$t("form.project.projectKeywordsHelp", {
maxLength: $v.projectForm.keywords.maxLength.$params.max,
})
}}
</template>
<template #noOptions>
{{
$t("form.project.projectKeywordsEmpty", {
maxLength: $v.projectForm.keywords.maxLength.$params.max,
})
}}
</template>
</multiselect>
</coscine-form-group>
<!-- Visibility -->
<coscine-form-group
:mandatory="true"
label-for="Visibility"
:label="$t('form.project.projectMetadataVisibilityLabel')"
:is-loading="isLoading"
type="input"
:info="true"
>
<template #popover>
{{ $t("form.project.projectMetadataVisibilityLabelPopover") }}
<b-link
:href="
$t(
'form.project.projectMetadataVisibilityLabelPopoverUrl'
).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="disabled"
@change="setVisibility(selectedVisibility)"
/>
</coscine-form-group>
<!-- Grant ID -->
<coscine-form-group
label-for="GrantId"
:label="$t('form.project.projectGrantIdLabel')"
:is-loading="isLoading"
type="input"
>
<b-form-input
id="GrantId"
v-model="$v.projectForm.grantId.$model"
:state="
$v.projectForm.grantId.$dirty ? !$v.projectForm.grantId.$error : null
"
:placeholder="$t('form.project.projectGrantId')"
:maxlength="$v.projectForm.grantId.maxLength.$params.max"
required
:disabled="disabled"
/>
<div class="invalid-tooltip">
{{
$t("form.project.projectGrantIdHelp", {
maxLength: $v.projectForm.grantId.maxLength.$params.max,
})
}}
</div>
</coscine-form-group>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, reactive, ref } from "vue-demi";
import { required, maxLength } from "@vuelidate/validators";
import { useVuelidate, Validation, ValidationArgs } from "@vuelidate/core";
import moment from "moment";
import "@/plugins/deprecated/vue-multiselect";
import type {
DisciplineObject,
OrganizationObject,
ProjectObject,
VisibilityObject,
} from "@coscine/api-client/dist/types/Coscine.Api.Project";
// import the store for current module
import useProjectStore from "../../store";
// import the main store
import useMainStore from "@/store/index";
export default defineComponent({
props: {
value: {
type: Object as PropType<ProjectObject>,
required: true,
},
currentProject: {
default: null,
type: [Object, null] as PropType<ProjectObject | null>,
required: false,
},
parentProject: {
default: null,
type: [Object, null] as PropType<ProjectObject | null>,
required: false,
},
disabled: {
default: false,
type: Boolean,
},
isLoading: {
default: false,
type: Boolean,
},
},
emits: {
validation: (_: ValidationArgs) => null,
},
setup(props) {
const mainStore = useMainStore();
const projectStore = useProjectStore();
/*
Definition of the validation rules and initial state
will enable proper typings in the code
*/
const state = reactive({
projectForm: ref(props.value),
});
const rules = {
projectForm: {
principleInvestigators: { required, maxLength: maxLength(500) },
startDate: { required },
endDate: { required },
disciplines: { required },
organizations: { required },
keywords: { maxLength: maxLength(1000) },
visibility: { required },
grantId: { maxLength: maxLength(500) },
},
};
const $v = useVuelidate(rules, state) as Validation<typeof rules>;
return { mainStore, projectStore, $v };
},
data() {
return {
projectForm: this.value,
isLoadingOrganizations: false,
selectedKeyword: [] as Array<string>,
selectedVisibility: "" as string | undefined,
queryTimer: 0,
};
},
computed: {
organizations(): OrganizationObject[] {
return (
this.projectStore.organizations ??
this.projectStore.defaultOrganizations
);
},
visibilities(): VisibilityObject[] | null {
return this.projectStore.visibilities;
},
disciplines(): DisciplineObject[] {
return this.projectStore.disciplines ?? [];
},
disciplineLabel(): string {
let locale = this.$root.$i18n.locale;
locale = locale.charAt(0).toUpperCase() + locale.slice(1);
return `displayName${locale}`;
},
},
watch: {
currentProject() {
this.onProjectLoaded();
},
parentProject() {
this.onParentProjectLoaded();
},
visibilities() {
// Used in Project Create
if (!this.currentProject) {
this.setDefaultVisibility();
}
},
selectedKeyword() {
const delimiter = ";";
let keywords = "";
this.selectedKeyword.forEach((element) => {
keywords += `${element}${delimiter}`;
});
if (keywords.charAt(keywords.length - 1) === delimiter) {
keywords = keywords.substring(0, keywords.length - 1);
}
this.projectForm.keywords = keywords;
},
"projectForm.startDate"() {
// Adjust the endDate to match startDate, if startDate is past endDate
const start = moment(this.projectForm.startDate);
const end = moment(this.projectForm.endDate);
const difference = moment.duration(start.diff(end)).asDays();
if (difference > 0) {
this.projectForm.endDate = this.projectForm.startDate;
}
},
"projectForm.organizations"() {
this.getLabels();
},
projectForm: {
handler() {
this.$emit("validation", this.$v.projectForm);
},
deep: true,
},
},
created() {
this.onProjectLoaded();
this.onParentProjectLoaded();
},
methods: {
onProjectLoaded() {
if (this.currentProject) {
this.loadKeywords(this.currentProject);
if (this.currentProject.visibility) {
this.selectedVisibility = this.currentProject.visibility.id;
}
this.getLabels();
} else {
this.setDefaultVisibility();
}
},
onParentProjectLoaded() {
if (this.parentProject) {
this.projectForm.parentId = this.parentProject.id;
}
},
limitKeywords(values: []): number | null {
// Should the max number of allowed characters is exceeded, prevent the addition of further tags
if (this.$v.projectForm.keywords?.$invalid) {
return values.length;
} else {
return null;
}
},
addTag(newTag: string) {
this.selectedKeyword.push(newTag);
this.$v.projectForm.keywords?.$touch();
},
async retrieveOrganizations(search: string) {
clearTimeout(this.queryTimer);
// Replace the list of available organizations
// with the default one after the search field was cleared
if (search.trim() === "") {
// See computed property organizations' definition
this.projectStore.organizations = null;
}
// Fetch organizations based on the search query
else {
// Add delay to API organization retrieval
this.queryTimer = window.setTimeout(async () => {
this.isLoadingOrganizations = true;
await this.projectStore.retrieveOrganizations(search);
this.isLoadingOrganizations = false;
}, 1000);
}
},
copyMetadataFromParent() {
if (this.parentProject) {
// --- set b-skeleton while filling parent-project form ---
this.fillWithProjectMetadata(this.parentProject);
this.getLabels();
this.$v.projectForm.$touch();
}
},
fillWithProjectMetadata(project: ProjectObject) {
this.projectForm.principleInvestigators = project.principleInvestigators;
this.projectForm.startDate = project.startDate;
this.projectForm.endDate = project.endDate;
this.projectForm.disciplines = project.disciplines;
this.projectForm.organizations = project.organizations;
this.projectForm.keywords = project.keywords;
this.projectForm.visibility = project.visibility;
this.projectForm.grantId = project.grantId;
// Update v-model for Keywords
this.loadKeywords(project);
// Update v-model for Visibility
if (project.visibility) {
this.selectedVisibility = project.visibility.id;
}
},
async getLabels() {
// Retrieves an organization's displayName
/* This method is required because the Organizations are delivered by the Database ProjectModel like so:
displayName:"https://ror.org/04xfq0f34"
url:"https://ror.org/04xfq0f34"
*/
this.isLoadingOrganizations = true;
if (this.projectForm.organizations) {
for (const org of this.projectForm.organizations) {
if (org.url) {
const result = await this.projectStore.getOrganizationByURL(
org.url
);
if (result) {
for (const entry of result) {
org.displayName = entry.displayName;
}
}
}
}
}
this.isLoadingOrganizations = false;
},
loadKeywords(project: ProjectObject) {
this.selectedKeyword = project.keywords
? project.keywords.split(";")
: [];
},
setVisibility(id: string) {
if (this.visibilities) {
const visibility = this.visibilities.find(
(entry) => entry.id === id
) as VisibilityObject;
this.projectForm.visibility = visibility;
this.$v.projectForm.visibility?.$touch();
}
},
setDefaultVisibility() {
if (this.visibilities) {
const visibility = this.visibilities.find(
(entry) => entry.displayName === "Project Members"
) as VisibilityObject;
this.selectedVisibility = visibility.id;
this.projectForm.visibility = visibility;
}
},
},
});
</script>
<style></style>