Select Git revision
StructureDb.vue
StructureDb.vue 12.79 KiB
<script setup lang="ts">
import type { SortItem, AutocompleteMeiliFacetProps } from "@/components/ServerDbTable.vue"
import { useNumericalFilter } from "@/composables/useNumericalfilter"
import { useRefinedUrl } from "@/composables/useRefinedUrl"
import { ServerDbTable } from "#components"
import { joinURL } from 'ufo'
import { useStructuresBasket } from "~/stores/structuresBasket"
interface Item {
Foldseek_name: string
system: string
}
const sortBy: Ref<SortItem[]> = ref([{ key: 'system', order: "asc" }])
const itemValue = ref("id");
const dbName = ref("structure")
const client = useMeiliSearchRef()
const structureTitle = ref("Structure")
const structureBasket = useStructuresBasket()
onMounted(async () => {
try {
const data = await client.index(toValue(dbName)).search("", {
facets: ["*"],
filter: [],
page: 1,
hitsPerPage: 25,
})
autocompleteMeiliFacetsProps.value.facetDistribution = data?.facetDistribution
} catch (error) {
throw createError("Unable to get structures")
}
})
const autocompleteMeiliFacetsProps = ref<AutocompleteMeiliFacetProps>({
db: toValue(dbName),
facets: [
{ title: "Defense System", type: "subheader" },
{ title: "System", value: "system", type: "facet", icon: "i-tabler:virus-off", },
{ title: "Subsystem", value: "subsystem", type: "facet", icon: "i-tabler:virus-off" },
{ type: "divider" },
{ title: "Completed", value: "completed", type: "facet", icon: "md:done" },
{ title: "Prediction type", value: "prediction_type", type: "facet", icon: "i-gravity-ui:molecule" },
],
facetDistribution: undefined
})
const computedAutocompleteMeiliFacetsProps = computed(() => {
const toValFacetDistribution = toValue(autocompleteMeiliFacetsProps).facetDistribution
const toValFacets = toValue(autocompleteMeiliFacetsProps).facets
if (toValFacetDistribution !== undefined && toValFacets !== undefined) {
return {
...toValue(autocompleteMeiliFacetsProps), facets: toValFacets.map(facet => {
if (facet.type === "facet") {
const count = toValFacetDistribution?.[facet.value] ? Object.keys(toValFacetDistribution[facet.value]).length : undefined
return count ? { ...facet, count } : { ...facet }
}
else {
return { ...facet }
}
})
}
}
else {
return toValue(autocompleteMeiliFacetsProps)
}
})
const headers: Ref<Object[]> = ref([
{ title: 'Structure', key: 'structure', sortable: false, removable: false, fixed: true, minWidth: "110px" },
{ title: 'Foldseek', key: 'Foldseek_name', sortable: false },
{ title: "System", key: "system", removable: false },
// { title: "Gene name", key: "gene_name", removable: false },
{ title: "Sub-system", key: "subsystem", removable: false },
// { title: "pdb file", key: "pdb" },
// { title: "fasta", key: "fasta_file" },
{ title: "Proteins in structure", key: 'proteins_in_the_prediction', sortable: false, removable: true },
{ title: "System genes", key: "system_genes", sortable: false, removable: true },
{ title: "Sys id", key: "nb_sys", removable: true },
{ title: "Completed", key: "completed", removable: true },
{ title: "Prediction type", key: "prediction_type", removable: true },
{ title: "N genes in sys", key: "system_number_of_genes", removable: true },
{ title: "pLDDT", key: "plddts", removable: true },
{ title: "iptm+ptm", key: "iptm+ptm", removable: true },
{ title: "pDockQ", key: "pDockQ", removable: true },
// { title: "Type", key: "type", removable: true },
])
const { range: plddtsRange, stringifyFilter: plddtsFilter, reset: plddtsReset } = useNumericalFilter("plddts", 0, 100)
const { range: iptmRange, stringifyFilter: iptmFilter, reset: iptmReset } = useNumericalFilter("iptm+ptm", 0, 1)
const { range: pdockqRange, stringifyFilter: pdockqFilter, reset: pdockqReset } = useNumericalFilter("pDockQ", 0, 1)
function isString(item: Ref<string | undefined>): item is Ref<string> {
return toValue(item) !== undefined
}
const numericalFilters = computed(() => {
const listFilters = [plddtsFilter, iptmFilter, pdockqFilter].filter(isString).map(f => toValue(f))
return listFilters.length > 0 ? listFilters : undefined
})
const defaultDataTableServerProps = ref({
showExpand: false
})
const dataTableServerProps = computed(() => {
return {
...toValue(defaultDataTableServerProps),
headers: toValue(headers),
itemValue: toValue(itemValue)
}
})
function toFolseekUrl(item: Item) {
const url = joinURL("/" + item.system, item.Foldseek_name)
const { refinedUrl } = useRefinedUrl(url)
return toValue(refinedUrl)
}
function extractGeneName(name: string) {
if (name.includes("__")) {
return name.split("__")[1]
}
if (name.includes("_")) {
return name.split("_")[1]
}
return undefined
}
function namesToCollapsibleChips(names: string[], systemDir: string, file: string | null = null) {
if (file === null) {
return names.filter((it) => it !== "").map(it => ({ title: extractGeneName(it) }))
} else {
return names.filter((it) => it !== "").map(it => ({ title: extractGeneName(it), href: `/wiki/${systemDir}/${file}` }))
}
}
function pdbNameToCif(pdbPath: string) {
const cifPath = pdbPath.split(".").slice(0, -1).join(".")
return `${cifPath}.cif`
}
function buildStructureUrl(item) {
return [`/${item.system}/${pdbNameToCif(item.pdb)}`, `/${item.system}/${item.pdb}`]
}
function displayStructure(item) {
structureBasket.set(buildStructureUrl(item).map(url => {
return toValue(useRefinedUrl(url).refinedUrl)
}))
structureTitle.value = `${item.subtype} - ${item.gene_name}`
}
const { refinedUrl: downloadAllPdbUrl } = useRefinedUrl("/df-all-pdbs.tar.gz")
const { refinedUrl: downloadAllCifUrl } = useRefinedUrl("/df-all-cifs.tar.gz")
</script>
<template>
<v-card>
<ServerDbTable title="Predicted Structures" :sortBy="sortBy" :data-table-server-props="dataTableServerProps"
:autocomplete-meili-facets-props="computedAutocompleteMeiliFacetsProps"
:numerical-filters="toRef(numericalFilters)">
<template #numerical-filters="{ search }">
<v-card-text>
<v-row>
<v-col cols="12" md="4">
<v-card flat variant="tonal">
<v-card-item class="mb-4">
<v-card-title> pLDDT
</v-card-title>
</v-card-item>
<v-card-text class="pr-0">
<v-range-slider v-model="plddtsRange" strict density="compact" hide-details="auto"
class="" step="0.5" :min="0" :max="100" thumb-label="always"
@update:modelValue="search()">
<template #append>
<v-btn variant="text" density="compact" icon="md:restart_alt"
@click="plddtsReset()"></v-btn>
</template>
</v-range-slider>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="4">
<v-card flat variant="tonal">
<v-card-item class="mb-4">
<v-card-title> iptm+ptm
</v-card-title>
</v-card-item>
<v-card-text class="pr-0">
<v-range-slider v-model="iptmRange" strict density="compact" hide-details="auto"
step="0.1" :min="0" :max="1" thumb-label="always" @update:modelValue="search()">
<template #append>
<v-btn variant="text" density="compact" icon="md:restart_alt"
@click="iptmReset()"></v-btn>
</template>
</v-range-slider>
</v-card-text></v-card>
</v-col>
<v-col cols="12" md="4">
<v-card flat variant="tonal">
<v-card-item class="mb-4">
<v-card-title> pDockQ
</v-card-title>
</v-card-item>
<v-card-text class="pr-0">
<v-range-slider v-model="pdockqRange" density="compact" strict hide-details="auto"
step="0.1" :min="0" :max="1" thumb-label="always" @update:modelValue="search()">
<template #append>
<v-btn variant="text" density="compact" icon="md:restart_alt"
@click="pdockqReset()"></v-btn>
</template>
</v-range-slider>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</template>
<template #[`item.Foldseek_name`]="{ item }">
<FoldseekDialog v-if="item.Foldseek_name !== 'na'" :foldseek-path="toFolseekUrl(item)"></FoldseekDialog>
</template>
<template #[`item.proteins_in_the_prediction`]="{ item }">
<CollapsibleChips
:items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.system, item.fasta_file)">
</CollapsibleChips>
</template>
<template #[`item.system_genes`]="{ item }">
<CollapsibleChips :items="namesToCollapsibleChips(item.system_genes, item.system)">
</CollapsibleChips>
</template>
<template #[`item.structure`]="{ item }">
<v-row justify="space-between" dense no-gutters align="center">
<v-col>
<v-btn size="x-small" variant="text" icon="md:visibility"
@click="displayStructure(item)"></v-btn>
</v-col>
<v-col>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn :disabled="item.structuresUrls?.length < 1" size="x-small" variant="text"
icon="md:download" class="ml-1" v-bind="props"></v-btn>
</template>
<v-list>
<v-list-item v-for="(url, index) in item.structuresUrls" :key="index" :value="index"
:href="url">
<v-list-item-title>{{ url.split('.').slice(-1)[0] }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
</v-row>
</template>
<template #[`item.completed`]="{ item }">
<v-icon v-if="item.completed" color="success" icon="md:check"></v-icon>
<v-icon v-else color="warning" icon="md:dangerous"></v-icon>
</template>
<template #toolbar-items>
<v-menu>
<template v-slot:activator="{ props }">
<!-- <v-tooltip activator="parent" location="top">Download all structures</v-tooltip> -->
<v-btn class="align-self-end" v-bind="props" icon="md:download">
</v-btn>
</template>
<v-list>
<v-list-item value="pdb" :href="downloadAllPdbUrl">
<v-list-item-title>all pdbs</v-list-item-title>
</v-list-item>
<v-list-item value="cif" :href="downloadAllCifUrl">
<v-list-item-title>all cifs</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
</ServerDbTable>
<PdbeMolstarPlugin v-model="stuctureUrls" v-model:title="structureTitle" />
</v-card>
</template>