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 {