Select Git revision
CreateProject.vue

Marcel Nellesen authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
CreateProject.vue 26.47 KiB
<template>
<div id="CreateProject" class="container">
<b-row>
<div class="col-sm-2"></div>
<div class="col-sm-8">
<b-form id="edit_form" @submit.stop.prevent="onSubmit">
<b-row v-show="isNewProject && isRWTHMember">
<div class="col-sm-3"></div>
<div class="col-sm-9">{{ $t('affliliationNotification') }}</div>
</b-row>
<b-form-group
class="mandatory"
label-for="ProjectName"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectNameLabel')"
>
<b-form-input
id="ProjectName"
v-model="$v.form.ProjectName.$model"
@input="translateProjectNameToDisplayName"
aria-describedby="projectNameHelp"
:state="$v.form.ProjectName.$dirty ? !$v.form.ProjectName.$error : null"
:placeholder="$t('projectName')"
maxlength="200"
required="required"
v-bind:disabled="!(this.isOwner || isNewProject)"
/>
<div class="invalid-tooltip">{{ $t('projectNameHelp') }}</div>
</b-form-group>
<b-form-group
class="mandatory"
label-for="DisplayName"
label-cols-sm="3"
label-align-sm="right"
:label="$t('displayNameLabel')"
>
<b-form-input
id="DisplayName"
v-model="$v.form.DisplayName.$model"
@input="lockDisplayName"
aria-describedby="displayNameHelp"
:state="$v.form.DisplayName.$dirty ? !$v.form.DisplayName.$error : null"
:placeholder="$t('displayName')"
maxlength="25"
required="required"
v-bind:disabled="!(this.isOwner || isNewProject)"
/>
<div class="invalid-tooltip">{{ $t('displayNameHelp') }}</div>
</b-form-group>
<b-form-group
class="mandatory"
label-for="Description"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectDescriptionLabel')"
>
<b-form-textarea
id="Description"
v-model="$v.form.Description.$model"
aria-describedby="DescriptionHelp"
:state="$v.form.Description.$dirty ? !$v.form.Description.$error : null"
:placeholder="$t('projectDescription')"
maxlength="5000"
required="required"
v-bind:disabled="!(this.isOwner || isNewProject)"
/>
<div class="invalid-tooltip">{{ $t('projectDescriptionHelp') }}</div>
</b-form-group>
<!-- copy metadata from parent -->
<div class="h-divider"></div>
<div>
<h5>{{ $t('activatedImportFromParent') }}</h5>
</div>
<div v-show="copyMetadataLabel !== ''">
<b-form-group label-for="CopyData" label-cols-sm="3" label-align-sm="right" :label="copyMetadataLabel">
<b-button
variant="secondary"
id="project_copy_button"
@click.prevent="clickCopyMetadata"
v-bind:disabled="!(this.isOwner || isNewProject)"
>{{ $t('copyMetadataButton') }}</b-button
>
</b-form-group>
</div>
<b-form-group
class="mandatory"
label-for="PrincipleInvestigators"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectPrincipleInvestigatorsLabel')"
>
<b-form-input
id="PrincipleInvestigators"
v-model="$v.form.PrincipleInvestigators.$model"
aria-describedby="principleInvestigatorsHelp"
:state="$v.form.PrincipleInvestigators.$dirty ? !$v.form.PrincipleInvestigators.$error : null"
:placeholder="$t('projectPrincipleInvestigators')"
maxlength="500"
required="required"
v-bind:disabled="!(this.isOwner || isNewProject)"
/>
<div class="invalid-tooltip">{{ $t('projectPrincipleInvestigatorsHelp') }}</div>
</b-form-group>
<b-form-group
class="mandatory"
label-for="StartDate"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectStartLabel')"
>
<datepicker
id="StartDate"
:language="datepickerLanguage[$i18n.locale]"
v-model="form.StartDate"
:calendar-button="true"
calendar-button-icon="material-icons"
calendar-button-icon-content="date_range"
:placeholder="$t('projectStart')"
:bootstrap-styling="true"
required="required"
:full-month-name="true"
format="dd MMMM yyyy"
@input="startDateSelected"
@selected="startDateSelected"
v-bind:disabled="!(this.isOwner || isNewProject)"
>
</datepicker>
</b-form-group>
<b-form-group
class="mandatory"
label-for="EndDate"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectEndLabel')"
>
<datepicker
id="EndDate"
:language="datepickerLanguage[$i18n.locale]"
v-model="form.EndDate"
:disabled-dates="disabledEndDates"
:calendar-button="true"
calendar-button-icon="material-icons"
calendar-button-icon-content="date_range"
:placeholder="$t('projectEnd')"
:bootstrap-styling="true"
required="required"
:full-month-name="true"
format="dd MMMM yyyy"
@input="dateSelected"
@selected="dateSelected"
v-bind:disabled="!(this.isOwner || isNewProject)"
/>
</b-form-group>
<b-form-group
class="mandatory"
label-for="Discipline"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectDisciplineLabel')"
>
<multiselect
id="Discipline"
v-model="form.Discipline"
v-bind:disabled="!(this.isOwner || isNewProject)"
:options="disciplines"
:multiple="true"
:hide-selected="true"
:label="disciplineLabel"
:track-by="disciplineLabel"
:placeholder="$t('projectDiscipline')"
>
<template slot="singleLabel" slot-scope="props" v-bind:disabled="!(this.isOwner || isNewProject)">
{{ props.option[disciplineLabel] }}
</template>
<template slot="option" slot-scope="props" v-bind:disabled="!(this.isOwner || isNewProject)">
{{ props.option[disciplineLabel] }}
</template>
</multiselect>
</b-form-group>
<b-form-group
class="mandatory"
label-for="Organization"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectOrganizationLabel')"
>
<multiselect
id="Organization"
v-model="form.Organization"
v-bind:disabled="!(this.isOwner || isNewProject)"
:options="organizations"
:multiple="true"
:loading="loadingOrganizations"
:hide-selected="true"
label="displayName"
track-by="url"
:placeholder="$t('projectOrganization')"
@search-change="triggerFetchOptions"
>
<template slot="singleLabel" slot-scope="props" v-bind:disabled="!(this.isOwner || isNewProject)">
{{ props.option.displayName }}
</template>
<template slot="option" slot-scope="props" v-bind:disabled="!(this.isOwner || isNewProject)">
{{ props.option.displayName }}
</template>
<template slot="noOptions">
{{ $t('projectOrganizationNoOptions') }}
</template>
</multiselect>
</b-form-group>
<b-form-group
label-for="Keywords"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectKeywordsLabel')"
>
<multiselect
id="Keywords"
v-model="form.Keywords"
v-bind:disabled="!(this.isOwner || isNewProject)"
:options="keywordoptions"
:placeholder="$t('projectKeywords')"
:multiple="true"
:taggable="true"
:tag-placeholder="$t('tagPlaceholder')"
@tag="addTag"
>
</multiselect>
</b-form-group>
<b-form-group
class="mandatory"
label-for="Visibility"
label-cols-sm="3"
label-align-sm="right"
:label="$t('projectVisibilityLabel')"
v-bind:disabled="!(this.isOwner || isNewProject)"
>
<b-form-radio-group
v-model="form.Visibility.id"
:options="visibilities"
name="radios-stacked"
stacked
v-bind:disabled="!(this.isOwner || isNewProject)"
></b-form-radio-group>
</b-form-group>
<b-form-group label-for="GrantId" label-cols-sm="3" label-align-sm="right" :label="$t('projectGrantIdLabel')">
<b-form-input
id="GrantId"
v-model="$v.form.GrantId.$model"
aria-describedby="grantIdHelp"
:state="$v.form.GrantId.$dirty ? !$v.form.GrantId.$error : null"
:placeholder="$t('projectGrantId')"
maxlength="500"
required="required"
v-bind:disabled="!(this.isOwner || isNewProject)"
/>
<div class="invalid-tooltip">{{ $t('projectGrantIdHelp') }}</div>
</b-form-group>
<div class="h-divider"></div>
<div>
<h5>{{ $t('activatedFeaturesHeadline') }}</h5>
</div>
<div>
<b-form-group
v-for="feature in $v.features.$each.$iter"
:key="feature.id.$model"
:label-for="feature.id.$model"
label-cols-sm="3"
label-align-sm="right"
:label="feature.$model[language]"
>
<b-form-checkbox :id="feature.id.$model" v-model="feature.activated.$model" switch></b-form-checkbox>
</b-form-group>
</div>
<b-form-group>
<b-button
type="button"
variant="light"
name="archive"
id="project_archive_button"
disabled
@click.prevent="clickArchive"
v-show="isOwner || isNewProject"
>{{ $t('archive') }}</b-button
>
<b-button
type="button"
variant="light"
id="project_clear_button"
@click.prevent="clickClearForm"
@click="$v.form.$reset"
v-show="isOwner || isNewProject"
>{{ $t('clearFormButton') }}</b-button
>
<DeleteModal
:projectId="projectId"
:displayName="form.DisplayName"
:projectParentId="form.ParentId"
:languageLocale="languageLocale"
style="display: inline;"
v-show="isOwner"
></DeleteModal>
<b-button
type="submit"
variant="primary"
class="btn-float-right"
:disabled="$v.$invalid || isWaitingForResponse || !$v.$anyDirty"
name="save"
@click.prevent="clickSave"
v-show="isOwner || isNewProject"
style="display: inline;"
>{{ $t('save') }}</b-button
>
</b-form-group>
</b-form>
<loading
:active.sync="isWaitingForResponse"
:can-cancel="false"
:height="128"
:width="128"
:z-index="1500"
:color="color"
:is-full-page="true"
></loading>
</div>
<div class="col-sm-2"></div>
</b-row>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Datepicker from 'vuejs-datepicker';
import { de, en } from 'vuejs-datepicker/dist/locale';
import { validationMixin } from 'vuelidate';
import { required, minLength, maxLength } from 'vuelidate/lib/validators';
import tag_inputs_helper from './utils/tag_inputs_helper';
import {
ProjectApi,
ProjectRoleApi,
DisciplineApi,
OrganizationApi,
VisibilityApi,
ActivatedFeaturesApi,
} from '@coscine/api-connection';
import { GuidUtil, LinkUtil } from '@coscine/app-util';
import VueI18n from 'vue-i18n';
import BootstrapVue from 'bootstrap-vue';
import '@voerro/vue-tagsinput/dist/style.css';
import Multiselect from 'vue-multiselect';
import 'vue-multiselect/dist/vue-multiselect.min.css';
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/vue-loading.css';
import DeleteModal from './components/DeleteModal.vue';
Vue.use(BootstrapVue);
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'en',
messages: coscine.i18n['project-creation'],
silentFallbackWarn: true,
});
function checkKeywords(value: any) {
return tag_inputs_helper.checkKeywords(value, 1000);
}
export default Vue.extend({
i18n,
mixins: [validationMixin],
name: 'CreateProject',
components: {
Datepicker,
Multiselect,
DeleteModal,
Loading,
},
validations: {
form: {
ProjectName: {
required,
maxLength: maxLength(200),
},
DisplayName: {
required,
maxLength: maxLength(25),
},
Description: {
required,
maxLength: maxLength(5000),
},
StartDate: {
required,
},
EndDate: {
required,
},
PrincipleInvestigators: {
required,
maxLength: maxLength(500),
},
Discipline: {
required,
},
Organization: {
required,
},
Keywords: {
checkKeywords,
},
Visibility: {
required,
},
GrantId: {
maxLength: maxLength(500),
},
},
features: {
$each: {
activated: {
required,
},
id: {
required,
},
},
},
},
data() {
return {
datepickerLanguage: {
de,
en,
},
language: this.$props.languageLocale,
isNewProject: false,
isOwner: false,
isRWTHMember: false,
isWaitingForResponse: false,
color: '#00549f',
displayNameIsLocked: false,
disciplineLabel: 'displayNameEn',
form: {
ProjectName: '',
DisplayName: '',
Description: '',
PrincipleInvestigators: '',
StartDate: new Date(),
EndDate: new Date(),
Discipline: [] as object[],
Organization: [] as Organization[],
Keywords: [] as object[],
Visibility: {},
GrantId: '',
ParentId: String,
},
initialState: {
ProjectName: '',
DisplayName: '',
Description: '',
PrincipleInvestigators: '',
StartDate: new Date(),
EndDate: new Date(),
Discipline: [] as object[],
Organization: [] as Organization[],
Keywords: [] as object[],
Visibility: {},
GrantId: '',
ParentId: String,
},
disciplines: [] as object[],
organizations: [] as Organization[],
visibilities: [] as object[],
keywordoptions: [] as object[],
disabledEndDates: { to: new Date(1000, 1, 1) },
features: [] as object[],
queryTimer: 0,
// --- data for copyMetadata ----
copyMetadataLabel: '',
parentData: {},
loadingOrganizations: false,
};
},
props: {
projectId: String,
parentId: String,
languageLocale: {
default: 'en',
type: String,
},
},
watch: {
'form.Discipline'(newVal, oldVal) {
if (oldVal.length > 0) {
this.$v.form!.Discipline!.$touch();
}
},
'form.Organization'(newVal, oldVal) {
if (oldVal.length > 0) {
this.$v.form!.Organization!.$touch();
}
},
'form.Keywords'(newVal, oldVal) {
if (oldVal.length > 0) {
this.$v.form!.Keywords!.$touch();
}
},
},
methods: {
addTag(newTag: any) {
this.form.Keywords.push(newTag);
this.keywordoptions.push(newTag);
},
clickClearForm() {
this.tranferFormValues(this.initialState, this.form);
if (this.isNewProject) {
this.displayNameIsLocked = false;
}
},
checkOwnership(projectRoles: any) {
const thisComponent = this;
for (const projectRole in projectRoles) {
if (projectRoles[projectRole].role.displayName === 'Owner') {
thisComponent.isOwner = true;
}
}
},
clickSave() {
this.$v.$touch();
if (this.$v.$anyError || !this.$v.$anyDirty) {
return;
}
tag_inputs_helper.parseKeywords(this.form, ';');
if (this.$props.parentId !== null && this.$props.parentId !== '') {
this.form.ParentId = this.$props.parentId;
}
if (!this.isWaitingForResponse) {
this.isWaitingForResponse = true;
if (this.isNewProject) {
ProjectApi.storeProject(this.form, (response: any) => {
// activate initial features
for (const feature of this.features) {
if ((feature as any).activated) {
ActivatedFeaturesApi.activateFeature(response.data.id, (feature as any).id);
}
}
LinkUtil.redirectToExternalProject(response.data.slug);
this.handleAfterSave();
});
} else {
ProjectApi.updateProject(this.projectId, this.form, (response: any) => {
// update features
for (const feature of this.features) {
if ((feature as any).activated) {
ActivatedFeaturesApi.activateFeature(this.projectId, (feature as any).id);
} else {
ActivatedFeaturesApi.deactivateFeature(this.projectId, (feature as any).id);
}
}
response.data === 1 ? LinkUtil.redirectToProject('EditProject') : LinkUtil.redirectToRoot();
this.handleAfterSave();
});
}
}
},
handleAfterSave() {
this.isWaitingForResponse = false;
this.$v.$reset();
},
translateProjectNameToDisplayName() {
if (this.displayNameIsLocked) {
return;
}
this.form.DisplayName = this.form.ProjectName.substring(0, 25);
},
lockDisplayName() {
this.displayNameIsLocked = true;
},
startDateSelected(selectedStartDate: Date) {
if (selectedStartDate !== null) {
this.form.StartDate = new Date(this.form.StartDate);
this.disabledEndDates.to = selectedStartDate;
if (new Date(this.form.EndDate) < this.disabledEndDates.to) {
this.form.EndDate = this.disabledEndDates.to;
}
}
},
dateSelected() {
if (new Date(this.form.EndDate) < this.disabledEndDates.to) {
this.form.EndDate = this.disabledEndDates.to;
} else {
this.form.EndDate = new Date(this.form.EndDate);
}
},
fillForm(data: any) {
this.form.ProjectName = data.projectName;
this.form.DisplayName = data.displayName;
this.form.Description = data.description;
this.fillProjectMetadata(data);
},
// ----- fillForm by parent information --------------
fillProjectMetadata(data: any) {
this.form.PrincipleInvestigators = data.principleInvestigators;
this.form.StartDate = new Date(data.startDate);
this.startDateSelected(new Date(this.form.StartDate));
this.form.EndDate = new Date(data.endDate);
this.form.Discipline = data.disciplines;
this.form.Organization = data.organizations;
const keywords = data.keywords ? data.keywords.split(';') : [];
this.form.Keywords = keywords;
this.form.Visibility = data.visibility;
this.form.GrantId = data.grantId;
this.form.ParentId = data.parentId;
},
tranferFormValues(from: any, to: any) {
to.ProjectName = from.ProjectName;
to.DisplayName = from.DisplayName;
to.Description = from.Description;
to.PrincipleInvestigators = from.PrincipleInvestigators;
to.StartDate = from.StartDate;
to.EndDate = from.EndDate;
to.Keywords = [] as object[];
for (const keyword in from.Keywords) {
to.Keywords.push(from.Keywords[keyword]);
}
to.Organization = [] as Organization[];
for (const organization in from.Organization) {
to.Organization.push(from.Organization[organization]);
}
to.Discipline = [] as object[];
for (const discipline in from.Discipline) {
to.Discipline.push(from.Discipline[discipline]);
}
to.Visibility = from.Visibility;
to.GrantId = from.GrantId;
to.ParentId = from.ParentId;
},
triggerFetchOptions(search: string) {
clearTimeout(this.queryTimer);
if (search.length < 3) {
return;
}
this.queryTimer = setTimeout(() => this.fetchOrganizationOptions(search), 1000);
},
fetchOrganizationOptions(search: string) {
this.loadingOrganizations = true;
let newOrganizations: any = [];
for (const organization of this.form.Organization) {
newOrganizations.push(organization);
}
OrganizationApi.getOrganizationsWithFilter(search, (response: any) => {
for (const org of response.data.data) {
newOrganizations.push(org as any);
}
this.organizations = newOrganizations;
this.loadingOrganizations = false;
},(error:any) => { this.loadingOrganizations = false; });
},
clickCopyMetadata() {
if (this.parentData) {
this.fillProjectMetadata(this.parentData);
this.getLabels();
}
},
setCopyMetadata(parentId: string) {
ProjectApi.getProjectInformation(parentId, (response: any) => {
this.parentData = response.data;
// ---- set label for copyMetadata -----
const parentProjectName = response.data.projectName;
this.copyMetadataLabel = this.$t('copyMetadataLabel') + ' "' + parentProjectName + '":';
});
},
getLabels() {
for (let org of this.form.Organization) {
OrganizationApi.getOrganization(org.url, (innerResponse: any) => {
for (let innerOrg of innerResponse.data.data) {
this.organizations.push(innerOrg as any);
org.displayName = innerOrg.displayName;
}
});
}
},
},
async created() {
i18n.locale = this.$props.languageLocale;
if (this.$props.languageLocale === 'en') {
this.disciplineLabel = 'displayNameEn';
} else {
this.disciplineLabel = 'displayNameDe';
}
this.startDateSelected(new Date());
OrganizationApi.isRWTHMember((response: any) => {
this.isRWTHMember = response.data.isRWTHMember;
});
VisibilityApi.getVisibilities((response: any) => {
for (const visibility of response.data) {
if (visibility.displayName === 'Project Members') {
this.initialState.Visibility = visibility;
this.form.Visibility = visibility;
}
this.visibilities.push({ text: visibility.displayName, value: visibility.id });
}
});
if (GuidUtil.isValidGuid(this.projectId)) {
this.displayNameIsLocked = true;
this.isNewProject = false;
// Load features
ActivatedFeaturesApi.listAllFeaturesOfProject(this.projectId, (response: any) => {
response.data.forEach((feature: any) => {
this.features.push(feature);
});
});
ProjectApi.getProjectInformation(this.projectId, (response: any) => {
const parentId = response.data.parentId;
if (GuidUtil.isValidGuid(parentId)) {
this.setCopyMetadata(parentId);
}
this.fillForm(response.data);
this.tranferFormValues(this.form, this.initialState);
this.getLabels();
});
ProjectRoleApi.getUserRoles(this.projectId, (response: any) => {
this.checkOwnership(response.data);
});
} else {
// --- get parent information ---
if (GuidUtil.isValidGuid(this.parentId)) {
this.setCopyMetadata(this.parentId);
}
ActivatedFeaturesApi.listAllFeatures((response: any) => {
response.data.forEach((feature: any) => {
this.features.push(feature);
});
});
this.isNewProject = true;
this.tranferFormValues(this.form, this.initialState);
}
const disciplines = await DisciplineApi.getDisciplines((response: any) => response.data);
for (const discipline of disciplines) {
this.disciplines.push(discipline);
}
},
});
</script>
<style>
#CreateProject .vdp-datepicker .form-control {
background-color: #ffffff;
}
#CreateProject .multiselect {
min-height: calc(1.4em + 0.75rem + 2px);
}
#CreateProject .multiselect__input {
border: 0px;
min-height: calc(1.4em + 0.75rem + 2px);
}
#CreateProject .multiselect__tags {
border-radius: 0px;
}
#CreateProject .btn-float-right {
float: right;
}
#CreateProject .vdp-datepicker__calendar-button {
height: calc(1.4em + 0.75rem + 2px);
}
#CreateProject .form-group.mandatory .col-form-label:after {
content: ' *';
color: #a70619;
}
#CreateProject .col-form-label {
font-weight: bold;
}
.multiselect__tag {
background-color: #00549f !important;
}
.multiselect__option--highlight {
background-color: #00549f !important;
background: #00549f !important;
}
.multiselect__option--selected.multiselect__option--highlight {
background-color: #a70619 !important;
}
.multiselect__tag-icon:focus,
.multiselect__tag-icon:hover {
background-color: #a70619 !important;
}
.invalid-tooltip {
position: absolute;
top: 100%;
z-index: 5;
display: none;
max-width: 100%;
padding: 0.25rem 0.5rem;
margin-top: 0.1rem;
font-size: 0.76563rem;
line-height: 1.4;
color: #fff;
background-color: rgba(204, 7, 30, 0.9);
border-radius: 0;
}
#project_archive_button,
#project_clear_button {
display: none;
}
.h-divider {
margin-top: 5px;
margin-bottom: 10px;
height: 1px;
width: 100%;
border-top: 1px solid #bebbbb;
}
.form-group .custom-control-label {
vertical-align: super;
}
</style>