diff --git a/components/AutocompleteMeiliFacets.vue b/components/AutocompleteMeiliFacets.vue index edeac6077bbcb4c231949797e5e30ab34ced5517..23a375932e4f6f738b2af952da8bff0dfc25ad37 100644 --- a/components/AutocompleteMeiliFacets.vue +++ b/components/AutocompleteMeiliFacets.vue @@ -11,14 +11,26 @@ export interface FilterItem { export interface FacetItem { title: string value: string + type: "facet" + icon?: string + count?: number + } +export interface OperatorItem { + title: string, + type: 'operator' + +} export interface FacetCategory { title: string - icon: string + type: "subheader" +} +export interface FacetDivider { + type: "divider" } -export type FacetInputItem = FacetItem | FacetCategory +export type FacetInputItem = FacetItem | FacetCategory | FacetDivider export interface Props { db: string @@ -34,10 +46,10 @@ const props = withDefaults(defineProps<Props>(), { const filterItems = ref<FilterItem[] | undefined>(undefined) // const { result: msResult } = useMeiliSearch(props.db) const isAutocompleteFocused = ref<boolean>(false) -const facetDistribution: Ref<Record<string, Record<string, number>>> = useState('facetDistribution') +// const facetDistribution: Ref<Record<string, Record<string, number>>> = useState('facetDistribution') const computedFacets = computed(() => { - const toValFacetDistri = toValue(facetDistribution) + const toValFacetDistri = toValue(props.facetDistribution) if (toValFacetDistri) { return Object.keys(toValFacetDistri) } @@ -85,17 +97,36 @@ const autocompleteItems = computed(() => { const toValFilterItems = toValue(filterItems) const index = toValFilterItems?.length ?? 0 if (filterStep.value === undefined || filterStep.value === 0) { - return toValue(computedFacets)?.map(value => { - return { - type: "facet", - value: `${value}-${index}`, - title: value, - deletable: false, - props: { - deletable: false, - type: "facet" - } + return toValue(props.facets)?.map(facetItem => { + switch (facetItem.type) { + case "facet": + return { + type: "facet", + value: `${facetItem.value}-${index}`, + title: facetItem.title, + deletable: false, + icon: facetItem?.icon, + props: { + deletable: false, + type: "facet" + } + } + case "subheader": + return { + type: "subheader", + title: facetItem.title, + deletable: false, + + props: { + type: "subheader" + } + } + case "divider": + return { type: "divider" } + default: + break; } + }) } if (filterStep.value === 1) { @@ -108,7 +139,7 @@ const autocompleteItems = computed(() => { const sanitizedValue = value.split("-")[0] // console.log("compute new facets") // const facetDistri = msResult.value?.facetDistribution - const facetDistri = toValue(facetDistribution) + const facetDistri = toValue(props.facetDistribution) // console.log(facetDistri) return facetDistri?.[sanitizedValue] ? Object.entries(facetDistri[sanitizedValue]).map(([key, val]) => { return { @@ -201,6 +232,11 @@ function deleteOneFilter(index: number) { // filterItems.value?.splice(index - 2, 2) } + +function isItemFilter(type: string | undefined) { + return type === "facet" || type === "innerOperator" || type === "outerOperator" || type === "value" +} + </script> <template> <v-autocomplete v-model:model-value="filterItems" :items="autocompleteItems" auto-select-first chips clearable multiple @@ -208,10 +244,12 @@ function deleteOneFilter(index: number) { item-value="value" item-title="title" class="mx-2" @click:clear="clearFilters" @update:focused="updateAutocompleteFocused"> <template #item="{ props, item }"> - <v-list-item v-bind="{ ...props, active: false }" :title="item.title" + <v-list-item v-if="isItemFilter(item?.raw?.type)" v-bind="{ ...props, active: false }" :title="item.title" + :prepend-icon="item?.raw?.icon ? item.raw.icon : undefined" :subtitle="item.raw?.count ? item.raw.count : ''" :value="props.value"> - </v-list-item> + <v-divider v-if="item.raw.type === 'divider'"></v-divider> + <v-list-subheader v-if="item.raw.type === 'subheader'" :title="item.raw.title"></v-list-subheader> </template> <template #chip="{ props, item, index }"> <v-chip v-bind="props" :text="item.raw.title" :closable="item.props.deletable" diff --git a/components/ServerDbTable.vue b/components/ServerDbTable.vue index a11c7e8dd4c1689febdceb562464a4d1d7c3a309..394f7b2085af5e4512cd22f39f43944f6f1d80d5 100644 --- a/components/ServerDbTable.vue +++ b/components/ServerDbTable.vue @@ -30,7 +30,7 @@ export interface Props { numericalFilters?: MaybeRef<string | undefined> dataTableServerProps: Record<string, any> columnsToDownload?: MaybeRef<string[] | undefined> - facetDistribution: MaybeRef<Record<string, Record<string, number>>> + facetDistribution: MaybeRef<Record<string, Record<string, number>> | undefined> } export interface FilterItem { @@ -52,6 +52,7 @@ const props = withDefaults(defineProps<Props>(), { }); +// const facetDistribution: Ref<Record<string, Record<string, number>> | undefined> = useState(`refseqFacetDistribution`) const slots = useSlots() @@ -221,24 +222,6 @@ const msFacetDistributionParams = computed(() => { return { index: props.db, query: "", params: { ...paginationParams.value } } }) - -// watch(filterInputValues, (newSoF) => { -// if (isFilter.value && filterInputValues.value !== null && filterInputValues.value?.length % 3 === 0) { -// msFilter.value = filterInputValues.value.map((it, index) => { - -// const sanitizedValue = it.value.split("-").slice(0, -1).join("-") -// if (index >= 1 && (index + 1) % 3 === 1) { -// return ` AND ${sanitizedValue}` -// } else if ((index + 1) % 3 === 0) { -// return `"${sanitizedValue}"` -// } else { -// return `${sanitizedValue}` -// } - -// }).join("") -// } -// }) - watch(search, () => { searchOrFilter() // emitRefreshRes() @@ -269,17 +252,32 @@ const autocompleteItems = computed(() => { const index = filterOrSearch.value?.length ?? 0 // console.log(index) if (filterStep.value === null || filterStep.value === 0) { - return toValue(facetsRef).map(value => { - return { - type: "facet", - value: `${value}-${index}`, - title: value, - deletable: false, - props: { - deletable: false, - type: "facet" - } + return toValue(facetsRef).map(facetItem => { + switch (facetItem.type) { + case "facet": + return { + type: "facet", + value: `${facetItem.value}-${index}`, + title: facetItem.title, + deletable: false, + props: { + deletable: false, + type: "facet" + } + } + case "subheader": + return { + type: "subheader", + title: facetItem.title, + deletable: false, + props: { + type: "subheader" + } + } + default: + break; } + }) } if (filterStep.value === 1) { @@ -390,7 +388,7 @@ function focusedOrBlur(isFocused: boolean) { </v-toolbar> --> <v-toolbar> <AutocompleteMeiliFacets v-model="msFilterCompo" :db="props.db" :facets="$props.facets" - :facet-distribution="msResult?.value?.facetDistribution" @update:modelValue="emitRefreshRes"> + :facet-distribution="props.facetDistribution" @update:modelValue="emitRefreshRes"> </AutocompleteMeiliFacets> </v-toolbar> </template> @@ -432,8 +430,7 @@ function focusedOrBlur(isFocused: boolean) { </v-card>--> <v-card variant="flat" color="transparent" :min-width="500" class="mx-2" :rounded="false"> <AutocompleteMeiliFacets v-model="msFilterCompo" :db="props.db" :facets="props.facets" - :facet-distribution="msResult?.value?.facetDistribution" - @update:modelValue="emitRefreshRes"> + :facet-distribution="props.facetDistribution" @update:modelValue="emitRefreshRes"> </AutocompleteMeiliFacets> </v-card> diff --git a/components/content/RefseqDb.vue b/components/content/RefseqDb.vue index f461c4338debc1f5477632e6932de667f348afaf..2b7c6cb4c85e3a3aea6144299f792433201cf0b2 100644 --- a/components/content/RefseqDb.vue +++ b/components/content/RefseqDb.vue @@ -18,7 +18,7 @@ const { width } = useDisplay(); const dbName = ref("refseq") const scaleTransform: Ref<string[]> = ref([]) -const facetDistribution: Ref<Record<string, Record<string, number>>> = useState(`${toValue(dbName)}FacetDistribution`) +const facetDistribution: Ref<Record<string, Record<string, number>> | undefined> = ref(undefined) await callOnce(async () => { console.log("dans le call once") @@ -33,26 +33,59 @@ await callOnce(async () => { console.log(data) + facetDistribution.value = toValue(data)?.facetDistribution }) +// onMounted(async () => { +// console.log("dans le mounted refseq") +// const { data } = await useAsyncMeiliSearch({ +// index: toValue(dbName), query: "", params: { +// facets: ["*"], +// filter: [], +// page: 1, +// hitsPerPage: 25, +// } +// }) +// console.log(toValue(data)) +// facetDistribution.value = toValue(data)?.facetDistribution + +// }) const { serialize } = useSerialize() const { rasterize } = useRasterize() const { download } = useDownloadBlob() + + + const facets = ref<FacetInputItem[]>([ - { title: "Replicon", value: "replicon", icon: "" }, - { title: "Defense System", icon: "" }, - { title: "System", value: "type", icon: "" }, - { title: "Subsystem", value: "subtype", icon: "" }, - { title: "Taxonomy", icon: "", }, - { title: "Superkingdom", value: "Superkingdom", icon: "" }, - { title: "", value: "phylum", icon: "" }, - { title: "", value: "order", icon: "" }, - { title: "", value: "family", icon: "" }, - { title: "", value: "genus", icon: "" }, - { title: "", value: "species", icon: "" }, + { title: "Defense System", type: "subheader" }, + { title: "System", value: "type", type: "facet", icon: "mdi-virus-outline", }, + { title: "Subsystem", value: "subtype", type: "facet", icon: "mdi-virus-outline" }, + { type: "divider" }, + { title: "Taxonomy", type: "subheader" }, + { title: "Superkingdom", value: "Superkingdom", type: "facet", icon: "mdi-family-tree" }, + { title: "Phylum", value: "phylum", type: "facet", icon: "mdi-family-tree" }, + { title: "Order", value: "order", type: "facet", icon: "mdi-family-tree" }, + { title: "Family", value: "family", type: "facet", icon: "mdi-family-tree" }, + { title: "Genus", value: "genus", type: "facet", icon: "mdi-family-tree" }, + { title: "Species", value: "species", type: "facet", icon: "mdi-family-tree" }, + { type: "divider" }, + { title: "Replicon", value: "replicon", type: "facet", icon: "mdi-dna", }, + ]) + + +const computedFacets = computed(() => { + const toValFacetDistribution = toValue(facetDistribution) + console.log(toValFacetDistribution) + return toValue(facets).map(facet => { + const count = toValFacetDistribution?.[facet.value] ? Object.values(toValFacetDistribution[facet.value]).reduce((acc, curr) => { return acc + curr }, 0) : undefined + + return count ? { ...facet, count } : { ...facet } + }) +}) + const availableTaxo: Ref<string[]> = ref([ "species", "genus", @@ -428,8 +461,8 @@ async function downloadPng(component: ComponentPublicInstance | null, filename: </v-expansion-panel> </v-expansion-panels> - <ServerDbTable title="RefSeq" :db="dbName" :sortBy="sortBy" :facets="facets" - :facet-ditribution="facetDistribution" :data-table-server-props="dataTableServerProps" + <ServerDbTable title="RefSeq" :db="dbName" :sortBy="sortBy" :facets="computedFacets" + :facet-distribution="facetDistribution" :data-table-server-props="dataTableServerProps" @refresh:search="(params) => getAllHits(params)"> <template #[`item.accession_in_sys`]="{ item }">