From 6c251ef9e64dabe3e158021875a3b1e8336dad68 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 10 Jun 2020 12:52:06 +0300 Subject: [PATCH 1/3] #4503 --- .../components/Contentful/Tabs/Tabs.jsx | 37 ++++++++++++++++++- .../components/Contentful/Tabs/index.jsx | 1 + .../Contentful/Viewport/Viewport.jsx | 11 ++++++ src/shared/utils/url.js | 20 ++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/shared/components/Contentful/Tabs/Tabs.jsx b/src/shared/components/Contentful/Tabs/Tabs.jsx index c4f790cc08..b8bc202a10 100644 --- a/src/shared/components/Contentful/Tabs/Tabs.jsx +++ b/src/shared/components/Contentful/Tabs/Tabs.jsx @@ -17,6 +17,7 @@ import { TabPanel, } from 'react-tabs'; import { fixStyle } from 'utils/contentful'; +import { getQuery, updateQuery } from 'utils/url'; import defaultTheme from './themes/style.scss'; import zurichTheme from './themes/zurich.scss'; import tabsGroup from './themes/tabsGroup.scss'; @@ -38,7 +39,38 @@ export const TAB_THEMES = { export default class TabsItemsLoader extends Component { constructor(props) { super(props); - this.state = { tabIndex: props.selected }; + this.state = { + tabIndex: props.selected || 0, + }; + + this.updatePageUrl.bind(this); + } + + componentDidMount() { + const q = getQuery(); + const { tabId } = this.props; + const { tabIndex } = this.state; + if (q.tabs && q.tabs[tabId] && Number(q.tabs[tabId]) !== tabIndex) { + this.setState({ tabIndex: Number(q.tabs[tabId]) }); + } else { + this.updatePageUrl(); + } + } + + componentDidUpdate() { + this.updatePageUrl(); + } + + updatePageUrl() { + const q = getQuery(); + const { tabId } = this.props; + const { tabIndex } = this.state; + updateQuery({ + tabs: { + ...q.tabs, + [tabId]: tabIndex || 0, + }, + }); } render() { @@ -48,6 +80,7 @@ export default class TabsItemsLoader extends Component { spaceName, environment, theme, + tabId, } = this.props; const { tabIndex } = this.state; @@ -127,6 +160,7 @@ export default class TabsItemsLoader extends Component { environment={environment} selected={fields.selected} theme={TAB_THEMES[fields.theme || 'Default']} + tabId={tabId} /> ); } @@ -171,4 +205,5 @@ TabsItemsLoader.propTypes = { environment: PT.string, selected: PT.number, theme: PT.shape().isRequired, + tabId: PT.string.isRequired, }; diff --git a/src/shared/components/Contentful/Tabs/index.jsx b/src/shared/components/Contentful/Tabs/index.jsx index a109794431..cda003fb7f 100644 --- a/src/shared/components/Contentful/Tabs/index.jsx +++ b/src/shared/components/Contentful/Tabs/index.jsx @@ -34,6 +34,7 @@ function ContentfulTabs(props) { environment={environment} selected={fields.selected} theme={TAB_THEMES[fields.theme || 'Default']} + tabId={fields.urlQueryName || id} /> ); }} diff --git a/src/shared/components/Contentful/Viewport/Viewport.jsx b/src/shared/components/Contentful/Viewport/Viewport.jsx index 1b7609d55b..606091089a 100644 --- a/src/shared/components/Contentful/Viewport/Viewport.jsx +++ b/src/shared/components/Contentful/Viewport/Viewport.jsx @@ -4,6 +4,8 @@ import PT from 'prop-types'; import React from 'react'; import { themr } from 'react-css-super-themr'; +import { getHash } from 'utils/url'; + /* Loads viewport content assets. */ const Viewport = ({ @@ -13,6 +15,15 @@ const Viewport = ({ viewportId, animation, }) => { + const hash = getHash(); + if (hash && hash.vid && hash.vid === viewportId) { + setTimeout(() => { + requestAnimationFrame(() => { + const section = document.getElementById(viewportId); + if (section) section.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); + }); + } // Animated on scroll viewport? if (animation.animateOnScroll) { return ( diff --git a/src/shared/utils/url.js b/src/shared/utils/url.js index 73154aa74a..dc2a90d0ac 100644 --- a/src/shared/utils/url.js +++ b/src/shared/utils/url.js @@ -8,6 +8,22 @@ import _ from 'lodash'; import qs from 'qs'; import { isomorphy } from 'topcoder-react-utils'; +/** + * Get current URL hash parameters as object + */ +export function getHash() { + if (isomorphy.isServerSide()) return null; + return qs.parse(window.location.hash.slice(1)); +} + +/** + * Get current URL query parameters as object + */ +export function getQuery() { + if (isomorphy.isServerSide()) return {}; + return qs.parse(window.location.search.slice(1)); +} + /** * If executed client-side (determined in this case by the presence of global * window object), this function updates query section of URL; otherwise does @@ -22,6 +38,7 @@ export function updateQuery(update) { if (isomorphy.isServerSide()) return; let query = qs.parse(window.location.search.slice(1)); + const { hash } = window.location; /* _.merge won't work here, because it just ignores the fields explicitely * set as undefined in the objects to be merged, rather than deleting such @@ -31,6 +48,9 @@ export function updateQuery(update) { else query[key] = value; }); query = `?${qs.stringify(query, { encodeValuesOnly: true })}`; + if (hash) { + query += hash; + } window.history.replaceState(window.history.state, '', query); } From 584931175874f5b429a98fe8cbda99b45a763039 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 10 Jun 2020 12:53:51 +0300 Subject: [PATCH 2/3] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3af2326db1..532cae71c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -231,7 +231,7 @@ workflows: branches: only: - develop - - feature-contentful + - feature-contentful-tabs-urls # This is alternate dev env for parallel testing - "build-test": context : org-global From d8687a73016db44243066859c13ac0505f3b9567 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 24 Jul 2020 15:28:38 +0300 Subject: [PATCH 3/3] ci: feature-contentful-tabs-urls on dev --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f73a20e51d..50466e3582 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -231,6 +231,7 @@ workflows: branches: only: - develop + - feature-contentful-tabs-urls # This is alternate dev env for parallel testing - "build-test": context : org-global