From 52e3ef31a6157a6a544bf712a73d118d22913583 Mon Sep 17 00:00:00 2001 From: Bryn Ryans Date: Tue, 21 Sep 2021 17:55:51 -0700 Subject: [PATCH] fix: Sidenav selection (#831) Tabs in sidenav were not being synced with history. Now if tab selected, history is updated. If page is reloaded, tabs are synced to current history. If link is clicked that updates sidenav panel, panel is updated accordingly. --- .../src/components/SideNav/SideNav.spec.tsx | 3 +- .../src/components/SideNav/SideNav.tsx | 40 ++++++++++++++----- .../api-explorer/src/reducers/spec/utils.ts | 7 +++- .../src/scenes/DiffScene/DiffScene.tsx | 9 ++++- .../src/scenes/TagScene/TagScene.tsx | 24 ++++++----- 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index f1f9e94bf..5a4fc362a 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -83,7 +83,8 @@ describe('SideNav', () => { specs={specs} spec={specState.spec} specDispatch={specDispatch} - /> + />, + ['/3.1/methods'] ) expect(screen.getAllByText(allTagsPattern)).toHaveLength(2) expect( diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index d364fcf85..d3868db4a 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -26,6 +26,7 @@ import type { FC, Dispatch } from 'react' import React, { useContext, useEffect, useState } from 'react' +import { useHistory, useLocation } from 'react-router-dom' import { Heading, TabList, @@ -43,7 +44,6 @@ import type { ApiModel, } from '@looker/sdk-codegen' import { CriteriaToSet } from '@looker/sdk-codegen' -import { useRouteMatch } from 'react-router-dom' import { SearchContext } from '../../context' import type { SpecAction } from '../../reducers' @@ -66,25 +66,38 @@ interface SideNavProps { specDispatch: Dispatch } -interface SideNavParams { - sideNavTab: string -} - export const SideNav: FC = ({ headless = false, specs, spec, specDispatch, }) => { + const history = useHistory() + const location = useLocation() const api = spec.api || ({} as ApiModel) const specKey = spec.key const tabNames = ['methods', 'types'] - const match = useRouteMatch(`/:specKey/:sideNavTab?`) - let defaultIndex = tabNames.indexOf('methods') - if (match && match.params.sideNavTab) { - defaultIndex = tabNames.indexOf(match.params.sideNavTab) + const pathParts = location.pathname.split('/') + const sideNavTab = pathParts[1] === 'diff' ? pathParts[3] : pathParts[2] + let defaultIndex = tabNames.indexOf(sideNavTab) + if (defaultIndex < 0) { + defaultIndex = tabNames.indexOf('methods') + } + const onTabChange = (index: number) => { + const parts = location.pathname.split('/') + if (parts[1] === 'diff') { + if (parts[3] !== tabNames[index]) { + parts[3] = tabNames[index] + history.push(parts.join('/')) + } + } else { + if (parts[2] !== tabNames[index]) { + parts[2] = tabNames[index] + history.push(parts.join('/')) + } + } } - const tabs = useTabs({ defaultIndex }) + const tabs = useTabs({ defaultIndex, onChange: onTabChange }) const { searchSettings, setSearchSettings } = useContext(SearchContext) const [pattern, setSearchPattern] = useState(searchSettings.pattern) const debouncedPattern = useDebounce(pattern, 250) @@ -120,6 +133,13 @@ export const SideNav: FC = ({ setSearchSettings(setPattern(debouncedPattern!)) }, [debouncedPattern, specKey, spec]) + useEffect(() => { + const { selectedIndex, onSelectTab } = tabs + if (defaultIndex !== selectedIndex) { + onSelectTab(defaultIndex) + } + }, [defaultIndex, tabs]) + const size = useWindowSize() const headlessOffset = headless ? 200 : 120 const menuH = size.height - 16 * HEADER_REM - headlessOffset diff --git a/packages/api-explorer/src/reducers/spec/utils.ts b/packages/api-explorer/src/reducers/spec/utils.ts index 55b8c5d06..8c67a665e 100644 --- a/packages/api-explorer/src/reducers/spec/utils.ts +++ b/packages/api-explorer/src/reducers/spec/utils.ts @@ -111,8 +111,13 @@ export const initDefaultSpecState = ( location: AbstractLocation ): SpecState => { const specKey = getSpecKey(location, specList) + // Handle bad spec in the URL. Fall back to 4.0 + let spec = specList[specKey] + if (!spec) { + spec = specList['4.0'] + } return { specList, - spec: specList[specKey], + spec, } } diff --git a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx index 6bf8f1367..3f6413843 100644 --- a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx +++ b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx @@ -99,11 +99,16 @@ export interface DiffSceneProps { toggleNavigation: (target?: boolean) => void } +const validateParam = (specs: SpecList, specKey = '') => { + return specs[specKey] ? specKey : '' +} + export const DiffScene: FC = ({ specs, toggleNavigation }) => { const history = useHistory() const match = useRouteMatch<{ l: string; r: string }>(`/${diffPath}/:l?/:r?`) - const l = match?.params.l || '' - const r = match?.params.r || '' + const l = validateParam(specs, match?.params.l) + const r = validateParam(specs, match?.params.r) + const options = Object.entries(specs).map(([key, spec]) => ({ value: key, label: `${key} (${spec.status})`, diff --git a/packages/api-explorer/src/scenes/TagScene/TagScene.tsx b/packages/api-explorer/src/scenes/TagScene/TagScene.tsx index 14594069f..7b34e5d25 100644 --- a/packages/api-explorer/src/scenes/TagScene/TagScene.tsx +++ b/packages/api-explorer/src/scenes/TagScene/TagScene.tsx @@ -25,9 +25,9 @@ */ import type { FC } from 'react' import React, { useEffect, useState } from 'react' +import { useHistory, useParams } from 'react-router-dom' import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' -import { useParams, useHistory } from 'react-router-dom' import { ApixSection, DocTitle, DocMethodSummary, Link } from '../../components' import { buildMethodPath } from '../../utils' import { getOperations } from './utils' @@ -44,14 +44,6 @@ interface TagSceneParams { export const TagScene: FC = ({ api }) => { const { specKey, methodTag } = useParams() const history = useHistory() - if (!(methodTag in api.tags)) { - history.push('/methods') - } - const methods = api.tags[methodTag] - const tag = Object.values(api.spec.tags!).find( - (tag) => tag.name === methodTag - )! - const operations = getOperations(methods) const [value, setValue] = useState('ALL') useEffect(() => { @@ -59,6 +51,20 @@ export const TagScene: FC = ({ api }) => { setValue('ALL') }, [methodTag]) + const methods = api.tags[methodTag] + useEffect(() => { + if (!methods) { + history.push(`/${specKey}/methods`) + } + }, [history, methods]) + if (!methods) { + return <> + } + const tag = Object.values(api.spec.tags!).find( + (tag) => tag.name === methodTag + )! + const operations = getOperations(methods) + return ( {`${tag.name}: ${tag.description}`}