CondensateDetailPage.vue 34.07 KiB
<template>
<div class="d-flex" id="wrapper">
<!-- 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 id="page-content-wrapper" class="main">
<fetch-condensate :condensate="condensate">
<template slot-scope="{ response, loading }">
<slot :response="response" :loading="loading">
<div v-if="loading || response === null">Loading...</div>
<div v-else>
<h2>{{ response.data.name }}</h2>
<h4 class="round">General Information</h4>
<div class="panel panel-default">
<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">Species</div>
<div class="col-sm-9">
{{ response.data.species_name }}
</div>
</div>
<div class="row">
<div class="text col-sm-3">Description</div>
<div class="col-sm-9">
{{ 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
class="panel panel-default"
v-if="showUpdateDescription"
>
<div class="panel-body">
<div class="container-fluid col-md-12">
<form
class="form-horizontal"
autocomplete="off"
@submit.prevent="updateDescription(response)"
>
<div class="form-group">
<label
class="control-label col-sm-2"
for="keyword"
>Update description</label
>
<div class="col-sm-10">
<textarea
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
"
id="comment"
rows="5"
placeholder="Description"
v-model.trim="description"
@keyup="descriptionKeyup"
></textarea>
<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
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
"
id="funComment"
rows="5"
:placeholder="
getUserData === 'Maintainer'
? 'Is optional'
: 'Is Mandatory'
"
v-model.trim="comment"
@keyup="commentKeyup"
></textarea>
<p
v-if="isCommentError"
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
class="btn btn-primary"
type="submit"
id="dropdownMenuButton"
>
Update
</button>
<button
class="btn btn-danger border"
type="button"
id="dropdownMenuButton"
@click="toggleUpdateDescription"
>
cancel
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!--<div class="row">-->
<!--<div class="text col-sm-3">Evidence Stars</div>-->
<!--<div class="col-sm-9 tooltipped tooltipped-w"-->
<!--:aria-label="getDbNames(response.data.data_sources)">-->
<!--<span v-for="(item, index) in getRating(response.data.data_sources)" :class="item" v-bind:key="index"/>-->
<!--</div>-->
<!--</div>-->
<div class="row" v-show="response.data.synonyms">
<div class="text col-sm-3">Also Known As</div>
<div class="col-sm-9">
{{
response.data.synonyms
? response.data.synonyms
.map((a) =>
a
.replace("-", " ")
.replace(/\b\w/g, (l) => l.toUpperCase())
)
.join(", ")
: ""
}}
</div>
</div>
<div class="row">
<div class="text col-sm-3">
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)"
></span>
<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"
:marker-data="response.data.biomarkers"
:condensate-id="response.data.canonical_id"
:proteins="response.data.proteins"
@close="closeAddDeleteMarker"
/>
</div>
</div>
<div class="row">
<div class="text col-sm-3">No. of Proteins</div>
<div class="col-sm-9">
{{ response.data.protein_count }}
</div>
</div>
</div>
</div>
</div>
<div v-show="response.data.experiments.length > 0">
<h4 class="round">Experiments</h4>
<div class="panel panel-default">
<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"
v-bind: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>
<fetch-pub-med :link="item.publication_link">
<template slot-scope="{ response, loading }">
<slot :response="response" :loading="loading">
<div v-if="loading"></div>
<div v-else>
<a
:href="item.publication_link"
class="
uniprot-link
tooltipped
tooltipped-n
tooltipped-multiline
"
:aria-label="
getTitleAuthors(
response.title,
response.authors
)
"
target="_blank"
>
PubMed
<i class="glyphicon glyphicon-link"></i>
</a>
</div>
</slot>
</template>
</fetch-pub-med>
</td>
<td
v-html="tokenize(item.solute_concentrations, ';')"
></td>
<td>
{{ item.temperature }}
</td>
<td>
<ul
v-for="(item, index) in item.salts"
v-bind:key="index"
>
<li
v-for="(value, key, index) in item"
v-bind:key="index"
>
{{ key }} : {{ value }}
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h4 class="round">Proteins</h4>
<button
v-if="
getUserData !== null &&
(getUserData === 'Maintainer' ||
getUserData === 'Contributor')
"
class="btn btn-primary dropdown-toggle"
type="button"
@click="showAddProtein = !showAddProtein"
>
Add a protein to this condensate
</button>
<div v-if="showAddProtein" class="panel panel-default mt-4">
<div class="panel-body">
<div class="container-fluid col-md-12">
<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 uniprod ID</label
>
<div class="col-sm-4">
<input
class="form-control input-sm"
id="keyword"
type="text"
v-model.trim="uniprotId"
placeholder="Uniprot ID"
@keyup="uniprotKeyup"
/>
<p
v-if="isUniProtIdError"
class="text-red-600 mt-4 font-bold"
>
{{ uniprotIdErrorMsg }}
</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="keyword"
>Reason</label
>
<div class="col-sm-4">
<textarea
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
"
id="comment"
rows="5"
:placeholder="
getUserData === 'Maintainer'
? 'Is optional.'
: 'Is mandatory.'
"
v-model.trim="comment"
@keyup="commentKeyup"
></textarea>
<p
v-if="isCommentError"
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
class="btn btn-primary"
type="submit"
id="dropdownMenuButton"
>
Add
</button>
<button
class="btn btn-danger border"
type="button"
id="dropdownMenuButton"
@click="showAddProtein = !showAddProtein"
>
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"
>
</llps-table>
<!-- <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> -->
<h4 class="round">Requests</h4>
<condensate-update-items-table id="condensateUpdateItem" :data="response.data.canonical_id" />
<!-- <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>
<!-- /#wrapper -->
</template>
<script>
import fetchCondensate from "@/components/DDCODE/fetchCondensate.vue";
import llpsTable from "@/components/LlpsTable.vue";
import fetchPubMed from "@/components/DDCODE/fetchPubMed";
import AddDeleteMarker from "./CMS/addDeleteMarker.vue";
import CondensateUpdateItemsTable from './CondensateUpdateItemsTable.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,
fetchPubMed,
AddDeleteMarker,
CondensateUpdateItemsTable,
// 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: "",
description: "",
descriptionMsg: "",
descriptionErrorMsg: "",
isUniProtIdError: false,
isCommentError: false,
isSubmitted: false,
error: false,
showUpdateDescription: false,
showAddDeleteMarker: false,
commentErrorMsg: "",
uniprotIdErrorMsg: "",
message: "",
whitespaceRegex: /(\s)/,
toggleModel: false,
};
},
computed: {
jwt: function () {
return this.$store.getters["User/jwt"];
},
getUserData() {
const userRole = this.$store.getters["User/userRole"];
return userRole;
},
},
methods: {
confirm() {
this.toggleModel = false;
},
cancel() {
this.toggleModel = false;
},
closeAddDeleteMarker() {
this.comment = "";
this.showAddDeleteMarker = false;
},
uniprotKeyup() {
this.message = "";
this.isUniProtIdError = false;
},
commentKeyup() {
this.message = "";
this.isCommentError = false;
},
descriptionKeyup() {
this.descriptionMsg = "";
this.descriptionErrorMsg = "";
},
toggleUpdateDescription(res) {
this.description = res;
this.descriptionMsg = "";
this.descriptionErrorMsg = "";
this.comment = "";
this.showUpdateDescription = !this.showUpdateDescription;
},
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.comment,
Status: "accepted",
};
} else {
if (this.comment === "") {
this.isCommentError = true;
this.commentErrorMsg = "Reason should not be empty!";
return;
}
data = {
Entity: "condensate",
EntityId: response.data.canonical_id,
ChangeOperation: "update",
Attribute: "description",
Value: this.description,
SubmissionComments: this.comment,
Status: "requested",
};
}
try {
await this.axios.post(
url,
{ data: data },
{
headers: {
Authorization: `Bearer ${this.jwt}`,
},
}
);
this.descriptionMsg = "Request submitted successfully!";
this.descriptionErrorMsg = "";
this.description = response.data.description;
this.commentErrorMsg = "";
this.comment = "";
this.isCommentError = false;
} catch (e) {
console.error(e);
this.descriptionMsg = "";
this.descriptionErrorMsg =
e.message || "Something went wrong, please try again later!";
}
},
async addProtein(response) {
if (this.uniprotId === "") {
this.uniprotIdErrorMsg = "Uniprot ID should not be emplty!";
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 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.comment,
Status: "accepted",
};
} else {
if (this.comment === "") {
this.commentErrorMsg = "Reason should not be empty!";
this.isCommentError = true;
return;
}
data = {
Entity: "condensate",
EntityId: response.data.canonical_id,
ChangeOperation: "add",
Attribute: "proteins",
Value: this.uniprotId,
SubmissionComments: this.comment,
Status: "requested",
};
}
try {
await this.axios.post(
url,
{ data: data },
{
headers: {
Authorization: `Bearer ${this.jwt}`,
},
}
);
this.message = "Request submitted successfully!";
this.error = false;
this.isSubmitted = true;
this.uniprotId = "";
this.comment = "";
} catch (e) {
console.error(e);
this.message =
e.message || "Something went wrong, please try again later!";
this.isSubmitted = true;
this.error = true;
}
try {
const res=await this.axios.get(
`${host}
/api/update-item/findCondensate/${response.data.canonical_id}`,
{
headers: {
Authorization: `Bearer ${this.jwt}`,
},
}
);
console.log(await res)
} catch (e) {
console.error(e);
}
},
// async getRequest() {
// if (this.isDev) {
// host = require("./js/const").devApiHost;
// }
// let url = `${host}/api/update-items`;
// try {
// const response = await this.axios.get(
// url,
// {
// headers: {
// Authorization: `Bearer ${this.jwt}`,
// },
// }
// );
// return response.data.data;
// } catch (e) {
// console.error(e);
// this.message =
// e.message || "Something went wrong, please try again later!";
// this.isSubmitted = true;
// this.error = true;
// }
// },
// async getSumittedRequest() {
// const requestData = await this.getRequest();
// console.log("get req", requestData);
// },
getProteinLinks(uniprots) {
if (uniprots)
return uniprots
.map((a) => `<a href='/protein/${a}' 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/>");
},
getRating(data) {
const scoreMap = { hungarian: 5, blue: 5, pink: 1, grey: 1 };
const rating = _.max(_.map(data, (i) => scoreMap[i]));
const r = [];
for (let i = 0; i < 5; i++) {
if (i < rating) {
r.push("fa fa-star checked");
} else {
r.push("fa fa-star");
}
}
return r;
},
},
};
</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");
.main {
/*margin-left: 200px;*/
margin-left: 20px;
}
h3 {
margin: 40px 0 0;
}
a {
color: #42b983;
}
#wrapper {
overflow-x: hidden;
}
.checked {
color: orange;
}
#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;
}
#page-content-wrapper {
min-width: 100vw;
}
#wrapper.toggled #sidebar-wrapper {
margin-left: 0;
}
@media (min-width: 768px) {
#sidebar-wrapper {
margin-left: 0;
}
#page-content-wrapper {
min-width: 0;
width: 100%;
}
#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>