diff --git a/ui/bedbase-types.d.ts b/ui/bedbase-types.d.ts index 410d2cb..ded3271 100644 --- a/ui/bedbase-types.d.ts +++ b/ui/bedbase-types.d.ts @@ -678,6 +678,17 @@ export interface components { /** Results */ results?: components["schemas"]["QdrantSearchResult"][]; }; + /** BedNeighborsResult */ + BedNeighboursResult: { + /** Count */ + count: number; + /** Limit */ + limit: number; + /** Offset */ + offset: number; + /** Results */ + results?: components["schemas"]["QdrantSearchResult"][]; + }; /** BedMetadataAll */ BedMetadataAll: { /** diff --git a/ui/src/components/bed-splash-components/cards/mean-region-width-card.tsx b/ui/src/components/bed-splash-components/cards/mean-region-width-card.tsx index 3a4f2b3..f094631 100644 --- a/ui/src/components/bed-splash-components/cards/mean-region-width-card.tsx +++ b/ui/src/components/bed-splash-components/cards/mean-region-width-card.tsx @@ -12,7 +12,7 @@ export const MeanRegionWidthCard = (props: Props) => { return ( diff --git a/ui/src/components/bed-splash-components/cards/no-regions-card.tsx b/ui/src/components/bed-splash-components/cards/no-regions-card.tsx index 998e130..b38836c 100644 --- a/ui/src/components/bed-splash-components/cards/no-regions-card.tsx +++ b/ui/src/components/bed-splash-components/cards/no-regions-card.tsx @@ -11,7 +11,7 @@ export const NoRegionsCard = (props: Props) => { const { metadata } = props; return ( diff --git a/ui/src/components/bed-splash-components/header.tsx b/ui/src/components/bed-splash-components/header.tsx index 1887704..245befe 100644 --- a/ui/src/components/bed-splash-components/header.tsx +++ b/ui/src/components/bed-splash-components/header.tsx @@ -141,15 +141,12 @@ export const BedSplashHeader = (props: Props) => { -
- {metadata.name} -
- {metadata.description} -
+
+
{metadata.name}
+

{metadata?.description || 'No description available'}

-

@@ -270,7 +267,7 @@ export const BedSplashHeader = (props: Props) => {

- Last update:{' '} + Updated:{' '} {metadata?.last_update_date ? formatDateTime(metadata?.last_update_date) : 'No date available'}

diff --git a/ui/src/components/bed-splash-components/plots.tsx b/ui/src/components/bed-splash-components/plots.tsx index a773d49..c19747f 100644 --- a/ui/src/components/bed-splash-components/plots.tsx +++ b/ui/src/components/bed-splash-components/plots.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Col, Image, Row } from 'react-bootstrap'; import { components } from '../../../bedbase-types'; -import { chunkArray, makeThumbnailImageLink } from '../../utils'; +import { chunkArray, makeThumbnailImageLink , makePDFImageLink } from '../../utils'; import { Fragment } from 'react'; import { FigureModal } from '../modals/figure-modal'; @@ -13,12 +13,13 @@ type PlotsProps = { type PlotProps = { src: string; + pdf: string; alt: string; title: string; }; const Plot = (props: PlotProps) => { - const { src, alt, title } = props; + const { src, pdf, alt, title } = props; const [show, setShow] = useState(false); return ( @@ -31,13 +32,13 @@ const Plot = (props: PlotProps) => { }} className="h-100 border rounded p-1 shadow-sm hover-border-primary transition-all" > -
- {title} +
+ {title} {/* */}
-
+
{alt}
{ }} title={title} src={src} + pdf={pdf} alt={alt} />
@@ -55,13 +57,14 @@ const Plot = (props: PlotProps) => { export const Plots = (props: PlotsProps) => { const { metadata } = props; + const plotNames = metadata.plots ? Object.keys(metadata.plots) : []; return ( -
+ {metadata.plots && chunkArray(plotNames, 3).map((chunk, idx) => ( - + {chunk.map((plotName) => { // this is for type checking const plotNameKey = plotName as keyof typeof metadata.plots; @@ -73,19 +76,20 @@ export const Plots = (props: PlotsProps) => { metadata.plots[plotNameKey]?.description || metadata.plots[plotNameKey].title : plotName; return ( - + ); })} - + ))} -
+
); }; diff --git a/ui/src/components/bedset-splash-components/beds-table.tsx b/ui/src/components/bedset-splash-components/beds-table.tsx index 69e2e00..7fe74dc 100644 --- a/ui/src/components/bedset-splash-components/beds-table.tsx +++ b/ui/src/components/bedset-splash-components/beds-table.tsx @@ -139,65 +139,67 @@ export const BedsTable = (props: Props) => { }); return ( -
+
setGlobalFilter(e.target.value)} />
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - (window.location.href = `/bed/${row.original.id}`)} - > - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - -
- {header.isPlaceholder ? null : ( -
- {flexRender(header.column.columnDef.header, header.getContext())} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
- )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + (window.location.href = `/bed/${row.original.id}`)} + > + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: , + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
-
+
Showing diff --git a/ui/src/components/bedset-splash-components/cards/median-region-width.tsx b/ui/src/components/bedset-splash-components/cards/median-region-width.tsx index ca2a9ea..b198fec 100644 --- a/ui/src/components/bedset-splash-components/cards/median-region-width.tsx +++ b/ui/src/components/bedset-splash-components/cards/median-region-width.tsx @@ -11,7 +11,7 @@ export const MeanRegionWidthCard = (props: Props) => { const { metadata } = props; return ( - +

{formatNumberWithCommas(Math.round(metadata.statistics?.mean?.mean_region_width || 0))} bp diff --git a/ui/src/components/bedset-splash-components/header.tsx b/ui/src/components/bedset-splash-components/header.tsx index 1e7a347..6ef7e53 100644 --- a/ui/src/components/bedset-splash-components/header.tsx +++ b/ui/src/components/bedset-splash-components/header.tsx @@ -22,10 +22,10 @@ export const BedsetSplashHeader = (props: Props) => { return (
-
+

- + {metadata?.id || 'No name available'}

-

{metadata?.description || 'No description available'}

+

{metadata?.description || 'No description available'}

-
-
- - {metadata.md5sum} -
-
- - {metadata.bed_ids?.length} BED files +
+
+

+

+ + {metadata.md5sum} +
+

+ { metadata.bed_ids && +

+

+ + {metadata.bed_ids?.length} BED files +
+

+ }
diff --git a/ui/src/components/bedset-splash-components/plots.tsx b/ui/src/components/bedset-splash-components/plots.tsx index d2575cc..7ed822a 100644 --- a/ui/src/components/bedset-splash-components/plots.tsx +++ b/ui/src/components/bedset-splash-components/plots.tsx @@ -58,10 +58,10 @@ export const Plots = (props: PlotsProps) => { const plotNames = metadata.plots ? Object.keys(metadata.plots) : []; return ( -
+ {metadata.plots && chunkArray(plotNames, 3).map((chunk, idx) => ( - + {chunk.map((plotName) => { // this is for type checking const plotNameKey = plotName as keyof typeof metadata.plots; @@ -83,9 +83,9 @@ export const Plots = (props: PlotsProps) => { ); })} - + ))} -
+
); }; diff --git a/ui/src/components/modals/figure-modal.tsx b/ui/src/components/modals/figure-modal.tsx index a60a700..c142113 100644 --- a/ui/src/components/modals/figure-modal.tsx +++ b/ui/src/components/modals/figure-modal.tsx @@ -3,13 +3,15 @@ import { Modal } from 'react-bootstrap'; type Props = { title: string; src: string; + pdf?: string; alt: string; show: boolean; onHide: () => void; }; export const FigureModal = (props: Props) => { - const { title, src, alt, show, onHide } = props; + const { title, src, pdf, alt, show, onHide } = props; + return ( { link.click(); }} > - Download + Download PNG + + {pdf && + + } + diff --git a/ui/src/components/search/bed2bed/b2b-search-results-table.tsx b/ui/src/components/search/bed2bed/b2b-search-results-table.tsx index 844a427..40793cc 100644 --- a/ui/src/components/search/bed2bed/b2b-search-results-table.tsx +++ b/ui/src/components/search/bed2bed/b2b-search-results-table.tsx @@ -183,17 +183,23 @@ export const Bed2BedSearchResultsTable = (props: Props) => { getFilteredRowModel: getFilteredRowModel(), }); + const handleRowClick = (id?: string) => (e: React.MouseEvent) => { + if (!(e.target as HTMLElement).closest('button')) { + window.location.href = `/bed/${id}`; + } + }; + return ( -
+
setGlobalFilter(e.target.value)} />
- +
{table.getHeaderGroups().map((headerGroup) => ( @@ -215,8 +221,8 @@ export const Bed2BedSearchResultsTable = (props: Props) => { > {flexRender(header.column.columnDef.header, header.getContext())} {{ - asc: ' 🔼', - desc: ' 🔽', + asc: , + desc: , }[header.column.getIsSorted() as string] ?? null} )} @@ -227,7 +233,11 @@ export const Bed2BedSearchResultsTable = (props: Props) => { {table.getRowModel().rows.map((row) => ( - + {row.getVisibleCells().map((cell) => ( ))} @@ -236,8 +246,8 @@ export const Bed2BedSearchResultsTable = (props: Props) => {
{flexRender(cell.column.columnDef.cell, cell.getContext())}
-
-
+
+
Showing {table.getState().pagination.pageSize * table.getState().pagination.pageIndex + 1} to{' '} diff --git a/ui/src/components/search/search-bedset-table.tsx b/ui/src/components/search/search-bedset-table.tsx index 9417728..3e2c9d0 100644 --- a/ui/src/components/search/search-bedset-table.tsx +++ b/ui/src/components/search/search-bedset-table.tsx @@ -9,6 +9,12 @@ type Props = { export const SearchBedSetResultTable = (props: Props) => { const { results } = props; + const handleRowClick = (id?: string) => (e: React.MouseEvent) => { + if (!(e.target as HTMLElement).closest('button')) { + window.location.href = `/bedset/${id}`; + } + }; + return ( @@ -24,13 +30,17 @@ export const SearchBedSetResultTable = (props: Props) => { {results.results?.map((result) => ( - + -

BED Sets

-
-
{result?.id || 'Unknown Id'} {result?.name || 'Unknown Name'} {result?.description || 'Unknown Description'} {result?.bed_ids?.length || 0} - + diff --git a/ui/src/components/search/text2bed/t2b-search-results-table.tsx b/ui/src/components/search/text2bed/t2b-search-results-table.tsx index c5f9765..971ee3b 100644 --- a/ui/src/components/search/text2bed/t2b-search-results-table.tsx +++ b/ui/src/components/search/text2bed/t2b-search-results-table.tsx @@ -7,38 +7,47 @@ import toast from 'react-hot-toast'; import YAML from 'js-yaml'; type SearchResponse = components['schemas']['BedListSearchResult']; +type BedNeighboursResponse = components['schemas']['BedNeighboursResult']; type Props = { - results: SearchResponse; + results: SearchResponse | BedNeighboursResponse; }; export const Text2BedSearchResultsTable = (props: Props) => { const { results } = props; const { cart, addBedToCart, removeBedFromCart } = useBedCart(); + + const handleRowClick = (id?: string) => (e: React.MouseEvent) => { + if (!(e.target as HTMLElement).closest('button')) { + window.location.href = `/bed/${id}`; + } + }; + return ( - - - - - - - {/**/} - {/**/} - - - - - {/* */} - - + + + + + + + + + + + + - {results.results?.map((result) => ( - + {results.results?.map((result) => ( + - {/**/} - {/**/} - - {/**/} - - {/* */} - {/**/} -

Overview

-
-
NameGenomeTissueCell LineCell TypeTarget AntibodyDescriptionAssayInfoScoreBEDbase ID - Actions -
NameGenomeTissueCell LineCell TypeDescriptionAssayInfoScore + Actions +
{result?.metadata?.name || 'No name'} {result?.metadata?.genome_alias || 'N/A'} @@ -46,12 +55,8 @@ export const Text2BedSearchResultsTable = (props: Props) => { {result?.metadata?.annotation?.tissue || 'N/A'} {result?.metadata?.annotation?.cell_line || 'N/A'} {result?.metadata?.annotation?.cell_type || 'N/A'}{result?.metadata?.annotation?.target || 'N/A'}{result?.metadata?.annotation?.antibody || 'N/A'}{result?.metadata?.description || ''} {result?.metadata?.annotation?.assay || 'N/A'} { } > - + @@ -77,30 +82,17 @@ export const Text2BedSearchResultsTable = (props: Props) => { variant="primary" /> {result?.metadata?.id || 'No id'}*/} - {/* /!*{result?.metadata?.submission_date === undefined*!/*/} - {/* /!* ? 'No date'*!/*/} - {/* /!* : new Date(result.metadata?.submission_date).toLocaleDateString()}*!/\*/} - {/* */} - {/* - - {/**/} - {cart.includes(result?.metadata?.id || '') ? ( ) : (
+

Overview

+
+
@@ -123,83 +154,110 @@ export const BedSplash = () => { {Object.keys(metadata?.annotation || {}).map((k) => { if (k === 'input_file' || k === 'file_name' || k === 'sample_name') { return null; - // @ts-expect-error wants to get mad because it could be an object and React cant render that (it wont be) - } else if (!metadata?.annotation[k]) { + } + + const value = getAnnotationValue(metadata, k); + if (!value) { return null; - } else { - return ( - - - - - - ); } + + return ( + + + + + ); })}
Key
- {snakeToTitleCase(k)} - - {/* @ts-expect-error wants to get mad because it could be an object and React cant render that (it wont be) */} - {metadata?.annotation[k] || 'N/A'} -
+ {snakeToTitleCase(k)} + + {value ?? 'N/A'} +
-
+ +

BEDsets

+
+
- + - {metadata?.bedsets?.map((bedset) => ( - - - - - - - )) || 'N/A'} + {[ + ...(metadata?.bedsets || []).map((bedset) => ( + + + + + + + )), + ...Array( + Math.max( + 0, + getFilteredKeys(metadata).length - (metadata?.bedsets?.length || 0) + ) + ).fill(null).map((_, index) => ( + + + + + + + )) + ]}
BED set IDBEDset ID Name Description View
- {bedset.id} - - {bedset.name || 'No name'} - - {bedset.description || 'No description'} - - View -
+ {bedset.id} + + {bedset.name || 'No name'} + + {bedset.description || 'No description'} + + View +
    
-

Statistics

- + + +

Statistics

{metadata && ( - + )} - + - {/* */}
-

Plots

+ - + +

Plots

+ +
+ + { neighbours && + +

Similar BED Files

+ + + +
+ }
); } -}; +}; \ No newline at end of file diff --git a/ui/src/pages/bedset-splash.tsx b/ui/src/pages/bedset-splash.tsx index d31ccf6..f40ef6a 100644 --- a/ui/src/pages/bedset-splash.tsx +++ b/ui/src/pages/bedset-splash.tsx @@ -64,17 +64,17 @@ export const BedsetSplash = () => { } else if (error) { if ((error as AxiosError)?.response?.status === 404) { return ( - +

Oh no!

-

+

We could not find BEDset with record identifier:
{bedsetId} -

+

@@ -102,47 +102,53 @@ export const BedsetSplash = () => { } } else { return ( - +

- + {metadata !== undefined ? : null} -

Statistics

- + + +

Statistics

{metadata && ( - - - - - - - - - - + + + + + )} + + +
-

Plots

+ - + +

Plots

+ +
-

BED files in this BED set

+ - {isLoadingBedfiles ? ( -
- - {Array.from({ length: 10 }).map((_, index) => ( -
- -
- ))} -
- ) : ( - bedfiles && - )} +

Constituent BED Files

+ + {isLoadingBedfiles ? ( +
+ + {Array.from({ length: 10 }).map((_, index) => ( +
+ +
+ ))} +
+ ) : ( + bedfiles && + )} +
+
); diff --git a/ui/src/queries/useBedNeighbours.ts b/ui/src/queries/useBedNeighbours.ts new file mode 100644 index 0000000..885ec32 --- /dev/null +++ b/ui/src/queries/useBedNeighbours.ts @@ -0,0 +1,24 @@ +import { useQuery } from '@tanstack/react-query'; +import { useBedbaseApi } from '../contexts/api-context'; +import { components } from '../../bedbase-types'; + +type BedNeighboursResponse = components['schemas']['BedNeighboursResult']; +type BedNeighboursQuery = { + md5?: string; + limit?: number; + offset?: number; +}; + +export const useBedNeighbours = (query: BedNeighboursQuery) => { + const { api } = useBedbaseApi(); + + const { md5, limit } = query; + + return useQuery({ + queryKey: ['neighbours', md5], + queryFn: async () => { + const { data } = await api.get(`/bed/${md5}/neighbours?limit=${limit}`); + return data; + } + }); +}; diff --git a/ui/src/queries/useBedSetBedfiles.ts b/ui/src/queries/useBedSetBedfiles.ts index ffa5edc..b700377 100644 --- a/ui/src/queries/useBedSetBedfiles.ts +++ b/ui/src/queries/useBedSetBedfiles.ts @@ -1,6 +1,6 @@ import type { components } from '../../bedbase-types'; import { useQuery } from '@tanstack/react-query'; -import { useBedbaseApi } from '../contexts/api-context.tsx'; +import { useBedbaseApi } from '../contexts/api-context'; type BedSetBedfilesResponse = components['schemas']['BedSetBedFiles']; @@ -11,11 +11,7 @@ type BedSetBedfilesQuery = { export const useBedsetBedfiles = (query: BedSetBedfilesQuery) => { const { api } = useBedbaseApi(); - const { id, autoRun } = query; - let enabled = false; - if (autoRun !== undefined && autoRun && id) { - enabled = true; - } + const { id } = query; return useQuery({ queryKey: ['bedset-bedfiles', id], @@ -23,7 +19,10 @@ export const useBedsetBedfiles = (query: BedSetBedfilesQuery) => { const { data } = await api.get(`/bedset/${id}/bedfiles`); return data; }, - enabled: enabled, - staleTime: 0, + enabled: Boolean(id), + staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes + gcTime: 30 * 60 * 1000, // This replaces cacheTime in newer versions + refetchOnWindowFocus: false, + refetchOnMount: false, }); -}; +}; \ No newline at end of file diff --git a/ui/src/queries/useBedsetMetadata.ts b/ui/src/queries/useBedsetMetadata.ts index e847bf9..303448c 100644 --- a/ui/src/queries/useBedsetMetadata.ts +++ b/ui/src/queries/useBedsetMetadata.ts @@ -1,6 +1,6 @@ import type { components } from '../../bedbase-types'; import { useQuery } from '@tanstack/react-query'; -import { useBedbaseApi } from '../contexts/api-context.tsx'; +import { useBedbaseApi } from '../contexts/api-context'; type BedSetMetadataResponse = components['schemas']['BedSetMetadata']; @@ -11,11 +11,7 @@ type BedSetMetadataQuery = { export const useBedsetMetadata = (query: BedSetMetadataQuery) => { const { api } = useBedbaseApi(); - const { md5, autoRun } = query; - let enabled = false; - if (autoRun !== undefined && autoRun && md5) { - enabled = true; - } + const { md5 } = query; return useQuery({ queryKey: ['bedset-metadata', md5], @@ -23,7 +19,10 @@ export const useBedsetMetadata = (query: BedSetMetadataQuery) => { const { data } = await api.get(`/bedset/${md5}/metadata`); return data; }, - enabled: enabled, - staleTime: 0, + enabled: Boolean(md5), + staleTime: 5 * 60 * 1000, + gcTime: 30 * 60 * 1000, // This replaces cacheTime in newer versions + refetchOnWindowFocus: false, + refetchOnMount: false, }); -}; +}; \ No newline at end of file diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 1af3a65..ce365ad 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -23,6 +23,11 @@ export const makeThumbnailImageLink = (md5: string, plotName: string, type: Obje return `${API_BASE}/objects/${type}.${md5}.${plotName}/access/http/thumbnail`; }; +export const makePDFImageLink = (md5: string, plotName: string, type: ObjectType) => { + const API_BASE = import.meta.env.VITE_API_BASE || ''; + return `${API_BASE}/objects/${type}.${md5}.${plotName}/access/http/bytes`; +}; + export const formatDateTime = (date: string) => { return new Date(date).toLocaleString(); };