diff --git a/scripts/create_customdc_stable_update_pr.sh b/scripts/create_customdc_stable_update_pr.sh new file mode 100755 index 0000000000..730080cb7d --- /dev/null +++ b/scripts/create_customdc_stable_update_pr.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Opens a PR to merge new changes into the custom DC stable branch. + +set -e + +MASTER_BRANCH=master +STABLE_BRANCH=customdc_stable +UPDATE_BRANCH=cdcStableUpdate + +# Check if GitHub CLI is installed +if ! command -v gh &> /dev/null; then + echo "GitHub CLI is not installed. Please install it to continue." + echo "You can find installation instructions at https://cli.github.com" + exit 1 +fi + +# Check if the user is authorized with GitHub CLI +if ! gh auth status &> /dev/null; then + echo "You are not logged in to GitHub CLI." + echo "Please run 'gh auth login' to authenticate." + exit 1 +fi + +# Check if GitHub CLI default repository is set +if gh repo set-default --view 2>&1 >/dev/null | grep "no default repository"; then + # No default repository is set for GitHub CLI. + # We don't have to print an error message because the above already prints one. + echo "Choose your *forked repo* as the default." + exit 1 +fi + +# Find the remote associated with the main repo +upstream_name=$(git remote -v | grep "datacommonsorg" | awk '{print $1}' | head -n 1) +if [ -z "$upstream_name" ]; then + echo "No remote found with 'datacommonsorg' in its URL." + exit 1 +fi +echo "Remote for main repo is '${upstream_name}'". + +# Find the remote associated with the forked repo +fork_name=$(git remote -v | grep -v "datacommonsorg" | awk '{print $1}' | head -n 1) +if [ -z "$fork_name" ]; then + echo "No remote found without 'datacommonsorg' in its URL." + exit 1 +fi +echo "Remote for forked repo is '${fork_name}'". + +# Check whether a local update branch already exists. +if git show-ref --verify --quiet "refs/heads/${UPDATE_BRANCH}"; then + echo "A local branch named '${UPDATE_BRANCH}' already exists." + echo "Delete it, then re-run this script." + exit 1 +fi + +# Check whether a remote update branch already exists. +if git ls-remote --heads "$fork_name" "$UPDATE_BRANCH" | grep -q "$UPDATE_BRANCH"; then + echo "A branch named '${UPDATE_BRANCH}' already exists on remote '${fork_name}'". + echo "Delete it, then re-run this script." + exit 1 +fi + +# Fetch relevant branches from the main repo +git fetch $upstream_name $MASTER_BRANCH $STABLE_BRANCH + +# Show the user the commits that are in master but not in customdc_stable +echo "" +echo "The following commits are in ${MASTER_BRANCH} but not in ${STABLE_BRANCH}:" +echo "" +git log --pretty=format:"%h %s" ${upstream_name}/${STABLE_BRANCH}..${upstream_name}/${MASTER_BRANCH} --reverse +echo "" + +# Ask the user to select a commit +read -p "Enter the commit hash to create the branch from: " commit_hash + +# Create a new branch at the selected commit +git checkout -b "$UPDATE_BRANCH" "$commit_hash" + +# Get the current date in YYYY-MM-DD format +current_date=$(date +%Y-%m-%d) + +echo "Creating PR..." + +# Open a draft PR using GitHub CLI +gh pr create \ + --repo datacommonsorg/website \ + --base customdc_stable \ + --head "${fork_name}:${UPDATE_BRANCH}" \ + --title "$current_date Custom DC stable release" \ + --body "TODO: Summarize changes since last stable release." \ + --draft diff --git a/scripts/update_customdc_stable_tag.sh b/scripts/update_customdc_stable_tag.sh deleted file mode 100755 index ee338e0ee7..0000000000 --- a/scripts/update_customdc_stable_tag.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reassigns the customdc_stable tag to the HEAD commit on the specified remote. - -set -e - -TAG_NAME=customdc_stable - -read -p "Enter name of remote where the tag should be created (e.g. upstream, dc): " REMOTE_NAME - -git push $REMOTE_NAME :refs/tags/$TAG_NAME -git tag -fa $TAG_NAME -git push $REMOTE_NAME refs/tags/$TAG_NAME - -echo "Tag URL: https://github.com/datacommonsorg/website/releases/tag/customdc_stable" - diff --git a/server/templates/explore_landing.html b/server/templates/explore_landing.html index 564a835db8..f2bc2889c3 100644 --- a/server/templates/explore_landing.html +++ b/server/templates/explore_landing.html @@ -20,6 +20,7 @@ {% set main_id = 'explore-landing' %} {% set page_id = 'explore-landing-page' %} {% set title = 'Explore ' + topic | title %} +{% set is_show_header_search_bar = true %} {% block head %} diff --git a/static/css/content/brick_wall.scss b/static/css/content/brick_wall.scss new file mode 100644 index 0000000000..69b621b788 --- /dev/null +++ b/static/css/content/brick_wall.scss @@ -0,0 +1,119 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + Styles for a brick wall component that displays a given list of + arbitrary elements in an interlocking wall of bricks. +*/ + +.brick-wall-block { + display: grid; + grid-template-columns: 1fr 1fr; + gap: calc(#{var.$spacing} * 3); + @include media-breakpoint-down(md) { + grid-template-columns: 1fr; + } + h3 { + @include var.title_lg; + grid-column: 1 / span 2; + max-width: 750px; + @include media-breakpoint-down(md) { + grid-column: 1; + } + } +} + +.brick-wall-section { + margin: 0; + padding: 0; + display: grid; + grid-template-columns: 1fr 1fr; + &.row-count-0 { + grid-template-columns: 1fr; + grid-template-rows: max-content; + } + &.row-count-1 { + grid-template-rows: 1fr; + } + &.row-count-2 { + grid-template-rows: repeat( 2, min-content ); + } + &.row-count-3 { + grid-template-rows: repeat( 3, min-content ); + } + &.row-count-4 { + grid-template-rows: repeat( 4, min-content ); + } + &.row-count-5 { + grid-template-rows: repeat( 5, min-content ); + } + &.row-count-6 { + grid-template-rows: repeat( 6, min-content ); + } + gap: calc(#{var.$spacing} * 3); + align-items: start; + justify-content: start; + @include media-breakpoint-down(sm) { + grid-template-columns: 1fr; + } + .brick-wall-item { + display: block; + list-style: none; + a { + @include var.white-box; + @include var.shadow; + @include var.text_xl; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; + padding: calc(#{var.$spacing} * 3); + border-radius: calc(#{var.$spacing} * 4); + } + } + &:nth-of-type(1) { + .brick-wall-item { + &:nth-of-type(3), &:nth-of-type(4) { + grid-column: 1 / span 2; + @include media-breakpoint-down(sm) { + grid-column: 1; + } + } + &:nth-of-type(7), &:nth-of-type(8) { + grid-column: 1 / span 2; + @include media-breakpoint-down(sm) { + grid-column: 1; + } + } + } + } + &:nth-of-type(2) { + .brick-wall-item { + &:nth-of-type(1), &:nth-of-type(4) { + grid-column: 1 / span 2; + @include media-breakpoint-down(sm) { + grid-column: 1; + } + } + &:nth-of-type(5), &:nth-of-type(8) { + grid-column: 1 / span 2; + @include media-breakpoint-down(sm) { + grid-column: 1; + } + } + } + } +} diff --git a/static/css/content/chips.scss b/static/css/content/chips.scss index 15c1b5ecac..d6ec17827d 100644 --- a/static/css/content/chips.scss +++ b/static/css/content/chips.scss @@ -39,8 +39,28 @@ .chip-item { display: block; list-style: none; + &.elevated { + a { + @include var.white-box; + @include var.shadow; + color: var.$color-blue; + + .icon svg { + fill: var.$color-blue; + } + } + } + &.flat { + a { + @include var.blue-box; + color: var.$color-black; + + .icon svg { + fill: var.$color-black; + } + } + } a { - @include var.white-box; @include var.text_sm; display: flex; justify-content: center; @@ -48,10 +68,6 @@ gap: 10px; border-radius: calc(#{var.$spacing} * 10); padding: 10px calc(#{var.$spacing} * 3) 10px calc(#{var.$spacing} * 2); - - .icon svg { - fill: #007bff; - } } } -} \ No newline at end of file +} diff --git a/static/css/content/intro_text.scss b/static/css/content/intro_text.scss new file mode 100644 index 0000000000..f4319337d5 --- /dev/null +++ b/static/css/content/intro_text.scss @@ -0,0 +1,33 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.a + */ + + .intro-text { + .container { + display: grid; + grid-template-columns: 6fr 4fr; + gap: calc(#{var.$spacing} * 2) calc(#{var.$spacing} * 6); + @include media-breakpoint-down(md) { + grid-template-columns: 1fr; + } + @include media-breakpoint-down(sm) { + display: block; + } + } + h1 { + @include var.title_lg; + margin-bottom: calc(#{var.$spacing} * 3); + } +} \ No newline at end of file diff --git a/static/css/content/sample_questions.scss b/static/css/content/sample_questions.scss index f09d4136c6..23b5f34e94 100644 --- a/static/css/content/sample_questions.scss +++ b/static/css/content/sample_questions.scss @@ -42,6 +42,7 @@ list-style: none; a { @include var.white-box; + @include var.shadow; display: flex; flex-direction: column; align-items: flex-start; diff --git a/static/css/content/tools.scss b/static/css/content/tools.scss index 675c9320d9..1ce07afbf4 100644 --- a/static/css/content/tools.scss +++ b/static/css/content/tools.scss @@ -51,6 +51,7 @@ } a { @include var.white-box; + @include var.shadow; @include var.title_xs; display: flex; width: 100%; diff --git a/static/css/explore_landing.scss b/static/css/explore_landing.scss index b5b6b1a4cb..53cd39d1f0 100644 --- a/static/css/explore_landing.scss +++ b/static/css/explore_landing.scss @@ -14,110 +14,39 @@ * limitations under the License. */ -@use "./shared/subject_page"; -@use "./nl_search_bar"; -@import "./tools/base_tools"; -@import "./draw"; -@import "./draw_choropleth"; -@import "./draw_scatter"; -@import "./shared/item_list"; -@import "./shared/story_block"; -@import "./shared/story_chart"; + @use "./variables" as var; + @import "base"; + @import "./content/intro_text"; + @import "./content/chips"; + @import "./content/brick_wall"; -#main-header { - height: 72px; -} - -.main-content { - flex-grow: 1; -} -.explore-container { - display: flex; - flex-direction: column; - font-size: 1rem; - - h1 { - font-size: 2.25rem; - margin: 3rem 0; - font-weight: 300; +#explore-landing { + &.main-content { + margin: 0; } - - .explore-title { - display: flex; - flex-direction: row; - gap: 20px; - margin-bottom: 40px; - - &-text { - display: flex; - flex-direction: column; - - h1 { - margin: 0; - color: var(--gm-3-ref-neutral-neutral-20); - font-size: 28px; - font-style: normal; - font-weight: 400; - line-height: 36px; - margin-bottom: 8px; - } - - p { - color: #000; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 20px; - margin-bottom: 0; + .container { + padding: calc(#{var.$spacing} * 8) 0; + &.big { + padding: calc(#{var.$spacing} * 12) 0; + @include media-breakpoint-down(lg) { + padding: calc(#{var.$spacing} * 8) calc(#{var.$spacing} * 3); } } - - &-image { - img { - width: 138px; - height: 93px; - flex-shrink: 0; - } + @include media-breakpoint-down(lg) { + max-width: 100%!important; + padding: var.$container-horizontal-padding calc(#{var.$spacing} * 3); } } - .explore-title-sub-topics { - max-width: 500px; + .intro-text .container { + padding-bottom: 0; } - .topic-queries { - margin-bottom: 2rem; - font-family: var(--font-family-sans-serif); - - ul { - padding: 0; - - li { - list-style: none; - line-height: 1.5rem; - margin-left: 32px; - margin-bottom: 12px; - } - } - - .topic-title { - font-family: $headings-font-family; - font-size: 22px; - font-weight: 400; - line-height: 28px; - margin-bottom: 20px; + .chip-section { + h3 { + @include var.title_lg; + max-width: 750px; } } - - .topic-more { - margin: 2rem 0; - } - - .topic-sources { - margin: 2rem 0; - font-size: 0.9rem; - color: var(--gray); - font-style: italic; - } } diff --git a/static/css/variables.scss b/static/css/variables.scss index 331d3b76ea..bf67babc8e 100644 --- a/static/css/variables.scss +++ b/static/css/variables.scss @@ -9,11 +9,14 @@ $container-horizontal-padding: 3rem; $color-white-container: #F8FAFD; +$color-black: #001D35; + $color-blue:#0B57D0; $color-blue_pill_text:#041E49; $color-blue_light: #6991D6; $color-blue_pill_bckg:#E8F0FE; $color-blue-bckg: #F6F9FF; +$color-blue-chip_bckg: #C2E7FF; $color-green:#146C2E; $color-green_pill_text:#072711; @@ -127,11 +130,18 @@ $color-gray_pill_bckg:#E2E2EC; } @mixin white-box { - @include shadow; background-color: $color-white-container; text-decoration: none; &:hover { - background-color: rgba(11, 87, 208, 0.08); + background-color: rgba($color-blue, $alpha: 0.075); + } +} + +@mixin blue-box { + background-color: $color-blue-chip_bckg; + text-decoration: none; + &:hover { + background-color: rgba($color-blue, $alpha: 0.2); } } diff --git a/static/js/apps/explore/item_list.tsx b/static/js/apps/explore/item_list.tsx index cf72f90250..04c922fd1a 100644 --- a/static/js/apps/explore/item_list.tsx +++ b/static/js/apps/explore/item_list.tsx @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,10 @@ */ /** - * Component for rendering the side bar of a subject page. + * Component for rendering the sidebar of a subject page. */ -import React from "react"; +import React, { ReactElement } from "react"; export interface Item { text: string; @@ -29,7 +29,7 @@ interface ItemListPropType { items: Item[]; } -export function ItemList(props: ItemListPropType): JSX.Element { +export function ItemList(props: ItemListPropType): ReactElement { return (
diff --git a/static/js/apps/explore_landing/app.tsx b/static/js/apps/explore_landing/app.tsx index 817d868666..c4af574927 100644 --- a/static/js/apps/explore_landing/app.tsx +++ b/static/js/apps/explore_landing/app.tsx @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,101 +17,55 @@ /** * Main component for DC Explore. */ -import React from "react"; -import { Container } from "reactstrap"; +import React, { ReactElement } from "react"; -import { NlSearchBar } from "../../components/nl_search_bar"; -import { - GA_EVENT_NL_SEARCH, - GA_PARAM_QUERY, - GA_PARAM_SOURCE, - GA_VALUE_SEARCH_SOURCE_EXPLORE_LANDING, - triggerGAEvent, -} from "../../shared/ga_events"; -import { Topic, TopicConfig } from "../../shared/topic_config"; -import { TopicQueries } from "../../shared/topic_queries"; -import { Item, ItemList } from "../explore/item_list"; -import allTopics from "./topics.json"; +import { TopicConfig, TopicData } from "../../shared/topic_config"; +import { ExploreIntro } from "./components/explore_intro"; +import { Queries } from "./components/queries"; +import { StatVarQueries } from "./components/stat_var_queries"; +import topicData from "./topics.json"; + +const topics: TopicData = topicData; /** * Application container */ -export function App(): JSX.Element { - const topic = window.location.href.split("/").pop().split("#")[0]; - const currentTopic = allTopics.topics[topic] as TopicConfig; - const additionalTopics = allTopics.allTopics - .map((name) => ({ - name, - title: allTopics.topics[name]?.title, - })) - .filter((item) => !item.title || item.name !== topic) as Topic[]; - const subTopicItems: Item[] = - currentTopic.subTopics?.map((query) => ({ - text: query.title, - url: `/explore#${query.url || "/"}`, - })) || []; +export function App(): ReactElement { + const topicSlug = window.location.href.split("/").pop().split("#")[0]; + const topic = topics.topics[topicSlug] as TopicConfig; + + //const dc = topicSlug === "sdg" ? "sdg" : ""; - let dc = ""; - if (topic === "sdg") { - dc = "sdg"; - } if (!topic) { return ( -
- -

- No topics found for {'"'} - {topic} - {'"'} -

-
+
+

+ No topics found for {'"'} + {topic} + {'"'} +

); } - const placeholderQuery = - currentTopic.examples?.general?.length > 0 - ? currentTopic.examples.general[0] + /* + NOTE: The comments on this page existed as comments in the original template. + const placeholderQuery = + topic.examples?.general?.length > 0 + ? topic.examples.general[0] : { title: "family earnings in california" }; const placeholderHref = `/explore#${placeholderQuery.url}` || - `/explore#q=${encodeURIComponent(placeholderQuery.title)}&dc=${dc}`; + `/explore#q=${encodeURIComponent(placeholderQuery.title)}&dc=${dc}`;*/ return ( -
- - { - triggerGAEvent(GA_EVENT_NL_SEARCH, { - [GA_PARAM_QUERY]: q, - [GA_PARAM_SOURCE]: GA_VALUE_SEARCH_SOURCE_EXPLORE_LANDING, - }); - window.location.href = - q.toLocaleLowerCase() === placeholderQuery.title.toLowerCase() - ? placeholderHref - : `/explore#q=${encodeURIComponent(q)}&dc=${dc}`; - }} - placeholder={"Enter a question to explore"} - initialValue={""} - shouldAutoFocus={false} - /> -
-
- -
-
-

