diff --git a/frontend/packages/data-portal/app/components/Index/IndexCTA.tsx b/frontend/packages/data-portal/app/components/Index/IndexCTA.tsx new file mode 100644 index 000000000..2ac328999 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Index/IndexCTA.tsx @@ -0,0 +1,62 @@ +import { Button } from '@czi-sds/components' + +import { I18n } from 'app/components/I18n' +import { Link } from 'app/components/Link' +import { useI18n } from 'app/hooks/useI18n' + +function CTA({ + title, + text, + buttonText, + url, +}: { + title: string + text: string + buttonText: string + url: string +}) { + return ( + <> +
+

+ {title} +

+

{text}

+
+
+ + + +
+ + ) +} + +export function IndexCTA() { + const { t } = useI18n() + + return ( +
+

+ +

+
+ +
+ +
+
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Index/IndexContent.tsx b/frontend/packages/data-portal/app/components/Index/IndexContent.tsx new file mode 100644 index 000000000..8ba875c1f --- /dev/null +++ b/frontend/packages/data-portal/app/components/Index/IndexContent.tsx @@ -0,0 +1,43 @@ +import { I18n } from 'app/components/I18n' + +import { IndexContributors } from './IndexContributors' +import { IndexCTA } from './IndexCTA' + +export function IndexContent() { + return ( +
+
+
+

+ +

+
+

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+
+
+ + +
+
+

+ +

+
+
+
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Index/IndexContributors.tsx b/frontend/packages/data-portal/app/components/Index/IndexContributors.tsx new file mode 100644 index 000000000..ae4c70bbd --- /dev/null +++ b/frontend/packages/data-portal/app/components/Index/IndexContributors.tsx @@ -0,0 +1,49 @@ +import { I18n } from 'app/components/I18n' + +const CONTRIBUTORS = ` +David Agard +Ben Barad +Florian Bec +John Briggs +Zhen Chen +Jane Ding +Ben Engel +Ryan Feathers +Sara Goetz +Danielle Grotjhan +Gus Hart +Grant Jensen +Mohammed Kaplan +Zunlong Ke +Sagar Khavnekar +Abhay Kotecha +Jun Liu +Julia Mahamid +Michaela Medina +Jürgen Plitzko +Ricado Righetto +Kem Sochacki +Matt Swulius +Liang Xue +Huaxin Yu +Ellen Zhong +` + +export function IndexContributors() { + const contributors = CONTRIBUTORS.split('\n').filter(Boolean) + + return ( +
+

+ +

+
    + {contributors.map((name) => ( +
  • + {name} +
  • + ))} +
+
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Index/IndexHeader.tsx b/frontend/packages/data-portal/app/components/Index/IndexHeader.tsx new file mode 100644 index 000000000..3e614de3a --- /dev/null +++ b/frontend/packages/data-portal/app/components/Index/IndexHeader.tsx @@ -0,0 +1,95 @@ +import { Button } from '@czi-sds/components' +import { styled } from '@mui/material/styles' +import { useTypedLoaderData } from 'remix-typedjson' + +import { LandingPageDataQuery } from 'app/__generated__/graphql' +import { I18n } from 'app/components/I18n' +import { Link } from 'app/components/Link' +import { useI18n } from 'app/hooks/useI18n' +import { theme } from 'app/theme' +import { cns, cnsNoMerge } from 'app/utils/cns' + +function MetricField({ title, count }: { title: string; count: number }) { + return ( +
+

+ {title} +

+

+ {count.toLocaleString()} +

+
+ ) +} + +const CTAButton = styled(Button)({ + 'background-color': theme.palette.grey[200], + color: theme.palette.common.black, + filter: 'drop-shadow(0 0 7px rgba(0, 0, 0, 0.5))', + '&:hover': { + color: theme.palette.common.black, + 'background-color': theme.palette.common.white, + }, +}) + +const DIVIDER = ( +
+) + +export function IndexHeader() { + const { t } = useI18n() + const data = useTypedLoaderData() + + const datasets = data.datasets_aggregate.aggregate?.count + const species = data.species_aggregate.aggregate?.count + const tomograms = data.tomograms_aggregate.aggregate?.count + + return ( +
+
+
+

+ +

+
+ + {DIVIDER} + + {DIVIDER} + +
+ + + + + +
+ +

+ +

+ +
+
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Index/index.ts b/frontend/packages/data-portal/app/components/Index/index.ts new file mode 100644 index 000000000..39388cb2e --- /dev/null +++ b/frontend/packages/data-portal/app/components/Index/index.ts @@ -0,0 +1,2 @@ +export * from './IndexContent' +export * from './IndexHeader' diff --git a/frontend/packages/data-portal/app/components/MDX/MdxContent.tsx b/frontend/packages/data-portal/app/components/MDX/MdxContent.tsx index 25ae95630..a91a183fd 100644 --- a/frontend/packages/data-portal/app/components/MDX/MdxContent.tsx +++ b/frontend/packages/data-portal/app/components/MDX/MdxContent.tsx @@ -10,7 +10,7 @@ export function MdxContent() { return (
-
+
{match(values.length) .with(0, () => null) - .with(1, () => datum.renderValue?.(values[0]) ?? values[0]) + .with(1, () => ( + + {datum.renderValue?.(values[0]) ?? values[0]} + + )) .otherwise(() => (
    {values.map((value, valueIdx) => ( diff --git a/frontend/packages/data-portal/app/i18n.ts b/frontend/packages/data-portal/app/i18n.ts index 4a9cd4e04..1983359b1 100644 --- a/frontend/packages/data-portal/app/i18n.ts +++ b/frontend/packages/data-portal/app/i18n.ts @@ -46,6 +46,9 @@ export const i18n = { citations: 'Citations', clearFilters: 'Clear Filters', confidence: 'confidence', + contributeCta: + 'We encourage you to share datasets and/or annotations to existing data. Click below to fill out the inquiry form.', + contributeYourData: 'Contribute your Data', cookiePolicy: 'Cookie Policy', ctfCorrected: 'Ctf Corrected', curatorRecommended: 'Curator Recommended', @@ -94,6 +97,7 @@ export const i18n = { imageCorrector: 'Image Corrector', includedContents: 'Included Contents', keyPhoto: 'key photo', + landingHeaderTitle: 'Open access to annotated cryoET tomograms', lastModified: (date: string) => `Last Modified: ${date}`, lastModifiedBlank: 'Last Modified', license: 'License', @@ -118,6 +122,7 @@ export const i18n = { objectName: 'Object Name', objectShapeType: 'Object Shape Type', objectState: 'Object State', + orExploreViaApi: 'or explore via API', organism: 'Organism', organismName: 'Organism Name', otherSetup: 'Other Setup', @@ -155,8 +160,10 @@ export const i18n = { showLess: 'Show Less', size: 'Size', smallestAvailableVoxelSpacing: 'Smallest Available Voxel Spacing', + species: 'Species', sphericalAberrationConstant: 'Spherical Aberration Constant', submitFeedback: 'Submit Feedback', + tellUsMore: 'Tell us More', terms: 'Terms', termsOfUse: 'Terms of Use', tiltAxis: 'Tilt Axis', @@ -187,5 +194,8 @@ export const i18n = { unitVolts: (x: number) => `${x} V`, valueToValue: (x: string, y: string) => `${x} to ${y}`, veryPoor: 'Very Poor', + viewAndDownloadDatasets: 'View and Download Datasets', + viewDatasetsCta: + 'Find and visualize cryoET datasets in the portal and download to use for your own work.', yes: 'Yes', } diff --git a/frontend/packages/data-portal/app/routes/_index.tsx b/frontend/packages/data-portal/app/routes/_index.tsx index c317599e3..229670274 100644 --- a/frontend/packages/data-portal/app/routes/_index.tsx +++ b/frontend/packages/data-portal/app/routes/_index.tsx @@ -1,40 +1,52 @@ -import { ComplexFilter, DefaultDropdownMenuOption } from '@czi-sds/components' -import Typography from '@mui/material/Typography' import type { MetaFunction } from '@remix-run/node' +import { json } from '@remix-run/server-runtime' + +import { gql } from 'app/__generated__' +import { apolloClient } from 'app/apollo.server' +import { IndexContent, IndexHeader } from 'app/components/Index' + +const LANDING_PAGE_DATA_QUERY = gql(` + query LandingPageData { + datasets_aggregate { + aggregate { + count(distinct: true) + } + } + species_aggregate: datasets_aggregate { + aggregate { + count(distinct: true, columns: organism_taxid) + } + } + tomograms_aggregate { + aggregate { + count(distinct: true) + } + } + } +`) + +export async function loader() { + const { data } = await apolloClient.query({ + query: LANDING_PAGE_DATA_QUERY, + }) + + return json(data) +} export const meta: MetaFunction = () => { return [ { - title: 'Remix Starter', - description: 'Welcome to remix!', + title: 'CryoET Data Portal', + description: 'Welcome to the CryoET Data Portal!', }, ] } -const OPTIONS: DefaultDropdownMenuOption[] = [ - { name: 'Filter Item 1', section: 'Section 1' }, - { name: 'Filter Item 2', section: 'Section 1' }, - { name: 'Filter Item 3', section: 'Section 2' }, -] - export default function HomePage() { return ( -
    - - Hello, CryoET Data Portal! - - - {}} - options={OPTIONS} - search - DropdownMenuProps={{ - groupBy: (option: DefaultDropdownMenuOption) => - option.section as string, - }} - /> +
    + +
    ) } diff --git a/frontend/packages/data-portal/public/images/index-header.png b/frontend/packages/data-portal/public/images/index-header.png new file mode 100644 index 000000000..f0f6e8bf4 Binary files /dev/null and b/frontend/packages/data-portal/public/images/index-header.png differ diff --git a/frontend/packages/data-portal/public/locales/en/translation.json b/frontend/packages/data-portal/public/locales/en/translation.json index 39a30e0b9..9cbfe8630 100644 --- a/frontend/packages/data-portal/public/locales/en/translation.json +++ b/frontend/packages/data-portal/public/locales/en/translation.json @@ -15,8 +15,8 @@ "annotationMethod": "Annotation Method", "annotationObject": "Annotation Object", "annotationOverview": "Annotation Overview", - "annotationSoftware": "Annotation Software", "annotations": "Annotations", + "annotationSoftware": "Annotation Software", "api": "API", "apiDocLink": "https://chanzuckerberg.github.io/cryoet-data-portal", "apply": "Apply", @@ -45,6 +45,8 @@ "close": "Close", "confidence": "confidence", "configureDownload": "Configure Download", + "contributeCta": "We encourage you to share datasets and/or annotations to existing data. Click below to fill out the inquiry form.", + "contributeYourData": "Contribute your Data", "cookiePolicy": "Cookie Policy", "copy": " Copy", "copyAndRunAwsS3Command": "Copy and run AWS S3 command in your terminal", @@ -59,9 +61,9 @@ "datasetId": "Dataset ID", "datasetIds": "Dataset IDs", "datasetMetadata": "Dataset Metadata", - "datasetTitle": "Dataset Title", "datasets": "Datasets", "datasetsTab": "Datasets {{count}}", + "datasetTitle": "Dataset Title", "depositionDate": "Deposition Date", "description": "Description", "directDownload": "Direct Download", @@ -95,8 +97,8 @@ "fundingAgency": "Funding Agency", "github": "GitHub", "goId": "GO ID", - "goToDocs": "Go to Documentation", "good": "Good", + "goToDocs": "Go to Documentation", "grantID": "Grant ID", "gridPreparation": "Grid Preparation", "groundTruth": "Ground Truth", @@ -105,11 +107,20 @@ "groundTruthUsed": "Ground Truth Used", "hardware": "Hardware", "helpAndReport": "Help & Report", + "helpUsAchieveThisVision": "Help us achieve this vision", "howToCite": "How to cite", "ifYouEncounterIssuesWithDownloadTime": "If you encounter issues with download time, we recommend downloading larger files via API.", "imageCorrector": "Image Corrector", "includedContents": "Included Contents", "keyPhoto": "key photo", + "landingHeaderImageAttribution": "Top Image: The inner workings of an algal cell as depicted with cryo-electron tomography, which aggregates multiple snapshots of a single piece of material. Visible are the Golgi apparatus (green and magenta), and vesicles (multi-colored circles). | Photo credit: Y. S. Bykkov et al./eLIFE (CC BY 4.0)", + "landingHeaderTitle": "Open access to annotated cryoET tomograms", + "landingPageCopy1": "Currently, annotating tomograms from cryo-electron tomography (cryoET) experiments is a tedious, time-consuming, and often manual process. Our goal is to accelerate this process by catalyzing the development of sophisticated machine-learning methods for automatic annotation, helping researchers find scientific insights faster.", + "landingPageCopy2": "The portal provides biologists and developers open access to high-quality, standardized, annotated data they can readily use to retrain or develop new annotation models and algorithms. Currently, the portal contains 13,861 tomograms from 53 datasets contributed by the groups of Julia Mahamid, Jürgen Plitzko, David Agard, John Briggs, Abhay Kotecha, Ben Engel, Danielle Grotjhan, and Grant Jensen.", + "landingPageCopy3": "All tomograms include rich standardized metadata such as data tree structure and naming conventions. Most groups have already provided annotations for their data, and there is a structure to add new annotations to existing tomograms.", + "landingPageCopy4": "We are actively growing the number of annotated datasets on the portal and encourage researchers to share their data. We are actively growing the number of annotated datasets on the CZ Imaging Institute portal and encourage researchers to share their data. We are working with EMPIAR to host the data and support annotation.", + "landingPageCopy5": "Ultimately, our vision is to contribute to developing a large, open-access database of annotated and validated 3D structural information for cells that researchers can use to gain new insights into cellular and structural biology.", + "landingPageWelcomeBlurb": "Welcome to the CryoET Data Portal, a project built by the Chan Zuckerberg Imaging Institute and the Chan Zuckerberg Initiative. ", "lastModified": "Last Modified: {{date}}", "lastModifiedBlank": "Last Modified", "lastUpdated": "Last Updated", @@ -122,11 +133,11 @@ "moderate": "Moderate", "moreInfo": "More Info", "mrcFormat": ".mrc format", - "nMoreObjects": "{{count}} More Objects", "na": "NA", "nameOrId": "Name/ID", "napariPlugin": "napari Plugin", "next": "Next", + "nMoreObjects": "{{count}} More Objects", "no": "No", "notApplicable": "Not Applicable", "notSubmitted": "Not Submitted", @@ -137,6 +148,7 @@ "objectShapeType": "Object Shape Type", "objectState": "Object State", "optional": "Optional", + "orExploreViaApi": "or explore via API", "organism": "Organism", "organismName": "Organism Name", "otherSetup": "Other Setup", @@ -178,12 +190,16 @@ "showLess": "Show Less", "size": "Size", "smallestAvailableVoxelSpacing": "Smallest Available Voxel Spacing", + "species": "Species", "sphericalAberrationConstant": "Spherical Aberration Constant", "stepCount": "Step {{count} of {max}}", "submitFeedback": "Submit Feedback", + "tellUsMore": "Tell us More", "terms": "Terms", "termsOfUse": "Terms of Use", + "thankYouToOurDataContributors": "Thank You to our Data Contributors…", "tiltAxis": "Tilt Axis", + "tiltingScheme": "Tilting Scheme", "tiltQuality": "Tilt Quality", "tiltRange": "Tilt Range", "tiltRangeFilterDescription": "Angle is in degrees (°). Tilt range is the difference between the max tilt angle and the min tilt angle.", @@ -194,15 +210,14 @@ "tiltSeriesMetadata": "Tilt-Series Metadata", "tiltSeriesQualityScore": "Tilt-Series Quality Score", "tiltStep": "Tilt Step", - "tiltingScheme": "Tilting Scheme", "tissueName": "Tissue Name", "title": "CryoET Data Portal", "tomogram": "Tomogram", "tomogramId": "Tomogram ID", "tomogramMetadata": "Tomogram Metadata", "tomogramProcessing": "Tomogram Processing", - "tomogramSampling": "Tomogram Sampling", "tomograms": "Tomograms", + "tomogramSampling": "Tomogram Sampling", "tools": "Tools", "totalFlux": "Total Flux", "true": "True", @@ -210,11 +225,17 @@ "unitDegree": "{{value}}°", "unitMillimeter": "{{value}} mm", "unitVolts": "{{value}} V", + "urlCZ": "https://chanzuckerberg.com/", + "urlCZII": "https://www.czimaginginstitute.org/", + "urlDataContributionForm": "https://airtable.com/apppmytRJXoXYTO9w/shr5UxgeQcUTSGyiY?prefill_Event=Contribution+from+portal&hide_Event=true", + "urlEMPIAR": "https://www.ebi.ac.uk/empiar/", "valueToValue": "{{value1} to {value2}}", "veryPoor": "Very Poor", "viaApi": "via API", "viaAwsS3": "via AWS S3", "viaCurl": "via cURL", + "viewAndDownloadDatasets": "View and Download Datasets", + "viewDatasetsCta": "Find and visualize cryoET datasets in the portal and download to use for your own work.", "viewTomogram": "View Tomogram", "voxelSpacingId": "Voxel Spacing ID", "yes": "Yes", diff --git a/frontend/packages/data-portal/tailwind.config.ts b/frontend/packages/data-portal/tailwind.config.ts index df64ac9ec..b5d276be1 100644 --- a/frontend/packages/data-portal/tailwind.config.ts +++ b/frontend/packages/data-portal/tailwind.config.ts @@ -1,5 +1,19 @@ import sds from '@czi-sds/components/dist/tailwind.json' import type { Config } from 'tailwindcss' +import defaultTheme from 'tailwindcss/defaultTheme' +import plugin from 'tailwindcss/plugin' + +const backgroundImageSrcPlugin = plugin(({ addUtilities, theme, e }) => { + const values = theme('backgroundImageSrc') + if (values) { + const utilities = Object.entries(values).map(([key, value]) => { + return { + [`.${e(`bg-img-${key}`)}`]: { '--tw-background-image': `${value}` }, + } + }) + addUtilities(utilities) + } +}) // eslint-disable-next-line import/no-default-export export default { @@ -14,7 +28,7 @@ export default { maxWidth: { content: '1600px', - 'mdx-content': '800px', + 'content-small': '800px', }, // overwrite faulty SDS line heights @@ -40,7 +54,28 @@ export default { 'sds-caps-xxxs': '16px', 'sds-caps-xxxxs': '14px', }, + + dropShadow: { + 'landing-header': '0 0 7px rgba(0, 0, 0, 0.5)', + }, + + backgroundImage: () => { + const values = Object.entries(defaultTheme.backgroundImage) + .filter(([key]) => key.includes('to')) + .map(([key, value]) => { + return { + [`gradient-img-${key.slice( + key.indexOf('-') + 1, + )}`]: `${value}, var(--tw-background-image)`, + } + }) + return values.reduce((prev, curr) => ({ ...prev, ...curr })) + }, + + backgroundImageSrc: { + 'landing-header': "url('/images/index-header.png')", + }, }, }, - plugins: [], + plugins: [backgroundImageSrcPlugin], } satisfies Config