diff --git a/components/ServerDbTable.vue b/components/ServerDbTable.vue new file mode 100644 index 0000000000000000000000000000000000000000..c64f77d7e0b372063fc9003fbdea22c7f0d0ab83 --- /dev/null +++ b/components/ServerDbTable.vue @@ -0,0 +1,242 @@ +<script setup lang="ts"> +import type { FacetDistribution } from "meilisearch"; +import { useDisplay } from "vuetify"; +import { useFacetsStore, type Facets } from '~~/stores/facets' + + +interface SortItem { + key: string, + order: boolean | 'asc' | 'desc' +} + +export interface Props { + title?: string + db?: string + sortBy?: SortItem[] + facets: string[] + headers: { title: string, key: string }[] + itemValue: string +} + +export interface FilterItem { + type: 'facet' | 'operator' | 'value' + value: string + title: string + count?: number +} + +const props = withDefaults(defineProps<Props>(), { + title: '', + db: 'refseq', + sortBy: () => [{ key: "type", order: "asc" }], +}); + + +const sortByRef = ref(toValue(props.sortBy)) +const facetsRef = toRef(() => props.facets) + + +const facetStore = useFacetsStore() +const search: Ref<string> = ref(""); +const filter: Ref<string> = ref('') +const filterOrSearch: Ref<FilterItem[] | string | null> = ref(null) +const hitsPerPage: Ref<number> = ref(25) +const limit = ref(1000) +const filterError: Ref<string | null> = ref(null) +const page = ref(1) +const items = ref() +const facetDistribution: Ref<FacetDistribution | undefined> = ref() +const itemsLength = ref(0) +let pending = ref(false) + +const { height } = useDisplay(); +const minTableHeight = ref(400) +const computedTableHeight = computed(() => { + const computedHeight = height.value - 500 + return computedHeight > minTableHeight.value ? computedHeight : minTableHeight.value +}) + +const msSortBy = computed(() => { + if (sortByRef.value.length > 0) { + return sortByRef.value.map((curr) => { + if (curr?.key && curr?.order) { + return `${curr.key}:${curr.order}` + } + else { return "" } + }) + } else { return null } +}) + +const msFilter = computed(() => { + if (Array.isArray(filterOrSearch.value)) { + return filterOrSearch.value.map((it, index) => { + console.log(index, ' ', it) + if (index >= 1 && (index + 1) % 3 === 1) { + console.log("should add AND") + return ` AND ${it.value}` + } else { return it.value } + + }).join("") + } + else { return "" } +}) +onMounted(async () => { + searchOrFilter() + // const { + // hits, + // pending: p, + // totalHits, + // filterError: fe, + // facetDistribution: fd } + // = await useFetchMsDocument( + // "refseq", + // search, + // filter, + // limit, + // hitsPerPage, + // page, + // facetsRef, + // msSortBy + // ) + // console.log("get the hits") + // console.log(hits) + // // console.log([...toValue(hits)]) + // items.value = toValue(hits) + // itemsLength.value = toValue(totalHits) + // facetDistribution.value = toValue(fd) + // filterError.value = toValue(fe) + // pending.value = toValue(p) + +}) +// Fetch results + + +async function searchOrFilter() { + try { + pending.value = true + const { + hits, + totalHits, + // filterError: fe, + facetDistribution: fd } + = await useFetchMsDocument( + "refseq", + search, + msFilter, + limit, + hitsPerPage, + page, + facetsRef, + msSortBy + ) + items.value = unref(hits) + itemsLength.value = toValue(totalHits) + facetDistribution.value = toValue(fd) + filterError.value = toValue(fe) + } catch (error: any) { + filterError.value = error + } + finally { + pending.value = false + } +} + +watch(filterOrSearch, (fos) => { + + if (Array.isArray(fos) && fos?.length % 3 === 0) { + searchOrFilter() + } + else { console.log("do not search") } +}) +watch(page, () => { + searchOrFilter() +}) + + +watch(facetDistribution, (facetDistri) => { + facetStore.setFacets({ facetDistribution: facetDistri, facetStat: undefined }) +}) + +const filterStep = computed(() => { + return (Array.isArray(filterOrSearch.value) && filterOrSearch.value.length > 0) ? filterOrSearch.value?.length % 3 : null +}) +const operatorItems = ref([ + { type: "operator", value: '=', title: "is" }, { type: "operator", value: '!=', title: "is not" } +]) + + +const computedItems = computed(() => { + if (filterStep.value === null || filterStep.value === 0) { + return props.facets.map(value => { + return { + type: "facet", + value, + title: value + } + }) + } + if (filterStep.value === 1) { + return operatorItems.value + } + if (filterStep.value === 2) { + // get the facet value + if (Array.isArray(filterOrSearch.value)) { + const { type, value } = filterOrSearch.value?.slice(-2, -1)[0] + + // + // console.log(facetStore.facets?.facetDistribution[value]) + + + return facetStore.facets?.facetDistribution?.[value] ? Object.entries(facetStore.facets.facetDistribution[value]).map(([key, val]) => { + return { type: "value", value: key, title: key, count: val } + }) : [] + } + } +}) + + + +function selectItem(item) { + filterOrSearch.value = Array.isArray(filterOrSearch.value) ? [...filterOrSearch.value, item] : [item] +} +const autocompleteElem = ref() +</script> +<template> + <v-card flat> + <v-toolbar> + <!-- <v-toolbar-title> + {{ title }} ({{ itemsLength }}) + </v-toolbar-title> --> + <v-autocomplete v-model:search="search" v-model:model-value="filterOrSearch" chips clearable + label="Search or filter results..." :items="computedItems" item-value="value" item-title="title" multiple + return-object append-inner-icon="md:search" @click:appendInner="searchOrFilter" + @click:clear="filterOrSearch = []"> + <template #chip="{ props, item }"> + <v-chip v-bind="props" :text="item.raw.title"></v-chip> + </template> + <template #item="{ props, item }"> + <!-- <pre>{{ props["v-on"] }}</pre> --> + + <!-- <pre v-for="(value, key) in props" :key="key"> + <span v-if="key != 'ref'">{{ key }} - {{ value }}</span> + </pre> --> + <v-list-item v-bind="{ ...props, active: false, onClick: () => selectItem(item) }" :title="item.title" + :subtitle="item.raw?.count ? item.raw.count : ''" :value="props.value"> + + </v-list-item> + </template> + + + + </v-autocomplete> + </v-toolbar> + + <v-data-table-server v-model:page="page" v-model:items-per-page="hitsPerPage" v-model:sortBy="sortByRef" + fixed-header :loading="pending" :headers="headers" :items="items" :items-length="itemsLength" + :item-value="itemValue" multi-sort density="compact" :height="computedTableHeight" class="elevation-1 mt-2"> + <template #[`item.accession_in_sys`]="{ item }"> + <accession-chips :accessions="item.accession_in_sys" baseUrl="http://toto.pasteur.cloud"></accession-chips> + </template> + </v-data-table-server> + </v-card> +</template> \ No newline at end of file diff --git a/components/content/RefseqDb.vue b/components/content/RefseqDb.vue new file mode 100644 index 0000000000000000000000000000000000000000..698aa17df2dfbb38d6cc32d47c9bb448e2e61b8d --- /dev/null +++ b/components/content/RefseqDb.vue @@ -0,0 +1,56 @@ +<script setup lang="ts"> +const sortBy: Ref<{ key: string, order: string }[]> = ref([{ key: 'type', order: "asc" }]) +const itemValue = ref("id"); + +const facets = ref([ + "type", + "Superkingdom", + "phylum", + "order", + "family", + "genus", + "species", +]) +const availableTaxo: Ref<string[]> = ref([ + "species", + "genus", + "family", + "order", + "phylum", + "Superkingdom" +]); + +const headers = ref([ + { title: "Replicon", key: "replicon" }, + { + title: "Type", + key: "type", + }, + { + title: "Subtype", + key: "subtype", + }, + { + title: "Accessions", + key: "accession_in_sys" + } +]) + +const computedHeaders = computed(() => { + return [...headers.value, ...availableTaxo.value.map(taxo => { + return { + title: capitalize(taxo), + key: taxo + } + })] +}) +function capitalize([first, ...rest]) { + return first.toUpperCase() + rest.join('').toLowerCase(); +} + +</script> + +<template> + <ServerDbTable title="RefSeq" db="refseq" :sortBy="sortBy" :headers="computedHeaders" :item-value="itemValue" :facets="facets"> + </ServerDbTable> +</template> \ No newline at end of file diff --git a/composables/useFetchMsDocument.ts b/composables/useFetchMsDocument.ts index d5b981e18fb98a8422e93efcd89b25ad19ce0c72..21a4511365483a6cd97127177163633043ceada7 100644 --- a/composables/useFetchMsDocument.ts +++ b/composables/useFetchMsDocument.ts @@ -1,7 +1,10 @@ import { MeiliSearch } from 'meilisearch' import { useRuntimeConfig, watchEffect, type MaybeRef, ref, toValue } from '#imports' import type { FacetDistribution, Hits } from 'meilisearch'; -export function useFetchMsDocument( +import { useAsyncState } from '@vueuse/core' +import { errorMonitor } from 'events'; + +export async function useFetchMsDocument( index: MaybeRef<string> = ref(""), search: Ref<string> = ref(""), filter: Ref<string> = ref(''), @@ -18,12 +21,11 @@ export function useFetchMsDocument( apiKey: runtimeConfig.public.meiliApiKey }) const pending = ref(false) - const filterError = ref(null) + const filterError: Ref<string | null> = ref(null) const hits: Ref<Hits<Record<string, any>>> = ref([]) const totalHits = ref(0) const totalPages = ref(0) const facetDistribution: Ref<FacetDistribution | undefined> = ref({}) - // reset page when filter and search change watch(filter, () => { page.value = 1 @@ -32,34 +34,36 @@ export function useFetchMsDocument( page.value = 1 }) - watchEffect(async () => { - try { - pending.value = true - const res = await client - .index(toValue(index)) - .search(toValue(search), { - limit: toValue(limit), - filter: toValue(filter), - hitsPerPage: toValue(hitsPerPage), - page: toValue(page), - facets: toValue(facets), - sort: toValue(sort), - }) - filterError.value = null - const { hits: resHits, totalHits: resTotalHits, totalPages: resTotalPages, facetDistribution: facetD } = res - totalHits.value = resTotalHits - hits.value = resHits - totalPages.value = resTotalPages - facetDistribution.value = facetD - } catch ({ code, message }) { - if (code === 'invalid_search_filter') { - filterError.value = message - } - } finally { - pending.value = false - } - }) + try { + pending.value = true + console.log(pending.value) + const res = await client + .index(toValue(index)) + .search(toValue(search), { + limit: toValue(limit), + filter: toValue(filter), + hitsPerPage: toValue(hitsPerPage), + page: toValue(page), + facets: toValue(facets), + sort: toValue(sort), + }) + filterError.value = null + const { hits: resHits, totalHits: resTotalHits, totalPages: resTotalPages, facetDistribution: facetD } = res + + totalHits.value = resTotalHits + hits.value = resHits + totalPages.value = resTotalPages + facetDistribution.value = facetD + pending.value = false + } catch (e: any) { + filterError.value = e + } + finally { + pending.value = false + } + // }) + console.log(hits) return { hits, totalHits, pending, filterError, totalPages, facetDistribution } } diff --git a/content/6.db.md b/content/6.db.md new file mode 100644 index 0000000000000000000000000000000000000000..98f9e569bbe0907be4af32a6f86b2277e56af4ca --- /dev/null +++ b/content/6.db.md @@ -0,0 +1,35 @@ +--- +layout: db +navigation: false +--- + + + +# Refseq + +Lorem markdownum arborea, euntem talia crimine parentque inque, belli nisi parte +induit; ut latos hortamine Aeneadae. Luctus madefacta o fluit ego exciderit +omnibus aestuat. Signum et se **illa vinci me** cortice Mopsopium pressum +*fessos*, inducere superabat liceat me. In et aethera mutavit, dictis sua, sub +insidiaeque, deus ramos illa hostem luet. + +> Est inductaque sunt nec, *sua quam modumque*, peperisse nunc tantum. Quem +> natus non sed; aliquid *artus arvo* alter peragit? Labefecit marcida mirantes +> Numici memor laborem, mirae sequentis det ego borean defensae: innocuae. In +> rate dat verbis spuma saxo aquarum recipit exiguus exstant et quam, Peneiaque. +> **Altaria** ferus super vir. + +Nulli venit pietas quodcumque oraque exemplo ut formae fugae hostibus iubenti, +avia quae. Virgae hominis in inutile; vagantur mortale est fero: sensit ne +felix. + +1. Medicamen tulit dexteriore +2. Imagine veri bonis +3. Male sitim iacentes mittentis cepere dixit contigit +4. Pugman olivis tristique +5. Capere in vomentes cunctis + +::refseq-db +:: + +## des choses à dire apres \ No newline at end of file