{currentTopic.title}

-
- -
-
-
- -
-
+ <> + + + + + ); } diff --git a/static/js/apps/explore_landing/components/explore_intro.tsx b/static/js/apps/explore_landing/components/explore_intro.tsx new file mode 100644 index 0000000000..c731028cee --- /dev/null +++ b/static/js/apps/explore_landing/components/explore_intro.tsx @@ -0,0 +1,58 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Component for topic page welcome message with query examples. + */ + +import React, { ReactElement } from "react"; + +import { IntroText } from "../../../components/content/intro_text"; +import { formatNumber } from "../../../i18n/i18n"; +import { TopicConfig } from "../../../shared/topic_config"; + +interface ExploreIntroProps { + topic: TopicConfig; +} + +export const ExploreIntro = ({ topic }: ExploreIntroProps): ReactElement => { + return ( + +
+

{topic.title}

+

+ Our {topic.title.toLocaleLowerCase()} data spans over{" "} + + {formatNumber(topic.meta.variableCount)} + {" "} + statistical variables. We collect our{" "} + {topic.title.toLocaleLowerCase()} information from sources such as:{" "} + {topic.meta.sources.map((s, i) => ( + + {topic.meta.sources.length > 1 && + i === topic.meta.sources.length - 1 + ? "and " + : ""} + {s} + {i === topic.meta.sources.length - 1 ? "" : ", "} + + ))} + {"."} +

+
+
+ ); +}; diff --git a/static/js/apps/explore_landing/components/queries.tsx b/static/js/apps/explore_landing/components/queries.tsx new file mode 100644 index 0000000000..b5c7d356f5 --- /dev/null +++ b/static/js/apps/explore_landing/components/queries.tsx @@ -0,0 +1,44 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Component for explore landing topic page - query examples displayed as + * interlocking bricks. + */ + +import React, { ReactElement } from "react"; + +import { BrickWall } from "../../../components/content/brick_wall"; +import { Query } from "../../../shared/topic_config"; +import { QueryLink } from "./query_link"; + +interface TopicQueriesProps { + title?: string; + appName: string; + queries: Query[]; +} + +export function Queries({ + title, + queries, + appName, +}: TopicQueriesProps): ReactElement { + const generalTopicQueries = queries.map((query) => ( + + )); + + return ; +} diff --git a/static/js/apps/explore_landing/components/query_link.tsx b/static/js/apps/explore_landing/components/query_link.tsx new file mode 100644 index 0000000000..d8215561f4 --- /dev/null +++ b/static/js/apps/explore_landing/components/query_link.tsx @@ -0,0 +1,46 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Component for query links used by various components in the explore section. + */ + +import React, { ReactElement } from "react"; + +import { CLIENT_TYPES } from "../../../constants/app/explore_constants"; +import { Query } from "../../../shared/topic_config"; + +interface QueryLinkProps { + query: Query; + appName: string; +} + +export function QueryLink(props: QueryLinkProps): ReactElement { + const { query } = props; + const cliParam = `client=${CLIENT_TYPES.LANDING}`; + let url = `${window.location.origin}/${props.appName}#${cliParam}`; + if (props.appName == "explore") { + if (query.url) { + url += `&${query.url}`; + } else { + url += `&oq=${encodeURIComponent(query.title)}`; + } + } else { + // NL + url += `&q=${encodeURIComponent(query.title)}&a=True`; + } + return {query.title}; +} diff --git a/static/js/apps/explore_landing/components/stat_var_queries.tsx b/static/js/apps/explore_landing/components/stat_var_queries.tsx new file mode 100644 index 0000000000..75224b5134 --- /dev/null +++ b/static/js/apps/explore_landing/components/stat_var_queries.tsx @@ -0,0 +1,50 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Component for the stat var queries on the explore landing pages. + */ + +import React, { ReactElement } from "react"; + +import { LinkChip, LinkChips } from "../../../components/content/link_chips"; +import { Query } from "../../../shared/topic_config"; + +interface StatVarQueriesProps { + queries: Query[]; +} + +export function StatVarQueries({ queries }: StatVarQueriesProps): ReactElement { + const statVarLinkChips: LinkChip[] = queries.map((query) => ({ + id: query.url, + title: query.title, + url: query.url, + })); + + if (queries.length === 0) { + return null; + } + return ( + + ); +} diff --git a/static/js/apps/explore_landing/topics.json b/static/js/apps/explore_landing/topics.json index 4649b311ac..7b8aa2efe9 100644 --- a/static/js/apps/explore_landing/topics.json +++ b/static/js/apps/explore_landing/topics.json @@ -63,6 +63,28 @@ "title": "What is the correlation between diabetes and poverty across US counties", "url": "q=What+is+the+correlation+between+diabetes+and+poverty+across+US+counties" } + ], + "statvar": [ + { + "title": "Population with household spending on health greater than 10% of total household in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22who%2FFINPROTECTION_CATA_TOT_10_LEVEL_SH%22%7D" + }, + { + "title": "Total population pushed below a relative poverty line by household health expenditures in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22who%2FFINPROTECTION_IMP_NP_REL_LEVEL_MILLION%22%7D" + }, + { + "title": "Mortality rate attributed to unsafe water, unsafe sanitation in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22who%2FWSH_1%22%7D" + }, + { + "title": "Legal frameworks that promote, enforce and monitor gender equality -- Area 3: employment and economic benefits in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22sdg%2FSG_LGL_GENEQEMP%22%7D" + }, + { + "title": "Legal frameworks that promote, enforce and monitor gender equality -- Area 2: violence against women in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22sdg%2FSG_LGL_GENEQVAW%22%7D" + } ] }, "meta": { @@ -194,6 +216,28 @@ "title": "Compare the education expenditure in the US vs. Canada", "url": "q=Compare+the+education+expenditure+in+the+US+vs+Canada" } + ], + "statvar": [ + { + "title": "Population in the labor force", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person_InLaborForce%22%7D" + }, + { + "title": "Percent of females working between (15 to 64) in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person_15To64Years_Female_InLaborForce_AsFractionOf_Count_Person_15To64Years_Female%22%7D" + }, + { + "title": "Outward Remittances in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Amount_Remittance_OutwardRemittance%22%7D" + }, + { + "title": "Mobile Phone Subscriptions Per Person in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Amount_Remittance_OutwardRemittance%22%7D___%7B%22dcid%22%3A%22Count_Product_MobileCellularSubscription_AsFractionOf_Count_Person%22%7D" + }, + { + "title": "Female Life Expectancy", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Amount_Remittance_OutwardRemittance%22%7D___%7B%22dcid%22%3A%22Count_Product_MobileCellularSubscription_AsFractionOf_Count_Person%22%7D___%7B%22dcid%22%3A%22LifeExpectancy_Person_Female%22%7D" + } ] }, "meta": { @@ -315,6 +359,28 @@ "title": "Tell me about maternal mortality across countries in the world", "url": "q=Tell+me+about+maternal+mortality+across+countries+in+the+world" } + ], + "statvar": [ + { + "title": "Fraction of 15+ Years Old Females Smoking", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person_15OrMoreYears_Female_Smoking_AsFractionOf_Count_Person_15OrMoreYears_Female%22%7D" + }, + { + "title": "Fraction of 20 - 79 years old people who have diabetes", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person_20To79Years_Diabetes_AsFractionOf_Count_Person_20To79Years%22%7D" + }, + { + "title": "Deaths Rate in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Death_AsAFractionOfCount_Person%22%7D" + }, + { + "title": "Crude birth rate in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22LifeExpectancy_Person%22%7D___%7B%22dcid%22%3A%22Count_BirthEvent_LiveBirth_AsFractionOf_Count_Person%22%7D" + }, + { + "title": "Male and female life expectancy in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22LifeExpectancy_Person_Male%22%7D___%7B%22dcid%22%3A%22LifeExpectancy_Person_Female%22%7D" + } ] }, "meta": { @@ -434,6 +500,32 @@ "title": "Median home value vs. Work from home population in US counties", "url": "q=Median+home+value+vs.+Work+from+home+population+in+US+counties" } + ], + "statvar": [ + { + "title": "Population living in rural areas around the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person_Rural%22%7D" + }, + { + "title": "Population in the Labor Force in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person_InLaborForce%22%7D" + }, + { + "title": "Population living in rural and urban areas around the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person_Rural%22%7D___%7B%22dcid%22%3A%22Count_Person_Urban%22%7D" + }, + { + "title": "Median monthly gross rent of housing unit in the USA", + "url": "/tools/visualization#visType%3Dtimeline%26place%3Dcountry%2FUSA%26placeType%3DState%26sv%3D%7B%22dcid%22%3A%22Monthly_Median_GrossRent_HousingUnit%22%7D" + }, + { + "title": "Count of Housing Unit: Without Mortgage, 800 USD or Less", + "url": "/tools/visualization#visType%3Dtimeline%26place%3Dcountry%2FUSA%26placeType%3DState%26sv%3D%7B%22dcid%22%3A%22dc%2Fq0p7fcll1lv8c%22%7D" + }, + { + "title": "Commute time in the USA", + "url": "/tools/visualization#visType%3Dtimeline%26place%3Dcountry%2FUSA%26placeType%3DState%26sv%3D%7B%22dcid%22%3A%22dc%2Fe9gftzl2hm8h9%22%7D" + } ] }, "meta": { @@ -555,6 +647,28 @@ "title": "Foreign born population vs. PhD holders in California counties", "url": "q=Foreign+born+population+vs.+PhD+holders+in+California+counties" } + ], + "statvar": [ + { + "title": "Fertility rate in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22FertilityRate_Person_Female%22%7D" + }, + { + "title": "Life expectancy in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22LifeExpectancy_Person%22%7D" + }, + { + "title": "Rate of population growth in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22GrowthRate_Count_Person%22%7D" + }, + { + "title": "Total population in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person%22%7D" + }, + { + "title": "Population living in Urban areas in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_Person%22%7D___%7B%22dcid%22%3A%22Count_Person_Urban%22%7D" + } ] }, "meta": { @@ -686,6 +800,28 @@ "title": "What is the correlation of AQI vs. blood pressure in New Jersey", "url": "q=What+is+the+correlation+of+AQI+vs+blood+pressure+in+New+Jersey" } + ], + "statvar": [ + { + "title": "Carbon dioxide emissions from fuel combustion in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22sdg%2FEN_ATM_CO2%22%7D" + }, + { + "title": "Annual Amount of Emissions: Aluminum Production, Carbon Dioxide", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Annual_Emissions_CarbonDioxide_AluminumProduction%22%7D" + }, + { + "title": "Annual amount of emissions: agriculture, carbon dioxide", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Annual_Emissions_CarbonDioxide_Agriculture%22%7D" + }, + { + "title": "Area of flood event(s) in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Area_FloodEvent%22%7D" + }, + { + "title": "Count of fire events in the world", + "url": "/tools/visualization#visType%3Dtimeline%26place%3DEarth%26placeType%3DCountry%26sv%3D%7B%22dcid%22%3A%22Count_FireEvent%22%7D" + } ] }, "meta": { diff --git a/static/js/apps/homepage/app.tsx b/static/js/apps/homepage/app.tsx index f530d30978..5d0be9640e 100644 --- a/static/js/apps/homepage/app.tsx +++ b/static/js/apps/homepage/app.tsx @@ -65,7 +65,11 @@ export function App({ return ( <> - + diff --git a/static/js/components/content/brick_wall.tsx b/static/js/components/content/brick_wall.tsx new file mode 100644 index 0000000000..56211bad84 --- /dev/null +++ b/static/js/components/content/brick_wall.tsx @@ -0,0 +1,73 @@ +import React, { ReactElement, useMemo } from "react"; + +interface BrickWallProps { + // Optional title for the wall + title?: string; + // Array of React elements to be arranged on the wall + bricks: ReactElement[]; +} + +// the number of columns that the brick wall is divided into +const NUM_COLUMNS = 2; +// the pattern of each column, where [2, 1, 1] means two bricks in the first row, then one brick then one brick +// the pattern then repeats +const patterns = [ + [2, 1, 1], + [1, 2, 1], +]; + +export const BrickWall = ({ title, bricks }: BrickWallProps): ReactElement => { + // This function divides the bricks into columns (based on the NUM_COLUMNS above), and tracks the number of rows in + // each column. The number of rows isn't simple to calculate because it varies based on the pattern of the bricks. + const { columns, numRows } = useMemo(() => { + const columns: ReactElement[][] = Array.from( + { length: NUM_COLUMNS }, + () => [] + ); + const numRows: number[] = Array(NUM_COLUMNS).fill(0); + const patternIndices = Array(NUM_COLUMNS).fill(0); + + let currentColumn = 0; + + for (let i = 0; i < bricks.length; ) { + const columnPattern = patterns[currentColumn % patterns.length]; + const currentPattern = columnPattern[patternIndices[currentColumn]]; + + for (let j = 0; j < currentPattern && i < bricks.length; j++, i++) { + columns[currentColumn].push(bricks[i]); + } + + numRows[currentColumn]++; + patternIndices[currentColumn] = + (patternIndices[currentColumn] + 1) % columnPattern.length; + + currentColumn = (currentColumn + 1) % NUM_COLUMNS; + } + + return { columns, numRows }; + }, [bricks]); + + if (bricks.length === 0) { + return null; + } + + return ( +
+
+ {title &&

{title}

} + {columns.map((column, columnIndex) => ( +
+ {column.map((brick, i) => ( +
+ {brick} +
+ ))} +
+ ))} +
+
+ ); +}; diff --git a/static/js/components/content/intro_text.tsx b/static/js/components/content/intro_text.tsx new file mode 100644 index 0000000000..74ab8327d6 --- /dev/null +++ b/static/js/components/content/intro_text.tsx @@ -0,0 +1,34 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A component to display an introductory text block + */ + +import React, { ReactElement } from "react"; + +interface IntroTextProps { + //the content (text or other content) as a React element + children: ReactElement; +} + +export const IntroText = ({ children }: IntroTextProps): ReactElement => { + return ( +
+
{children}
+
+ ); +}; diff --git a/static/js/components/content/link_chips.tsx b/static/js/components/content/link_chips.tsx index fa7a857553..ba658ef91e 100644 --- a/static/js/components/content/link_chips.tsx +++ b/static/js/components/content/link_chips.tsx @@ -38,31 +38,36 @@ export interface LinkChip { } interface LinkChipsProps { + //the variant of the link chip to display: elevated is a raised grey chip and flat is a flat blue chip + variant?: "elevated" | "flat"; //the title of the component, displayed as a header above the chips title?: string; + //the section gives location of the chip component in order to give context for the GA event + section: string; //the link linkChips: LinkChip[]; } export const LinkChips = ({ + variant = "elevated", title, + section, linkChips, }: LinkChipsProps): ReactElement => { return ( -
+
{title &&

{title}

}