diff --git a/packages/api-explorer/src/components/DocReferences/utils.tsx b/packages/api-explorer/src/components/DocReferences/utils.tsx index 5b0f6bd71..af577c617 100644 --- a/packages/api-explorer/src/components/DocReferences/utils.tsx +++ b/packages/api-explorer/src/components/DocReferences/utils.tsx @@ -25,7 +25,7 @@ */ import React from 'react'; import type { ApiModel, IMethod, IType } from '@looker/sdk-codegen'; -import { Link } from 'react-router-dom'; +import { Link } from '../Link'; import { RunItHeading } from '@looker/run-it'; import { buildPath, highlightHTML } from '../../utils'; diff --git a/packages/api-explorer/src/components/Link/Link.tsx b/packages/api-explorer/src/components/Link/Link.tsx index 01343d3f8..42d83ff06 100644 --- a/packages/api-explorer/src/components/Link/Link.tsx +++ b/packages/api-explorer/src/components/Link/Link.tsx @@ -24,10 +24,76 @@ */ -import { NavLink } from 'react-router-dom'; +import type { FC, MouseEvent } from 'react'; +import React from 'react'; +import { NavLink, useHistory, useLocation } from 'react-router-dom'; import styled from 'styled-components'; +import { tryGetEnvAdaptor } from '@looker/extension-utils'; -export const Link = styled(NavLink)` +export interface LinkProps { + to: string; + exact?: boolean; + activeClassName?: string; + className?: string; +} + +const LinkComponent: FC = ({ + to, + exact, + activeClassName, + className, + children, + ...props +}) => { + const adaptor = tryGetEnvAdaptor(); + const history = useHistory(); + const location = useLocation(); + + if (adaptor && adaptor.isExtension()) { + const absoluteUrl = adaptor.resolveRouteToAbsoluteUrl(to); + + const handleClick = (e: MouseEvent) => { + const isRegularClick = + e.button === 0 && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey; + + if (isRegularClick) { + e.preventDefault(); + history.push(to); + } + }; + + const isActive = exact + ? location.pathname === to + : location.pathname.startsWith(to); + const activeClass = isActive ? activeClassName || 'active' : ''; + const combinedClassName = `${className || ''} ${activeClass}`.trim(); + + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +export const Link = styled(LinkComponent)` text-decoration: none; color: inherit; diff --git a/packages/extension-utils/src/adaptorUtils.ts b/packages/extension-utils/src/adaptorUtils.ts index 2c8f34e74..a92583fc6 100644 --- a/packages/extension-utils/src/adaptorUtils.ts +++ b/packages/extension-utils/src/adaptorUtils.ts @@ -61,6 +61,8 @@ export interface IEnvironmentAdaptor extends IAuthAdaptor { openBrowserWindow: (url: string, target?: string) => void; /** error logger */ logError: (error: Error, componentStack: string) => void; + /** Resolves a path to a fully qualified URL for the environment host */ + resolveRouteToAbsoluteUrl(pathname: string, search?: string): string; } /** @@ -137,6 +139,10 @@ export const getEnvAdaptor = () => { return extensionAdaptor; }; +export const tryGetEnvAdaptor = () => { + return extensionAdaptor; +}; + /** * Used by some unit tests * @param adaptor to use for testing diff --git a/packages/extension-utils/src/browserAdaptor.ts b/packages/extension-utils/src/browserAdaptor.ts index 588edcb1b..1ee559e4d 100644 --- a/packages/extension-utils/src/browserAdaptor.ts +++ b/packages/extension-utils/src/browserAdaptor.ts @@ -92,4 +92,8 @@ export class BrowserAdaptor logError(_error: Error, _componentStack: string): void { // noop - error logging for standalone applications TBD } + + resolveRouteToAbsoluteUrl(pathname: string, search = '') { + return `${window.location.origin}${pathname}${search}`; + } } diff --git a/packages/extension-utils/src/extensionAdaptor.ts b/packages/extension-utils/src/extensionAdaptor.ts index 47e4cdac1..2dbcb78a4 100644 --- a/packages/extension-utils/src/extensionAdaptor.ts +++ b/packages/extension-utils/src/extensionAdaptor.ts @@ -99,4 +99,13 @@ export class ExtensionAdaptor message: componentStack, } as ErrorEvent); } + + resolveRouteToAbsoluteUrl(pathname: string, search = '') { + const { lookerHostData } = this.extensionSdk; + if (lookerHostData) { + const { hostOrigin, extensionId } = lookerHostData; + return `${hostOrigin}/extensions/${extensionId}${pathname}${search}`; + } + return `${pathname}${search}`; + } }