<template> <div> <base-toaster :open="toasterIsOpen" @close="hideDialog" > <div class="flex justify-between items-center"> <font-awesome-icon class="ml-3" icon="fa-solid fa-thumbs-up " size="lg" /> <h4>Request submitted successfully!</h4> <button class="btn btn-outline" @click="hideDialog" > <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> </button> </div> </base-toaster> <div class="flex flex-wrap justify-center"> <!-- Sidebar --> <!--<div class="bg-light border-right" id="sidebar-wrapper">--> <!--<!–<div class="sidebar-heading">Menu </div>–>--> <!--<div class="list-group list-group-flush">--> <!--<a href="#" class="list-group-item list-group-item-action bg-light">Search</a>--> <!--<a href="#" class="list-group-item list-group-item-action bg-light">Columns</a>--> <!--<a href="#" class="list-group-item list-group-item-action bg-light">Filters</a>--> <!--<a href="#" class="list-group-item list-group-item-action bg-light">Download</a>--> <!--<a href="#" class="list-group-item list-group-item-action bg-light">Contacts</a>--> <!--</div>--> <!--</div>--> <!-- /#sidebar-wrapper --> <div class="w-5/6"> <fetch-condensate :condensate="condensate"> <template slot-scope="{ response, loading, fetchError }"> <slot :response="response" :loading="loading" :fetchError="fetchError" > <div v-if="loading || response === null"> <base-spinner /> </div> <div> <h2 v-if="fetchError" class="text-red-500" > {{ fetchError }} </h2> </div> <!-- <div v-if="loading"> <base-spinner></base-spinner> </div> --> <div v-if="response"> <div class="flex space-x-4"> <h2>{{ response.data.name }}</h2> <button v-if=" getUserData !== null && (getUserData === 'Maintainer' || getUserData === 'Contributor') " class="rounded-lg px-5 py-4 text-center" @click="toggleChangeName" > <font-awesome-icon size="lg" icon="fa-solid fa-pen-to-square fa-xl" /> </button> </div> <div v-if="changeName && (getUserData === 'Maintainer' || getUserData === 'Contributor')" class=" bg-white p-4 border border-gray-300 rounded-lg bg-opacity-80 " > <form class="form-horizontal" @submit.prevent="changeCondensateName(response)" > <div class="form-group space-y-4"> <div class="row"> <label for="condensateName" class="control-label col-sm-2" >Name</label> <div class="col-sm-10"> <div class="w-1/4"> <input id="condensateName" v-model.trim="condensateName" class="form-control" type="text" placeholder="Enter condensate name." @keyup="validateName" > <p v-if="nameError && nameErrMsg" class="text-red-500 mt-2 text-2xl font-bold" > {{ nameErrMsg }} </p> </div> </div> </div> <div class="row"> <label for="condensateName" class="control-label col-sm-2" >Reason</label> <div class="col-sm-10"> <div class="w-3/4"> <textarea v-model.trim="condensateNameComment" class=" form-control block px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none " rows="5" :placeholder=" getUserData === 'Maintainer' ? 'Optional' : 'Mandatory' " @keyup="descriptionKeyup" /> <p v-if=" nameCommentErr && condensateNameCommentErrMsg " class="text-red-500 mt-2 text-2xl font-bold" > {{ condensateNameCommentErrMsg }} </p> </div> <div class="flex space-x-4 mt-4"> <button type="submit" class=" text-white bg-blue-500 hover:bg-blue-700 focus:ring-2 focus:ring-blue-300 rounded-lg inline-flex items-center px-5 py-2.5 text-center font-bold " > Save </button> <button class=" bg-white hover:bg-gray-200 focus:ring-2 focus:ring-gray-300 rounded-lg border border-gray-300 px-5 py-2.5 hover:text-gray-900 font-bold " @click="toggleChangeName" > Cancel </button> </div> </div> </div> </div> </form> </div> <h4 class="round"> General Information </h4> <div class=" bg-white border shadow-md rounded-lg bg-opacity-80 " > <div class="panel-body"> <div class="container-fluid col-md-12"> <!-- <div class="row"> <div class="text col-sm-3"> Canonical ID </div> <div class="col-sm-9"> {{ response.data.canonical_id }} </div> </div> --> <div class="row"> <div class="text col-sm-3 text-2xl"> Species </div> <div class="col-sm-9 text-2xl"> {{ response.data.species_name }} ({{ response.data.species_tax_id }}) </div> </div> <div class="row text-2xl"> <div class="text col-sm-3"> Description </div> <div class="col-sm-9 text-2xl"> {{ response.data.description }} <button v-if=" getUserData !== null && (getUserData === 'Maintainer' || getUserData === 'Contributor') " class="btn btn-primary btn-link" @click=" toggleUpdateDescription(response.data.description) " > <font-awesome-icon icon="fa-solid fa-pen-to-square fa-xl" /> </button> <div v-if="showUpdateDescription && (getUserData === 'Maintainer' || getUserData === 'Contributor')" class="panel panel-default" > <div class="panel-body"> <div class="container-fluid col-md-12"> <form class="form-horizontal" autocomplete="off" @submit.prevent="updateDescription(response)" > <div v-if="isLoading"> <base-spinner /> </div> <div class="form-group"> <label class="control-label col-sm-2" for="keyword" >Update description</label> <div class="col-sm-10"> <textarea id="comment" v-model.trim="description" class=" form-control block px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none " rows="5" placeholder="Description" @keyup="descriptionKeyup" /> <p v-if="descriptionErrorMsg" class="text-red-600 font-bold" > {{ descriptionErrorMsg }} </p> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="funComment" >Reason</label> <div class="col-sm-10"> <textarea id="funComment" v-model.trim="descriptionComment" class=" form-control block px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none " rows="5" :placeholder=" getUserData === 'Maintainer' ? 'Optional' : 'Mandatory' " @keyup="commentKeyup" /> <p v-if="descriptionCommentErr" class="text-red-600 font-bold" > {{ commentErrorMsg }} </p> <p v-if="descriptionMsg" class="text-green-600 font-bold" > {{ descriptionMsg }} </p> <div class="flex space-x-4"> <button id="dropdownMenuButton" class=" text-white bg-blue-500 hover:bg-blue-700 focus:ring-2 focus:ring-blue-300 rounded-lg inline-flex items-center px-5 py-2.5 text-center font-bold " type="submit" > Update </button> <button id="dropdownMenuButton" class=" bg-white hover:bg-gray-200 focus:ring-2 focus:ring-gray-300 rounded-lg border border-gray-300 px-5 py-2.5 hover:text-gray-900 font-bold " type="button" @click="toggleUpdateDescription" > Cancel </button> </div> </div> </div> </form> </div> </div> </div> </div> </div> <div v-show="response.data.synonyms" class="row" > <div class="text col-sm-3"> Also Known As </div> <div class="col-sm-9"> {{ response.data.synonyms ? response.data.synonyms .map((a) => a .replaceAll("-", " ") .replace(/\b\w/g, (l) => l.toUpperCase()) ) .join(", ") : "" }} <button v-if=" getUserData !== null && (getUserData === 'Maintainer' || getUserData === 'Contributor') " class="btn btn-primary btn-link" @click="showAddDeleteSynonym = !showAddDeleteSynonym" > <font-awesome-icon icon="fa-solid fa-pen-to-square fa-xl" /> </button> <add-delete-synonym v-if="showAddDeleteSynonym && (getUserData === 'Maintainer' || getUserData === 'Contributor')" :synonym-data="response.data.synonyms" :condensate-id="response.data.canonical_id" @update-key="updatedKey += 1" @close="closeAddDeleteSynonym" /> </div> </div> <div v-if="getIsExperimental=== 'false'" class="row text-2xl" > <div class="text col-sm-3 text-2xl"> Markers <a class="uniprot-link tooltipped tooltipped-e" aria-label="Proteins which can help in unique identification of the condensate organelle" > <span class="fa fa-info-circle" /> </a> </div> <div class="col-sm-9"> <span v-if="!response.data.biomarkers"> None </span> <span v-else v-html="getProteinLinks(response.data.biomarkers)" /> <button v-if=" getUserData !== null && (getUserData === 'Maintainer' || getUserData === 'Contributor') " class="btn btn-primary btn-link" @click="showAddDeleteMarker = !showAddDeleteMarker" > <font-awesome-icon icon="fa-solid fa-pen-to-square fa-xl" /> </button> <add-delete-marker v-if="showAddDeleteMarker && (getUserData === 'Maintainer' || getUserData === 'Contributor')" :marker-data="response.data.biomarkers" :condensate-id="response.data.canonical_id" :proteins="response.data.proteins" @update-key="updatedKey += 1" @close="closeAddDeleteMarker" /> </div> </div> <div class="row text-2xl"> <div class="text col-sm-3"> No. of Proteins </div> <div class="col-sm-9"> {{ response.data.protein_count }} </div> </div> <div class="row text-2xl"> <div class="text col-sm-3 text-2xl"> Evidence star </div> <div class="flex col-sm-9"> <star-rating :star-size="20" :show-rating="false" :rating=" response.data.confidence_score ? response.data.confidence_score : 0 " :read-only="true" :increment="0.01" /> </div> </div> </div> </div> </div> <div v-show="response.data.experiments.length > 0" class="border bg-white shadow-md rounded-lg my-14 p-8" > <h4 class="round"> Experiments </h4> <div class="panel panel-default"> <table id="experiment-table" class="csi table table-hover table-responsive" > <thead> <tr class="active"> <!-- <th>Exp. ID</th>--> <th>Method</th> <th>Phase Separated</th> <th>pH value</th> <th>Morphology</th> <th>PubMed</th> <th>Solute Concentration</th> <th>Temperature</th> <th>Salts</th> </tr> </thead> <tbody> <tr v-for="(item, index) in response.data.experiments" :key="index" > <!-- <td>{{item.exp_id}}</td>--> <td class="text-nowrap"> {{ item.detection_method }} </td> <td>{{ item.is_phase_separated }}</td> <td>{{ item.ph_value }}</td> <td>{{ item.morphology }}</td> <td class="text-nowrap"> <a :id="item.publication_link.split('/')[3]" :href="item.publication_link" class="uniprot-link tooltipped tooltipped-n tooltipped-multiline" target="_blank" @mouseover="fetchPubMedId(item.publication_link.split('/')[3])" > {{ item.publication_link.split('/')[3] }} <i class="glyphicon glyphicon-link" /> </a> </td> <td> {{ tokenize(item.solute_concentrations, ";") }} </td> <td> {{ item.temperature }} </td> <td> <ul v-for="(item_salt, index_salt) in item.salts" :key="index_salt" > <li v-for="(value, key, index_li) in item_salt" :key="index_li" > {{ key }} : {{ value }} </li> </ul> </td> </tr> </tbody> </table> </div> </div> <div class="my-14 border bg-white shadow-md rounded-lg p-8"> <h4>Proteins</h4> <button v-if=" getUserData !== null && !response.data.is_experimental && (getUserData === 'Maintainer' || getUserData === 'Contributor') " class=" text-white bg-blue-500 hover:bg-blue-700 focus:ring-2 focus:ring-blue-300 rounded-lg inline-flex items-center px-5 py-3 text-center font-bold " type="button" @click="showAddProtein = !showAddProtein" > Add a protein to this condensate </button> <div v-if="showAddProtein && (getUserData === 'Maintainer' || getUserData === 'Contributor')" class="panel panel-default mt-4" > <div class="panel-body"> <div class="container-fluid col-md-12"> <div v-if="isLoading"> <base-spinner /> </div> <form class="form-horizontal" autocomplete="off" @submit.prevent="addProtein(response)" > <div class="form-group"> <label class="control-label col-sm-2" for="species" >Add protein with uniprot ID</label> <div class="col-sm-4 text-2xl"> <div class="flex"> <input id="keyword" v-model.trim="uniprotId" class="form-control" type="text" placeholder="Uniprot ID. (e.g. P35637)" @keyup="uniprotKeyup" > <a class=" uniprot-link tooltipped ml-2 tooltipped-e " aria-label="Proteins which can help in unique identification of the condensate organelle" > <span class="fa fa-info-circle" /> </a> </div> <p v-if="isUniProtIdError" class="text-red-600 mt-4 font-bold" v-html="uniprotIdErrorMsg" /> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="keyword" >Reason</label> <div class="col-sm-8"> <textarea id="comment" v-model.trim="proteinComment" class=" form-control block px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none " rows="5" :placeholder=" getUserData === 'Maintainer' ? 'Optional.' : 'Mandatory.' " @keyup="validateProteinComment" /> <p v-if="proteinCommentErr" class="text-red-600 font-bold" > {{ commentErrorMsg }} </p> <p v-if="isSubmitted" :class=" error ? 'text-red-600 font-bold' : 'text-green-600 font-bold' " > {{ message }} </p> <div class="flex space-x-4"> <button id="dropdownMenuButton" class=" text-white bg-blue-500 hover:bg-blue-700 focus:ring-2 focus:ring-blue-300 rounded-lg inline-flex items-center px-5 py-2.5 text-center font-bold " type="submit" > Add </button> <button id="dropdownMenuButton" class=" bg-white hover:bg-gray-200 focus:ring-2 focus:ring-gray-300 rounded-lg border border-gray-300 px-5 py-2.5 hover:text-gray-900 font-bold " type="button" @click="cancelAddProtein" > Cancel </button> </div> </div> </div> </form> </div> </div> </div> <llps-table id="protein-table" :data="response.data.proteins" :canonical-id="response.data.canonical_id" :map="response.data.protein_confidence_score" :is-experimental="response.data.is_experimental" :pubmed="response.data.protein_pubmed_ids" :db-tags="response.data.protein_source_db_tags" :protein-driver-criterion=" response.data.protein_driver_criterion " :protein-experimental-evidence=" response.data.protein_exp_evidence " :protein-functional-type=" response.data.protein_functional_type " @update-key="updatedKey += 1" /> </div> <!-- for above llps table protein functiona type data --> <!-- :protein-driver-criterion="response.data.protein_driver_criterion" :protein-experimental-evidence="response.data.protein_exp_evidence" --> <!-- :protein-functional-type="response.data.protein_functional_type" --> <!-- <fetch-user-specific-update-items :id="response.data.canonical_id"> <template slot-scope="{ response, loading }"> <slot :response="response" :loading="loading"> <div v-if="loading || response === null">Loading...</div> <div v-else> <h4 class="round">Requests</h4> <condensate-update-items-table id="condensateUpdateItem" :data="response" /> </div> </slot> </template> </fetch-user-specific-update-items> --> <div v-if="getUserData " class="border bg-white shadow-md rounded-lg p-8" > <h4 class="round mb-8"> All submitted changes for this condensate </h4> <condensate-update-items-table id="condensateUpdateItem" :key="updatedKey" :data="response.data.canonical_id" /> </div> <contributor-list-table class="my-14" :id="response.data.canonical_id" entity="condensate" /> <!-- <request-update-item-table id="protein-table" :data="response.data.proteins" /> --> <!-- {{response.data.experiments}}--> <!--<div class="panel panel-default">--> <!--<table class="csi table table-hover table-responsive">--> <!--<thead>--> <!--<tr class="active">--> <!--<th>Name</th>--> <!--<th>Species</th>--> <!--<th>Uniprot</th>--> <!--<th>Sequence</th>--> <!--</tr>--> <!--</thead>--> <!--<tbody>--> <!--<tr v-for="(item, index) in response.data.llps_ptms" v-bind:key="index">--> <!--<td class="col-sm-6">{{item.name}}</td>--> <!--<td>{{item.species_name}}</td>--> <!--<td>{{item.uniprot_id}}</td>--> <!--<td>--> <!--<input type="text" :value="item.sequence">--> <!--<button class="copy-button" :name="'item.uniprot_id'" :id="'item.uniprot_id'" :data-clipboard-text="item.sequence">Copy</button>--> <!--</td>--> <!--</tr>--> <!--</tbody>--> <!--</table>--> <!--</div>--> <!--<h4 class="round">Additional Information</h4>--> <!--<div class="panel panel-default">--> <!--<div class="panel-body">--> <!--<div class="row">--> <!--<div class="text col-sm-3">Proteins</div>--> <!--<div class="col-sm-9">--> <!--{{response.data.proteins}}--> <!--</div>--> <!--</div>--> <!--</div>--> <!--</div>--> </div> </slot> </template> </fetch-condensate> </div> </div> </div> <!-- /#wrapper --> </template> <script> import fetchCondensate from '@/components/DDCODE/fetchCondensate.vue'; import llpsTable from '@/components/LlpsTable.vue'; import AddDeleteMarker from './CMS/addDeleteMarker.vue'; import AddDeleteSynonym from './CMS/addDeleteSynonym.vue'; import BaseSpinner from './UI/BaseSpinner.vue'; import CondensateUpdateItemsTable from './CondensateUpdateItemsTable.vue'; import BaseToaster from './UI/BaseToaster.vue'; import StarRating from 'vue-star-rating'; import ContributorListTable from './ContributorListTable.vue'; //import FetchUserSpecificUpdateItems from './CMS/fetchUserSpecificUpdateItems.vue'; //import RequestUpdateItemTable from "./RequestUpdateItemTable.vue"; // import TheModal from './UI/TheModal.vue'; const _ = require('lodash'); require('./js/clipboard'); let host = require('./js/const').apiHost; export default { name: 'CondensateDetailPage', components: { fetchCondensate, llpsTable, AddDeleteMarker, AddDeleteSynonym, BaseSpinner, CondensateUpdateItemsTable, BaseToaster, StarRating, ContributorListTable, // FetchUserSpecificUpdateItems, // RequestUpdateItemTable, }, props: ['condensateId'], data() { return { condensate: this.$route.params.condensate ? this.$route.params.condensate : this.condensateId, dbNames: require('./js/const').db, isDev: process.env.NODE_ENV === 'development', showAddProtein: false, uniprotId: '', comment: '', proteinComment: '', condensateNameComment: '', condensateNameCommentErrMsg: '', markerComment: '', descriptionComment: '', description: '', descriptionMsg: '', condensateName: '', descriptionErrorMsg: '', isUniProtIdError: false, isCommentError: false, isSubmitted: false, error: false, showUpdateDescription: false, showAddDeleteMarker: false, showAddDeleteSynonym: false, commentErrorMsg: '', descriptionCommentErr: false, nameCommentErr: false, proteinCommentErr: false, markerCommentErr: false, uniprotIdErrorMsg: '', message: '', whitespaceRegex: /(\s)/, nameError: false, toggleModel: false, isLoading: false, toasterIsOpen: false, changeName: false, updatedKey: 0, }; }, computed: { jwt: function () { return this.$store.getters['User/jwt']; }, getUserData() { const userRole = this.$store.getters['User/userRole']; return userRole; }, getIsExperimental(){ return this.$store.getters['Param/isExperimental'] } }, created(){ this.$store.dispatch('Param/getIsExperimentalFlagFromStorage'); }, methods: { cancelAddProtein(){ this.uniprotId='', this.proteinComment = '', this.showAddProtein = false }, validateName() { (this.nameErrMsg = ''), (this.nameError = false); }, showDialog() { this.toasterIsOpen = true; }, hideDialog() { this.toasterIsOpen = false; }, confirm() { this.toggleModel = false; }, cancel() { this.toggleModel = false; }, toggleChangeName() { this.changeName = !this.changeName; this.condensateName = ''; }, closeAddDeleteMarker() { this.comment = ''; this.showAddDeleteMarker = false; }, closeAddDeleteSynonym() { this.comment = ''; this.showAddDeleteSynonym = false; }, uniprotKeyup() { this.message = ''; this.isUniProtIdError = false; }, commentKeyup() { this.message = ''; this.descriptionCommentErr = false; this.isCommentError = false; }, descriptionKeyup() { this.descriptionMsg = ''; this.descriptionErrorMsg = ''; }, toggleUpdateDescription(res) { this.description = res; this.descriptionMsg = ''; this.descriptionErrorMsg = ''; this.descriptionCommentErr = false; this.descriptionComment = ''; this.showUpdateDescription = !this.showUpdateDescription; }, validateProteinComment() { this.proteinCommentErr = false; this.commentErrorMsg = ''; }, async changeCondensateName(response) { if (!this.condensateName) { this.nameError = true; this.nameErrMsg = 'Enter a condensate name.'; return; } this.nameError = false; this.nameErrMsg = ''; if (this.isDev) { host = require('./js/const').devApiHost; } let url = `${host}/api/update-items`; let data; if (this.getUserData === 'Maintainer') { data = { Entity: 'condensate', EntityId: response.data.canonical_id, ChangeOperation: 'update', Attribute: 'name', Value: this.condensateName, SubmissionComments: this.condensateNameComment ? this.condensateNameComment : 'Maintainer do not need to provide a reason for such change at the moment!', Status: 'accepted', }; } else { if ( this.condensateNameComment === '' || this.condensateNameComment.length < 50 ) { this.nameCommentErr = true; this.condensateNameCommentErrMsg = 'Reason should not be empty or less than 50 characters!'; return; } data = { Entity: 'condensate', EntityId: response.data.canonical_id, ChangeOperation: 'update', Attribute: 'name', Value: this.condensateName, SubmissionComments: this.condensateNameComment, Status: 'requested', }; } this.isLoading = true; try { const res = await this.axios.post( url, { data: data }, { headers: { Authorization: `Bearer ${this.jwt}`, }, } ); if(res.statusText!=='OK' && res.status >= 400 && res.status <= 499){ this.isLoading = false; this.nameCommentErr = false; this.condensateNameCommentErrMsg = `${res.status} ${res.statusText}` return; } if (res.statusText!=='OK' && res.status >= 500 && res.status <= 599) { this.isLoading = false; this.nameCommentErr = false; this.condensateNameCommentErrMsg = `${res.status} Internal Server Error. Please try again after some time.`; return; } this.isLoading = false; this.toasterIsOpen = true; this.nameCommentErr = false; this.condensateNameCommentErrMsg = ''; this.condensateNameComment = ''; this.condensateName = ''; this.updatedKey += 1; setTimeout(() => { this.toasterIsOpen = false; }, 2000); } catch (e) { this.isLoading = false; this.nameCommentErr = true; this.condensateNameCommentErrMsg = e.message || 'Internal Server Error, please try again later!'; } }, async updateDescription(response) { if (this.description === '') { this.descriptionErrorMsg = 'Description should not be empty!'; return; } else if (this.description === response.data.description) { this.descriptionErrorMsg = 'You have not added any description. Please modify the existing description.'; this.descriptionMsg = ''; return; } if (this.isDev) { host = require('./js/const').devApiHost; } let url = `${host}/api/update-items`; let data; if (this.getUserData === 'Maintainer') { data = { Entity: 'condensate', EntityId: response.data.canonical_id, ChangeOperation: 'update', Attribute: 'description', Value: this.description, SubmissionComments: this.descriptionComment ? this.descriptionComment : 'Maintainer do not need to provide a reason for such change at the moment!', Status: 'accepted', }; } else { if ( this.descriptionComment === '' || this.descriptionComment.length < 50 ) { this.descriptionCommentErr = true; this.commentErrorMsg = 'Reason should not be empty or less than 50 characters!'; return; } data = { Entity: 'condensate', EntityId: response.data.canonical_id, ChangeOperation: 'update', Attribute: 'description', Value: this.description, SubmissionComments: this.descriptionComment, Status: 'requested', }; } this.isLoading = true; try { const res = await this.axios.post( url, { data: data }, { headers: { Authorization: `Bearer ${this.jwt}`, }, } ); if(res.statusText!=='OK' && res.status >= 400 && res.status <= 499){ this.isLoading = false; this.isCommentError = true; this.commentErrorMsg = `${res.status} ${res.statusText}` return; } if (res.statusText!=='OK' && res.status >= 500 && res.status <= 599) { this.isLoading = false; this.isCommentError = true; this.commentErrorMsg = `${res.status} Internal Server Error. Please try again after some time.`; return; } this.isLoading = false; this.toasterIsOpen = true; this.descriptionCommentErr = false; this.descriptionComment = ''; this.descriptionErrorMsg = ''; this.description = response.data.description; this.commentErrorMsg = ''; this.comment = ''; this.isCommentError = false; this.updatedKey += 1; setTimeout(() => { this.toasterIsOpen = false; }, 2000); } catch (e) { this.descriptionMsg = ''; this.descriptionErrorMsg = e.message || 'Something went wrong, please try again later!'; } }, async uniprotIdValidation(id) { try { const res = await fetch(`https://rest.uniprot.org/uniprotkb/${id}`, { method: 'HEAD', }); const response = await res; if (!response.ok && response.status === 400) { return { valid: false, msg: `No protein record was found at Uniprot with the given ID. Please check the URL <a class='text-blue-500 hover:text-blue-700' href='https://www.uniprot.org/uniprot/' target='_blank'> https://www.uniprot.org/uniprot/ </a>`, }; } else if (!response.ok && response.status === 500) { return { valid: false, msg: `Not able to validate at the moment. Please check the URL <a class='text-blue-500 hover:text-blue-700' href='https://www.uniprot.org/uniprot/' target='_blank'> https://www.uniprot.org/uniprot/ </a>. Please contact CD-CODE admin <a href="mailto:mail@cd-code.org" class="text-blue-500 hover:text-blue-700" >mail@cd-code.org</a> for further assist. `, }; } else { return { valid: true, msg: 'Valid Uniprot ID.' }; } } catch (err) { return { valid: false, msg: err.message + '.' + `Please contact CD-CODE admin <a href="mailto:mail@cd-code.org" class="text-blue-500 hover:text-blue-700" >mail@cd-code.org</a> for further assist.`, }; } }, async addProtein(response) { if (this.uniprotId === '') { this.uniprotIdErrorMsg = 'Uniprot ID should not be empty!'; this.isUniProtIdError = true; return; } else if (this.uniprotId.length < 6) { this.uniprotIdErrorMsg = 'Uniprot ID should be minimum of 6 character.'; this.isUniProtIdError = true; return; } else if (this.uniprotId.length > 10) { this.uniprotIdErrorMsg = 'Uniprot ID should be maximum of 10 character.'; this.isUniProtIdError = true; return; } else if (this.whitespaceRegex.test(this.uniprotId)) { this.uniprotIdErrorMsg = 'Uniprot ID should not have space in between.'; this.isUniProtIdError = true; return; } const validUniprot = await this.uniprotIdValidation(this.uniprotId); if (!validUniprot.valid) { this.message = ''; this.uniprotIdErrorMsg = validUniprot.msg; this.isUniProtIdError = true; return; } const findUniprotId = response.data.proteins.find( (u) => u.uniprot_id === this.uniprotId ); if (findUniprotId) { this.message = ''; this.uniprotIdErrorMsg = 'The Uniprot ID already exists!'; this.isUniProtIdError = true; return; } this.isError = false; if (this.isDev) { host = require('./js/const').devApiHost; } let url = `${host}/api/update-items`; let data; if (this.getUserData === 'Maintainer') { data = { Entity: 'condensate', EntityId: response.data.canonical_id, ChangeOperation: 'add', Attribute: 'proteins', Value: this.uniprotId, SubmissionComments: this.proteinComment ? this.proteinComment : 'Maintainer do not need to provide a reason for such change at the moment!', Status: 'accepted', }; } else { if (this.proteinComment === '' || this.proteinComment.length < 50) { this.proteinCommentErr = true; this.commentErrorMsg = 'Reason should not be empty or less than 50 characters!'; return; } data = { Entity: 'condensate', EntityId: response.data.canonical_id, ChangeOperation: 'add', Attribute: 'proteins', Value: this.uniprotId, SubmissionComments: this.proteinComment, Status: 'requested', }; } this.isLoading = true; try { const res = await this.axios.post( url, { data: data }, { headers: { Authorization: `Bearer ${this.jwt}`, }, } ); if(res.statusText!=='OK' && res.status >= 400 && res.status <= 499){ this.isLoading = false; this.proteinCommentErr = true; this.commentErrorMsg = `${res.status} ${res.statusText}` return; } if (res.statusText!=='OK' && res.status >= 500 && res.status <= 599) { this.isLoading = false; this.proteinCommentErr = true; this.commentErrorMsg = `${res.status} Internal Server Error. Please try again after some time.`; return; } this.isLoading = false; this.toasterIsOpen = true; this.error = false; this.isSubmitted = true; this.uniprotId = ''; this.comment = ''; this.proteinComment = '', this.proteinCommentErr = false; this.commentErrorMsg = ''; this.updatedKey += 1; setTimeout(() => { this.toasterIsOpen = false; }, 2000); } catch (e) { this.proteinComment = '', this.uniprotId = '', this.message = e.message || 'Something went wrong, please try again later!'; this.isSubmitted = true; this.error = true; } }, fetchPubMedId(item) { if (item) { const url = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&id='; const request = url + item; let $row = $('#' + item); if (!$row.attr('aria-label')) { fetch(request, { method: 'GET', mode: 'cors', headers: {}, }) .then((response) => response.json()) .then((response) => { setTimeout(() => { const res = response.result[item]; $(`a[id="${item}"]`).each(function () { $(this).attr( 'aria-label', `${res.title}\n\n${_.map(res.authors, (a) => a.name).join( ', ' )}` ); }); }, 0); }); } } }, getProteinLinks(uniprots) { if (uniprots) return uniprots .map( (a) => `<a href='/protein/${a}' class="link" target='_blank'>${a}</a>` ) .join(', '); else return ''; }, getDbNames(names) { return _.map(names, (i) => this.dbNames[i]).join(', '); }, getTitleAuthors(title, data) { return `${title}\n\n${_.map(data, (a) => a.name).join(', ')}`; }, tokenize(input, token) { return input.replaceAll(token, '<br/>'); }, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style> @import url("~@/assets/bootstrap.css"); @import url("~@/assets/datatable.css"); @import url("~@/assets/tooltip.css"); /* .star-custom-text { font-size: 1.5em; align-items: center; padding-left: 10px; padding-right: 10px; } */ .main { /*margin-left: 200px;*/ margin-left: 20px; } h3 { margin: 40px 0 0; } #wrapper { overflow-x: hidden; } .checked { color: orange; } .uniprot-link { font-weight: bold; color: #ef0087 !important; } #sidebar-wrapper { min-height: 100vh; margin-left: -15rem; -webkit-transition: margin 0.25s ease-out; -moz-transition: margin 0.25s ease-out; -o-transition: margin 0.25s ease-out; transition: margin 0.25s ease-out; position: absolute; z-index: 1; /* Stay on top */ left: 0; } #sidebar-wrapper .sidebar-heading { padding: 0.875rem 1.25rem; font-size: 1.2rem; } #sidebar-wrapper .list-group { position: -webkit-sticky; position: sticky; top: 0; width: 15rem; } #wrapper.toggled #sidebar-wrapper { margin-left: 0; } @media (min-width: 768px) { #sidebar-wrapper { margin-left: 0; } #wrapper.toggled #sidebar-wrapper { margin-left: -15rem; } } .d-flex { display: -ms-flexbox !important; display: flex !important; } .panel { font-size: 1.2rem; } .table-dark { color: #fff; background-color: #212529; } </style>