diff --git a/packages_rs/nextclade-cli/src/dataset/dataset_download.rs b/packages_rs/nextclade-cli/src/dataset/dataset_download.rs index cbf88065b..24e2e8d07 100644 --- a/packages_rs/nextclade-cli/src/dataset/dataset_download.rs +++ b/packages_rs/nextclade-cli/src/dataset/dataset_download.rs @@ -234,6 +234,7 @@ pub fn dataset_individual_files_load( rest_attrs: BTreeMap::default(), other: serde_json::Value::default(), }, + image: None, files: DatasetFiles { reference: "".to_owned(), pathogen_json: "".to_owned(), diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx index 521dbdd79..364088930 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx @@ -1,5 +1,5 @@ import { isNil } from 'lodash' -import { darken } from 'polished' +import { darken, transparentize } from 'polished' import React, { useMemo } from 'react' import { Badge } from 'reactstrap' import { useRecoilValue } from 'recoil' @@ -194,7 +194,8 @@ export interface DatasetInfoCircleProps { } function DatasetInfoAutodetectProgressCircle({ dataset, showSuggestions }: DatasetInfoCircleProps) { - const { attributes, path } = dataset + const { t } = useTranslationSafe() + const { attributes, path, image } = dataset const { name } = attributes const circleBg = useMemo(() => darken(0.1)(colorHash(path, { saturation: 0.5, reverse: true })), [path]) @@ -219,12 +220,17 @@ function DatasetInfoAutodetectProgressCircle({ dataset, showSuggestions }: Datas return { circleText: `0%`, percentage: 0, countText: `0 / ${numberAutodetectResults}` } }, [showSuggestions, records, numberAutodetectResults, name.valueFriendly, name.value]) + const circle = useMemo(() => { + if (image?.path) { + const title = image?.source ? t('Image credits: {{source}}', { source: image.source }) : undefined + return + } + return {circleText} + }, [circleBg, circleText, image?.path, image?.source, t]) + return ( <> - - {circleText} - - + {circle} {countText} ) @@ -257,6 +263,7 @@ const CircleBorder = styled.div.attrs((props) => ({ border-radius: 50%; width: 75px; height: 75px; + z-index: 10 !important; ` const Circle = styled.div<{ $bg?: string; $fg?: string }>` @@ -271,3 +278,18 @@ const Circle = styled.div<{ $bg?: string; $fg?: string }>` height: 60px; font-size: 1.2rem; ` + +const CircleImage = styled.div<{ $bg?: string; $fg?: string; $image?: string }>` + background-image: ${(props) => `url(${props.$image})`}; + background-color: ${(props) => props.$bg && transparentize(0.75)(props.$bg)}; + background-blend-mode: overlay; + background-size: contain; + display: flex; + margin: auto; + justify-content: center; + align-items: center; + border-radius: 50%; + width: 60px; + height: 60px; + font-size: 1.2rem; +` diff --git a/packages_rs/nextclade-web/src/io/fetchDatasetsIndex.ts b/packages_rs/nextclade-web/src/io/fetchDatasetsIndex.ts index c1690972f..a5504d6ce 100644 --- a/packages_rs/nextclade-web/src/io/fetchDatasetsIndex.ts +++ b/packages_rs/nextclade-web/src/io/fetchDatasetsIndex.ts @@ -2,6 +2,7 @@ import { head, mapValues, sortBy, sortedUniq } from 'lodash' import semver from 'semver' import { takeFirstMaybe } from 'src/helpers/takeFirstMaybe' import urljoin from 'url-join' +import copy from 'fast-copy' import { Dataset, DatasetFiles, DatasetsIndexJson, DatasetsIndexV2Json, MinimizerIndexVersion } from 'src/types' import { axiosFetch } from 'src/io/axiosFetch' @@ -27,10 +28,17 @@ export function fileUrlsToAbsolute(datasetServerUrl: string, dataset: Dataset): const restFilesAbs = mapValues(dataset.files, (file) => file ? urljoin(datasetServerUrl, dataset.path, dataset.version?.tag ?? '', file) : undefined, ) as DatasetFiles + const files = { ...restFilesAbs, } - return { ...dataset, files } + + const image = copy(dataset.image) + if (image?.path) { + image.path = urljoin(datasetServerUrl, dataset.path, dataset.version?.tag ?? '', image.path) + } + + return { ...dataset, files, image } } export function getLatestCompatibleEnabledDatasets(datasetServerUrl: string, datasetsIndexJson: DatasetsIndexV2Json) { diff --git a/packages_rs/nextclade/src/analyze/virus_properties.rs b/packages_rs/nextclade/src/analyze/virus_properties.rs index 1cec3211a..549d6fee7 100644 --- a/packages_rs/nextclade/src/analyze/virus_properties.rs +++ b/packages_rs/nextclade/src/analyze/virus_properties.rs @@ -5,7 +5,7 @@ use crate::analyze::pcr_primer_changes::PcrPrimer; use crate::coord::position::AaRefPosition; use crate::coord::range::AaRefRange; use crate::gene::genotype::Genotype; -use crate::io::dataset::{DatasetAttributes, DatasetCompatibility, DatasetFiles, DatasetVersion}; +use crate::io::dataset::{DatasetAttributes, DatasetCompatibility, DatasetFiles, DatasetImage, DatasetVersion}; use crate::io::fs::read_file_to_string; use crate::io::json::json_parse; use crate::io::schema_version::{SchemaVersion, SchemaVersionParams}; @@ -32,6 +32,8 @@ pub struct VirusProperties { pub attributes: DatasetAttributes, + pub image: Option, + pub files: DatasetFiles, #[serde(default = "bool_false")] diff --git a/packages_rs/nextclade/src/io/dataset.rs b/packages_rs/nextclade/src/io/dataset.rs index 8d9a90c65..0d8534dc7 100644 --- a/packages_rs/nextclade/src/io/dataset.rs +++ b/packages_rs/nextclade/src/io/dataset.rs @@ -70,6 +70,8 @@ pub struct Dataset { pub attributes: DatasetAttributes, + pub image: Option, + pub files: DatasetFiles, #[serde(default, skip_serializing_if = "DatasetCapabilities::is_default")] @@ -302,6 +304,17 @@ impl DatasetAttributeValue { } } +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct DatasetImage { + pub path: Option, + + pub source: Option, + + #[serde(flatten)] + pub other: serde_json::Value, +} + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct DatasetAttributes {