diff options
author | Brandon Millman <brandon.millman@gmail.com> | 2018-03-09 00:43:16 +0800 |
---|---|---|
committer | Brandon Millman <brandon.millman@gmail.com> | 2018-03-09 00:43:16 +0800 |
commit | 098dae9a7e57385505f0f272fd76d44f43fa1e34 (patch) | |
tree | bcd47532910e5804746ec88c8d9f6b85c157d6e1 /packages/react-shared/src | |
parent | 5b5037a844f49c17deeaf695fc8ed57832038da6 (diff) | |
parent | aaa7affa46450bb48639e9b90c2a2e8adb28826c (diff) | |
download | dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.tar.gz dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.tar.zst dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.zip |
Merge branch 'development' into feature/sra-reporter
* development: (68 commits)
Update list of packages and organize them alphabetically
Fix prettier issues
Add support for going back to previous hashes via the browser back button to wiki
Scroll to previous hashed elements when user clicks back button
Add back strict null checks to react-shared package and fix issues
remove ability to have implicit dependencies and add missing deps
update license
remove no-implicit-this
Add example & screenshot to npmignore
Remove `;` to be nice to windows users
Use unencoded @ symbol, browser will fix
Fix external type links
Add comment about commented out CSS exception
Update prettier since the previous version had a bug when dealing with css files
Fix css files with prettier
Added base-contract package to README
Prettify test jsons
Update yarn.lock
Improve README
Feedback
...
Diffstat (limited to 'packages/react-shared/src')
13 files changed, 680 insertions, 0 deletions
diff --git a/packages/react-shared/src/ts/components/anchor_title.tsx b/packages/react-shared/src/ts/components/anchor_title.tsx new file mode 100644 index 000000000..f44354097 --- /dev/null +++ b/packages/react-shared/src/ts/components/anchor_title.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; +import { Link as ScrollLink } from 'react-scroll'; + +import { HeaderSizes, Styles } from '../types'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +const headerSizeToScrollOffset: { [headerSize: string]: number } = { + h2: -20, + h3: 0, +}; + +export interface AnchorTitleProps { + title: string | React.ReactNode; + id: string; + headerSize: HeaderSizes; + shouldShowAnchor: boolean; +} + +export interface AnchorTitleState { + isHovering: boolean; +} + +const styles: Styles = { + anchor: { + fontSize: 20, + transform: 'rotate(45deg)', + cursor: 'pointer', + }, + headers: { + WebkitMarginStart: 0, + WebkitMarginEnd: 0, + fontWeight: 'bold', + display: 'block', + }, + h1: { + fontSize: '1.8em', + }, + h2: { + fontSize: '1.5em', + fontWeight: 400, + }, + h3: { + fontSize: '1.17em', + }, +}; + +export class AnchorTitle extends React.Component<AnchorTitleProps, AnchorTitleState> { + constructor(props: AnchorTitleProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + let opacity = 0; + if (this.props.shouldShowAnchor) { + opacity = this.state.isHovering ? 0.6 : 1; + } + return ( + <div className="relative flex" style={{ ...styles[this.props.headerSize], ...styles.headers }}> + <div className="inline-block" style={{ paddingRight: 4 }}> + {this.props.title} + </div> + <ScrollLink + to={this.props.id} + offset={headerSizeToScrollOffset[this.props.headerSize]} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <i + className="zmdi zmdi-link" + onClick={utils.setUrlHash.bind(utils, this.props.id)} + style={{ ...styles.anchor, opacity }} + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + /> + </ScrollLink> + </div> + ); + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/react-shared/src/ts/components/markdown_code_block.tsx b/packages/react-shared/src/ts/components/markdown_code_block.tsx new file mode 100644 index 000000000..2070bb8e1 --- /dev/null +++ b/packages/react-shared/src/ts/components/markdown_code_block.tsx @@ -0,0 +1,25 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as HighLight from 'react-highlight'; + +export interface MarkdownCodeBlockProps { + value: string; + language: string; +} + +export interface MarkdownCodeBlockState {} + +export class MarkdownCodeBlock extends React.Component<MarkdownCodeBlockProps, MarkdownCodeBlockState> { + // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying + // to copy-paste code examples. We therefore noop re-renders on this component if it's props haven't changed. + public shouldComponentUpdate(nextProps: MarkdownCodeBlockProps, nextState: MarkdownCodeBlockState) { + return nextProps.value !== this.props.value || nextProps.language !== this.props.language; + } + public render() { + return ( + <span style={{ fontSize: 14 }}> + <HighLight className={this.props.language || 'javascript'}>{this.props.value}</HighLight> + </span> + ); + } +} diff --git a/packages/react-shared/src/ts/components/markdown_link_block.tsx b/packages/react-shared/src/ts/components/markdown_link_block.tsx new file mode 100644 index 000000000..8f5862249 --- /dev/null +++ b/packages/react-shared/src/ts/components/markdown_link_block.tsx @@ -0,0 +1,47 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +export interface MarkdownLinkBlockProps { + href: string; +} + +export interface MarkdownLinkBlockState {} + +export class MarkdownLinkBlock extends React.Component<MarkdownLinkBlockProps, MarkdownLinkBlockState> { + // Re-rendering a linkBlock causes it to remain unclickable. + // We therefore noop re-renders on this component if it's props haven't changed. + public shouldComponentUpdate(nextProps: MarkdownLinkBlockProps, nextState: MarkdownLinkBlockState) { + return nextProps.href !== this.props.href; + } + public render() { + const href = this.props.href; + const isLinkToSection = _.startsWith(href, '#'); + // If protocol is http or https, we can open in a new tab, otherwise don't for security reasons + if (_.startsWith(href, 'http') || _.startsWith(href, 'https')) { + return ( + <a href={href} target="_blank" rel="nofollow noreferrer noopener"> + {this.props.children} + </a> + ); + } else if (isLinkToSection) { + return ( + <a + style={{ cursor: 'pointer', textDecoration: 'underline' }} + onClick={this._onHashUrlClick.bind(this, href)} + > + {this.props.children} + </a> + ); + } else { + return <a href={href}>{this.props.children}</a>; + } + } + private _onHashUrlClick(href: string) { + const hash = href.split('#')[1]; + utils.scrollToHash(hash, constants.SCROLL_CONTAINER_ID); + utils.setUrlHash(hash); + } +} diff --git a/packages/react-shared/src/ts/components/markdown_section.tsx b/packages/react-shared/src/ts/components/markdown_section.tsx new file mode 100644 index 000000000..d24a43dcb --- /dev/null +++ b/packages/react-shared/src/ts/components/markdown_section.tsx @@ -0,0 +1,94 @@ +import * as _ from 'lodash'; +import RaisedButton from 'material-ui/RaisedButton'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; +import { Element as ScrollElement } from 'react-scroll'; + +import { HeaderSizes } from '../types'; +import { colors } from '../utils/colors'; +import { utils } from '../utils/utils'; + +import { AnchorTitle } from './anchor_title'; +import { MarkdownCodeBlock } from './markdown_code_block'; +import { MarkdownLinkBlock } from './markdown_link_block'; + +export interface MarkdownSectionProps { + sectionName: string; + markdownContent: string; + headerSize?: HeaderSizes; + githubLink?: string; +} + +interface DefaultMarkdownSectionProps { + headerSize: HeaderSizes; +} + +type PropsWithDefaults = MarkdownSectionProps & DefaultMarkdownSectionProps; + +export interface MarkdownSectionState { + shouldShowAnchor: boolean; +} + +export class MarkdownSection extends React.Component<MarkdownSectionProps, MarkdownSectionState> { + public static defaultProps: Partial<MarkdownSectionProps> = { + headerSize: HeaderSizes.H3, + }; + constructor(props: MarkdownSectionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const { sectionName, markdownContent, headerSize, githubLink } = this.props as PropsWithDefaults; + + const id = utils.getIdFromName(sectionName); + return ( + <div + className="md-px1 sm-px2 overflow-hidden" + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <ScrollElement name={id}> + <div className="clearfix pt3"> + <div className="col lg-col-8 md-col-8 sm-col-12"> + <span style={{ textTransform: 'capitalize', color: colors.grey700 }}> + <AnchorTitle + headerSize={headerSize} + title={sectionName} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </span> + </div> + <div className="col col-4 sm-hide xs-hide right-align pr3" style={{ height: 28 }}> + {!_.isUndefined(githubLink) && ( + <a + href={githubLink} + target="_blank" + style={{ color: colors.linkBlue, textDecoration: 'none', lineHeight: 2.1 }} + > + Edit on Github + </a> + )} + </div> + </div> + <hr style={{ border: `1px solid ${colors.lightestGrey}` }} /> + <ReactMarkdown + source={markdownContent} + escapeHtml={false} + renderers={{ + code: MarkdownCodeBlock, + link: MarkdownLinkBlock, + }} + /> + </ScrollElement> + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx b/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx new file mode 100644 index 000000000..2225bd197 --- /dev/null +++ b/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx @@ -0,0 +1,158 @@ +import * as _ from 'lodash'; +import MenuItem from 'material-ui/MenuItem'; +import * as React from 'react'; +import { Link as ScrollLink } from 'react-scroll'; + +import { MenuSubsectionsBySection, Styles } from '../types'; +import { colors } from '../utils/colors'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +import { VersionDropDown } from './version_drop_down'; + +export interface NestedSidebarMenuProps { + topLevelMenu: { [topLevel: string]: string[] }; + menuSubsectionsBySection: MenuSubsectionsBySection; + sidebarHeader?: React.ReactNode; + shouldDisplaySectionHeaders?: boolean; + onMenuItemClick?: () => void; + selectedVersion?: string; + versions?: string[]; + onVersionSelected?: (semver: string) => void; +} + +export interface NestedSidebarMenuState {} + +const styles: Styles = { + menuItemWithHeaders: { + minHeight: 0, + }, + menuItemWithoutHeaders: { + minHeight: 48, + }, + menuItemInnerDivWithHeaders: { + color: colors.grey800, + fontSize: 14, + lineHeight: 2, + padding: 0, + }, +}; + +export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, NestedSidebarMenuState> { + public static defaultProps: Partial<NestedSidebarMenuProps> = { + shouldDisplaySectionHeaders: true, + onMenuItemClick: _.noop, + }; + public render() { + const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { + const finalSectionName = sectionName.replace(/-/g, ' '); + if (this.props.shouldDisplaySectionHeaders) { + const id = utils.getIdFromName(sectionName); + return ( + <div key={`section-${sectionName}`} className="py1" style={{ color: colors.grey800 }}> + <div style={{ fontWeight: 'bold', fontSize: 15 }} className="py1"> + {finalSectionName.toUpperCase()} + </div> + {this._renderMenuItems(menuItems)} + </div> + ); + } else { + return <div key={`section-${sectionName}`}>{this._renderMenuItems(menuItems)}</div>; + } + }); + const maxWidthWithScrollbar = 307; + return ( + <div> + {this.props.sidebarHeader} + {!_.isUndefined(this.props.versions) && + !_.isUndefined(this.props.selectedVersion) && + !_.isUndefined(this.props.onVersionSelected) && ( + <div style={{ maxWidth: maxWidthWithScrollbar }}> + <VersionDropDown + selectedVersion={this.props.selectedVersion} + versions={this.props.versions} + onVersionSelected={this.props.onVersionSelected} + /> + </div> + )} + <div className="pl1">{navigation}</div> + </div> + ); + } + private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] { + const menuItemStyles = this.props.shouldDisplaySectionHeaders + ? styles.menuItemWithHeaders + : styles.menuItemWithoutHeaders; + const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {}; + const menuItems = _.map(menuItemNames, menuItemName => { + const id = utils.getIdFromName(menuItemName); + return ( + <div key={menuItemName}> + <ScrollLink + key={`menuItem-${menuItemName}`} + to={id} + offset={-10} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <MenuItem + onTouchTap={this._onMenuItemClick.bind(this, menuItemName)} + style={menuItemStyles} + innerDivStyle={menuItemInnerDivStyles} + > + <span style={{ textTransform: 'capitalize' }}>{menuItemName}</span> + </MenuItem> + </ScrollLink> + {this._renderMenuItemSubsections(menuItemName)} + </div> + ); + }); + return menuItems; + } + private _renderMenuItemSubsections(menuItemName: string): React.ReactNode { + if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { + return null; + } + return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); + } + private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { + return ( + <ul style={{ margin: 0, listStyleType: 'none', paddingLeft: 0 }} key={menuItemName}> + {_.map(entityNames, entityName => { + const name = `${menuItemName}-${entityName}`; + const id = utils.getIdFromName(name); + return ( + <li key={`menuItem-${entityName}`}> + <ScrollLink + to={id} + offset={0} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + onTouchTap={this._onMenuItemClick.bind(this, name)} + > + <MenuItem + onTouchTap={this._onMenuItemClick.bind(this, name)} + style={{ minHeight: 35 }} + innerDivStyle={{ + paddingLeft: 16, + fontSize: 14, + lineHeight: '35px', + }} + > + {entityName} + </MenuItem> + </ScrollLink> + </li> + ); + })} + </ul> + ); + } + private _onMenuItemClick(name: string): void { + const id = utils.getIdFromName(name); + utils.setUrlHash(id); + if (!_.isUndefined(this.props.onMenuItemClick)) { + this.props.onMenuItemClick(); + } + } +} diff --git a/packages/react-shared/src/ts/components/section_header.tsx b/packages/react-shared/src/ts/components/section_header.tsx new file mode 100644 index 000000000..ee34a6c09 --- /dev/null +++ b/packages/react-shared/src/ts/components/section_header.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import { Element as ScrollElement } from 'react-scroll'; + +import { HeaderSizes } from '../types'; +import { colors } from '../utils/colors'; +import { utils } from '../utils/utils'; + +import { AnchorTitle } from './anchor_title'; + +export interface SectionHeaderProps { + sectionName: string; + headerSize?: HeaderSizes; +} + +interface DefaultSectionHeaderProps { + headerSize: HeaderSizes; +} + +type PropsWithDefaults = SectionHeaderProps & DefaultSectionHeaderProps; + +export interface SectionHeaderState { + shouldShowAnchor: boolean; +} + +export class SectionHeader extends React.Component<SectionHeaderProps, SectionHeaderState> { + public static defaultProps: Partial<SectionHeaderProps> = { + headerSize: HeaderSizes.H2, + }; + constructor(props: SectionHeaderProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const { sectionName, headerSize } = this.props as PropsWithDefaults; + + const finalSectionName = this.props.sectionName.replace(/-/g, ' '); + const id = utils.getIdFromName(finalSectionName); + return ( + <div + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <ScrollElement name={id}> + <AnchorTitle + headerSize={headerSize} + title={ + <span + style={{ + textTransform: 'uppercase', + color: colors.grey, + fontFamily: 'Roboto Mono', + fontWeight: 300, + fontSize: 27, + }} + > + {finalSectionName} + </span> + } + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </ScrollElement> + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/react-shared/src/ts/components/version_drop_down.tsx b/packages/react-shared/src/ts/components/version_drop_down.tsx new file mode 100644 index 000000000..d9e49b205 --- /dev/null +++ b/packages/react-shared/src/ts/components/version_drop_down.tsx @@ -0,0 +1,39 @@ +import * as _ from 'lodash'; +import DropDownMenu from 'material-ui/DropDownMenu'; +import MenuItem from 'material-ui/MenuItem'; +import * as React from 'react'; + +import { utils } from '../utils/utils'; + +export interface VersionDropDownProps { + selectedVersion: string; + versions: string[]; + onVersionSelected: (semver: string) => void; +} + +export interface VersionDropDownState {} + +export class VersionDropDown extends React.Component<VersionDropDownProps, VersionDropDownState> { + public render() { + return ( + <div className="mx-auto" style={{ width: 120 }}> + <DropDownMenu + maxHeight={300} + value={this.props.selectedVersion} + onChange={this._updateSelectedVersion.bind(this)} + > + {this._renderDropDownItems()} + </DropDownMenu> + </div> + ); + } + private _renderDropDownItems() { + const items = _.map(this.props.versions, version => { + return <MenuItem key={version} value={version} primaryText={`v${version}`} />; + }); + return items; + } + private _updateSelectedVersion(e: any, index: number, semver: string) { + this.props.onVersionSelected(semver); + } +} diff --git a/packages/react-shared/src/ts/globals.d.ts b/packages/react-shared/src/ts/globals.d.ts new file mode 100644 index 000000000..9b0bcf845 --- /dev/null +++ b/packages/react-shared/src/ts/globals.d.ts @@ -0,0 +1,7 @@ +declare module 'react-highlight'; + +// is-mobile declarations +declare function isMobile(): boolean; +declare module 'is-mobile' { + export = isMobile; +} diff --git a/packages/react-shared/src/ts/index.ts b/packages/react-shared/src/ts/index.ts new file mode 100644 index 000000000..3b50c0117 --- /dev/null +++ b/packages/react-shared/src/ts/index.ts @@ -0,0 +1,12 @@ +export { AnchorTitle } from './components/anchor_title'; +export { MarkdownLinkBlock } from './components/markdown_link_block'; +export { MarkdownCodeBlock } from './components/markdown_code_block'; +export { MarkdownSection } from './components/markdown_section'; +export { NestedSidebarMenu } from './components/nested_sidebar_menu'; +export { SectionHeader } from './components/section_header'; + +export { HeaderSizes, Styles, MenuSubsectionsBySection, EtherscanLinkSuffixes, Networks } from './types'; + +export { utils } from './utils/utils'; +export { constants } from './utils/constants'; +export { colors } from './utils/colors'; diff --git a/packages/react-shared/src/ts/types.ts b/packages/react-shared/src/ts/types.ts new file mode 100644 index 000000000..88fadcc09 --- /dev/null +++ b/packages/react-shared/src/ts/types.ts @@ -0,0 +1,25 @@ +export interface Styles { + [name: string]: React.CSSProperties; +} + +export enum HeaderSizes { + H1 = 'h1', + H2 = 'h2', + H3 = 'h3', +} + +export interface MenuSubsectionsBySection { + [section: string]: string[]; +} + +export enum EtherscanLinkSuffixes { + Address = 'address', + Tx = 'tx', +} + +export enum Networks { + Mainnet = 'Mainnet', + Kovan = 'Kovan', + Ropsten = 'Ropsten', + Rinkeby = 'Rinkeby', +} diff --git a/packages/react-shared/src/ts/utils/colors.ts b/packages/react-shared/src/ts/utils/colors.ts new file mode 100644 index 000000000..2eead95c7 --- /dev/null +++ b/packages/react-shared/src/ts/utils/colors.ts @@ -0,0 +1,48 @@ +import { colors as materialUiColors } from 'material-ui/styles'; + +export const colors = { + ...materialUiColors, + gray40: '#F8F8F8', + grey50: '#FAFAFA', + grey100: '#F5F5F5', + lightestGrey: '#F0F0F0', + greyishPink: '#E6E5E5', + grey300: '#E0E0E0', + beigeWhite: '#E4E4E4', + grey350: '#cacaca', + grey400: '#BDBDBD', + lightGrey: '#BBBBBB', + grey500: '#9E9E9E', + grey: '#A5A5A5', + darkGrey: '#818181', + landingLinkGrey: '#919191', + grey700: '#616161', + grey750: '#515151', + grey800: '#424242', + darkerGrey: '#393939', + heroGrey: '#404040', + projectsGrey: '#343333', + darkestGrey: '#272727', + dharmaDarkGrey: '#252525', + lightBlue: '#60A4F4', + lightBlueA700: '#0091EA', + linkBlue: '#1D5CDE', + darkBlue: '#4D5481', + turquois: '#058789', + lightPurple: '#A81CA6', + purple: '#690596', + red200: '#EF9A9A', + red: '#E91751', + red500: '#F44336', + red600: '#E53935', + limeGreen: '#66DE75', + lightGreen: '#4DC55C', + lightestGreen: '#89C774', + brightGreen: '#00C33E', + green400: '#66BB6A', + green: '#4DA24B', + amber600: '#FFB300', + orange: '#E69D00', + amber800: '#FF8F00', + darkYellow: '#caca03', +}; diff --git a/packages/react-shared/src/ts/utils/constants.ts b/packages/react-shared/src/ts/utils/constants.ts new file mode 100644 index 000000000..562ab776b --- /dev/null +++ b/packages/react-shared/src/ts/utils/constants.ts @@ -0,0 +1,20 @@ +import { Networks } from '../types'; + +export const constants = { + DOCS_SCROLL_DURATION_MS: 0, + DOCS_CONTAINER_ID: 'documentation', + SCROLL_CONTAINER_ID: 'documentation', + SCROLL_TOP_ID: 'pageScrollTop', + NETWORK_NAME_BY_ID: { + 1: Networks.Mainnet, + 3: Networks.Ropsten, + 4: Networks.Rinkeby, + 42: Networks.Kovan, + } as { [symbol: number]: string }, + NETWORK_ID_BY_NAME: { + [Networks.Mainnet]: 1, + [Networks.Ropsten]: 3, + [Networks.Rinkeby]: 4, + [Networks.Kovan]: 42, + } as { [networkName: string]: number }, +}; diff --git a/packages/react-shared/src/ts/utils/utils.ts b/packages/react-shared/src/ts/utils/utils.ts new file mode 100644 index 000000000..b3acb081e --- /dev/null +++ b/packages/react-shared/src/ts/utils/utils.ts @@ -0,0 +1,45 @@ +import isMobile = require('is-mobile'); +import * as _ from 'lodash'; +import { scroller } from 'react-scroll'; + +import { EtherscanLinkSuffixes, Networks } from '../types'; + +import { constants } from './constants'; + +export const utils = { + setUrlHash(anchorId: string) { + window.location.hash = anchorId; + }, + scrollToHash(hash: string, containerId: string): void { + let finalHash = hash; + if (_.isEmpty(hash)) { + finalHash = constants.SCROLL_TOP_ID; // scroll to the top + } + + scroller.scrollTo(finalHash, { + duration: 0, + offset: 0, + containerId, + }); + }, + isUserOnMobile(): boolean { + const isUserOnMobile = isMobile(); + return isUserOnMobile; + }, + getIdFromName(name: string) { + const id = name.replace(/ /g, '-'); + return id; + }, + getEtherScanLinkIfExists( + addressOrTxHash: string, + networkId: number, + suffix: EtherscanLinkSuffixes, + ): string | undefined { + const networkName = constants.NETWORK_NAME_BY_ID[networkId]; + if (_.isUndefined(networkName)) { + return undefined; + } + const etherScanPrefix = networkName === Networks.Mainnet ? '' : `${networkName.toLowerCase()}.`; + return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; + }, +}; |